carlos / todomvc-bakeoff Public
π€ CARLOS working conventions
How the family builds. These conventions exist because each was learned the hard way at least once; the incident is noted where it names the rule.
Repo documents
- AGENTS.md β "how we build and the decisions we've committed to." Written before the code (factor V: the intent is the system β the code should have to argue with something). Opens with the one rule. Contains: settled decisions (dated, attributed, with rejected alternatives), named laws, the workflow, and a "Deliberately not built yet" list with the trigger for each deferral. One file, for every agent that reads the repo β not one per vendor. Older repos still carry it as CLAUDE.md; rename it to AGENTS.md the next time you touch one, rather than keeping two.
- README.md β user-facing: what it is, run it, honest trade-offs.
- DESIGN.md β the spec, what gets built. PLAN.md β why it is shaped this way, the roadmap.
- docs/ β design records and briefs, written before the build ("Brainstormed with Paul; decisions recorded here"), each with a Status line (
built/approved design β not yet built), annotated in place when built. The doc's decisions are binding: if one proves wrong in practice, stop and flag it in the PR rather than redesigning.
Git and PR playbook
- Worktree per session, always. Multiple sessions (agents and humans) are active at once; the main checkout's uncommitted state belongs to another session. Never build in the main checkout β reading it is fine, writing is not.
- The cadence is: worktree β branch β regular pushed commits β squash-merge. Commit as you go and push every commit. The branch is where work in progress lives; you do not wait to be asked to put it there, and a session that ends with unpushed work has lost it as far as everyone else is concerned. Keep the PR small and focused.
- Squash-merge to main when all three hold: the work is done, a human has approved it, and CI is green (
gh pr merge --squash --delete-branchon GitHub; on amadan, where the branch is the pull request,amadan branch mergemakes a merge commit and-squashlands one commit; never squash by hand there, or the branch shows as deleted). Two of the three is not a merge β a green run on unapproved work waits, and an approval over a red run waits. Never commit to main. Deploy only merged main, and only when asked. - "CI green" binds once CI exists, and a new repo's early work is to make it exist. A repo with no CI has no green to wait for: the condition is not waived, it simply has nothing to read yet, so the first merges of a new project land on done + approved. Standing up CI is one of the first work items, not a prerequisite nobody can satisfy β and it is the gate you already run locally, kept as one definition rather than a second list that drifts, so debugging CI is running it. Once the repo has CI all three bind absolutely and the carve-out is spent.
- Commit subjects are area-prefixed prose that says why (
web: the front door is a homepage β pitch left, sign-in right;auth: delete the recovery passphrase, root and branch). Comment the why, not the what. - Every commit carries
Co-Authored-By: Claude <model> <noreply@anthropic.com>; the human is the author of record.
Canaries
- Every session gets its own canary; review happens on a deployed canary, never on localhost. (Adopted after two sessions sharing a dev host produced an evening outage.) Local stacks are for automated drives only.
- On the platform, a canary is a channel:
carlos canarydeploys tocanary/<slug>(a per-session dead-end rung, zero bake) and the canary host serves it at<slug>-<app>.<sqid>.<domain>β same rule, no box. Off-platform, canaries multiplex onto the devbox: own port/state/unit/TLS at<branch>.<dev-host>. Shared hosts (prod, the plain dev host) only ever carry main builds β enforced by the deploy path, not memory. - Report the complete clickable canary URL in every reply while iterating, with a short list of what to test. The human clicks through mid-flight; never make them scroll back for the URL.
- Tear your canary down when the branch merges. Never echo claim links or secrets into the session β the prompt ledger publishes what gets typed.
Testing
The layers, in order:
go test ./...,go vet ./...,gofmt -l .β all clean, always. Nothing lands untested; a bug fix carries its regression test. When gating on a test run, never pipe it β a pipe eats the exit code (a red suite once merged and deployed exactly this way). A guard belongs inside the test binary, never in a shell step around it β a guard a shell step can satisfy is a guard a shell step can be wrong about (one browser-required check went silently unrun for weeks).- Prefer a parsing test to a browser for anything computable from source. Contrast ratios computed from the token file's declarations (WCAG AA, both themes, no browser); network reachability rules parsed from the served JS; performance fixes shipped with an
EXPLAIN QUERY PLANassertion over the exported production query string so the test cannot drift from the real SQL. When a rule needs an exception, the allowlist entry pins the exact occurrence count, not just the file β a new call site inside an exempted file must still fail. - JS logic DOM-free under
node --test, wired intogo testvia a smalljstest_test.go, so one command runs everything.node --checkalone is not enough β it parses as CommonJS and swallows ES-module errors that crash Safari; check a.mjscopy and load it in a real engine. - uidump β dump, don't drive (adopted by the platform console). An env-gated test (
UIDUMP_DIRset, else skipped) boots the real handler in-process overhttptest, seeds a fixture world rich enough that every state has a subject, signs in, GETs every named screen, and writes each response body to disk with asset paths rewritten relative so the files stand alone. An agent then screenshots the static files directly with headless Chrome (--headless --screenshot=β¦), re-shooting dark mode by stamping the theme attribute and mobile at a narrow window. Near-zero cost, zero flake, works on a display-less box; adding a screen is one map entry β and "add your screen to the dump" is a checklist line in every plan, or coverage rots. It renders HTML+CSS only and asserts nothing itself: it is the visual-review gate, not the invariant gate. - Browser drives are for the residue that needs a JS engine and real layout geometry β enhancement-script behaviour, popover sizing, overflow, theme resolution β env-gated, with assertions written as the named bug class they caught. Playwright, or chromedp for pure-Go. Passkey flows run in Chromium β the virtual authenticator is a CDP feature; a separate passkey-free smoke runs in WebKit, Safari's engine, because it breaks first. Loud on purpose: fail on any console error, page error, or failed request, and junk-scan every view for
undefined,null,[object Object],NaNβ the bug class that renders perfectly and says nothing (a straynulltext node included: nativeappend()stringifies one). A skip is not a pass: report executed-vs-skipped. - The merciless greps (server-blind apps): end-to-end tests that grep raw SQLite bytes and S3 objects for plaintext content and names.
- After every deploy: the console check in WebKit against the live host (the keymail rule) β a JS syntax error takes the whole client down and only a real engine sees it; WebKit specifically, because it is the strictest engine and Safari users hit it first.
- Security scan (Aikido) before ending a work cycle; triage every finding; never publish over an untriaged one.
Simulation suites are the spec for convergence-critical engines (sync, progression): any behaviour change changes the suite first. Golden vectors pin multi-language implementations (see blueprint.md).
Ending a session
Every session ends through the committed /end-session gate. The shape:
- Clean tree β nothing uncommitted, nothing unpushed, no orphan worktrees.
- Gates green: build, vet, gofmt, tests (unpiped), browser drives with executed-vs-skipped counts.
- Security scan clean or triaged.
- Ledgers accrued (below), reviewed word-by-word for secrets before publishing β never publish text you haven't read; when unsure, redact with an annotated
β¦redacted: categoryβ§and say what and why. - Deploy state reported; canaries torn down or handed over.
- A handoff report: PRs, clickable canary URLs with what to verify, open threads, and what was deliberately not done.
Ledgers and transparency
The family practices radical operational transparency on a public status site: prompt registry (every human prompt behind the code, whole and in order, credentials redacted in place), carbon tally (token spend β COβe bands, plus the boxes' share), contributors (humans and AI models on one metric, from git trailers), roadmap and milestones written in member language β what changed for a member, never how the boxes are run. Ledger updates ship exactly like code: commit β PR β deploy. No hostnames, no account IDs, no third-party personal data in anything published.
AI authorship (factor X)
- Machine-authored text is marked π€ wherever it surfaces β visible, immediately before the heading or block it covers; a marker on a heading cascades. Never hide the π€ in a comment.
- A person marker (π¨/π€/π§) certifies human text: an LLM must not rewrite, rephrase, condense, or delete it. No marker means ambiguous β never claim human authorship for unmarked text.
- Disclosure extends to product: AI features are opt-in, never required, always disclosed, and every path works with the AI switched off β this is a hard constraint, not an aspiration. Nothing assumes AI stays cheap.
- Uncompiled or unverifiable code is marked (
// UNVERIFIED: written without an iOS toolchain) and kept in separate commits.
Working with the human
Patterns visible across every prompt history, useful calibration for agents:
- Directives are terse ("go", "merge", "Deploy!", "keep going until it's done"). Long autonomous runs are normal; write the design doc, then build until there's something live to click.
- Bug reports arrive as lived user experience from the live product, in product language. Reproduce on a canary, fix, and ask "is this flow being tested?" β because that question is coming.
- Product shape is given, parameters are left open ("allows edits for what, 5 minutes? 2 minutes?") β make the judgement call and flag it.
- Sibling apps are the design vocabulary ("like Eleven's home server", "Woodstar's shape, copied on purpose"). Check the sibling before inventing.
- Calm UI: red is for danger only, no toasts, no nerdspeak in the default flow, buttons stay where the thumb is, feedback is inline and honest.