# How we work (/dev/how-we-work)



This document owns how people and agents work: the habits the owner has asked for, for developers
and AI agents alike. What the code must be lives in [development principles](./development.md).

## Where rules live [#where-rules-live]

* Write down how we work here, in the repository, not in an agent's private memory. Anyone
  opening the repository, person or agent, should find the same rules. Agent memory is only for
  things that concern one agent, never for how the project works.
* Each fact has one home, as [development principles](./development.md#development-principles)
  require; that applies to these rules too.
* The root `AGENTS.md` and `CLAUDE.md` are pointers to [Agents: start here](./agents.md) and hold no
  rule of their own: `CLAUDE.md` is an `@import` of that page, so Claude Code loads it; `AGENTS.md`
  names it, for Codex and every other tool. A rule written into either is in the wrong place (owner,
  2026-10-10: "Agents.md and Claude.md in the root must just be a pointer to docs area for agents").

## See something: fix it, or write it down, in the same landing [#see-something-fix-it-or-write-it-down-in-the-same-landing]

Owner, 2026-10-09, after finding a gap each time he asked: "It's rather clear that the agent docs are
wrong if you're seeing issues and not acting on them and informing yourself and devs." So the rule
is general, not only for missing tools:

* An agent or developer who notices anything wrong, slow, missing or left over (a step that waits
  for nothing, a check nobody runs, a branch nobody owns, a doc that says what is no longer true) has
  two choices, both inside the landing they are on: **fix it** when it is small, or &#x2A;*write it into
  `.plans/now.md`** ("What is left, in order": what was seen, where, and what would fix it), a plan
  file when it is big. Mentioning it in a chat or a report alone is neither.
* **Everyone hears it through the repo**: `dev:status` prints that list to every session (agents
  open with it), so what one person saw reaches all, and the next landing takes the top of it.
* **"Done" is the end of a sweep, not of a list.** Before saying it: branches and worktrees, open
  pull requests, `remy cloudflare versions`, `remy plans check`, a search for the words the change retired, the docs
  the change touched. Show the sweep.

## Use the project's own tools first [#use-the-projects-own-tools-first]

* Start with `npm run setup`; [developer tooling](./tooling.md) says what it installs.
* Before doing any step by hand (installing, upgrading, releasing, deploying, translating), find its script:
  `npm run` lists them, in whichever repository you are in; an app has the same ones. Owner,
  2026-09-26, after an app was upgraded by hand past `npm run upgrade`: "you know we do not reinvent wheels".
* Run commands through the package.json scripts, check the real page with the Chrome DevTools tools
  (`npm run browser`), and use the installed skills before reading upstream sources, searching
  `node_modules` or writing one-off scripts.
* Look at the real page with the browser tools before writing a test for it.
* Once the tools can answer a question, stop researching and use them.
* Say how long a step will take before starting anything slower than a few seconds. Each command's
  header comment says its duration and what may run at the same time;
  run post-deploy checks in the background and keep working.

## Choose tools by survey, not by first find [#choose-tools-by-survey-not-by-first-find]

Before a plan names a library or tool:

1. List the realistic candidates, including newer ones found on npm and GitHub.
2. Score them against fixed, weighted criteria, with a source for each score.
3. Name the runner-up and what would make us switch to it.
4. Prove the top two with small scratch builds.

Mark anything not checked as assumed.

## Once a tool is chosen, take its shape [#once-a-tool-is-chosen-take-its-shape]

Bend our system to the tool, not the tool to our system. Before building on a chosen framework:

1. Scaffold its own template and run its own generators (CLI features) in a scratch app.
2. Build it and request every route it makes; list what works and what does not.
3. Change our layout, files and what our code hands the GUI to match, keeping only what an owner rule
   requires (Paraglide's URLs, shadcn's UI).
4. Delete whatever of ours the tool already does. A custom plugin or wrapper has to justify itself
   against the stock way, not the other way round.

Owner, 2026-09-26, after the Fumadocs docs were found half-custom: "It's funny how you don't realise
until you're pushed!" ([the Fumadocs decision](https://github.com/joeblew999/remy-auth/blob/main/.plans/docs-for-consumers.md#decision-fumadocs-fully-2026-09-26)).

## UI: shadcn and TanStack all the way [#ui-shadcn-and-tanstack-all-the-way]

UI is hard and never done, so we take what shadcn and TanStack have spent years getting right,
and write only what they do not provide.

* **shadcn's CLI writes every component and the theme.** Components come from `npm run ui:components`,
  the theme from `npm run ui:theme` (shadcn's default Nova style, neutral, Geist: no preset, no
  branding of our own). `npm run ui:verify` fails the release on any hand edit. The repository uses
  shadcn's monorepo layout, so `shadcn add` run in the app writes into the shared package.

* **Look for a shadcn block before building a layout or a screen:**
  `./node_modules/.bin/shadcn search @shadcn -t registry:block`. Add it with `shadcn add`, keep its
  structure, and replace only its sample data. Do not add constraints the block does not have (it is
  full width, so we are).

* **Compose, do not restyle.** Use variants and semantic tokens; follow the installed shadcn skill's
  rules (Separator, Skeleton, Badge, Empty, Alert, Field instead of hand-made equivalents).

* **Two kinds of page:** site pages for Google and app pages under `/app`, never mixed;
  [paths.js](https://github.com/joeblew999/remy-auth/blob/main/packages/ui/src/paths.js) defines both.

* **Adopt the TanStack library instead of our own code** (Form, Router's Zod adapter, Table, Devtools
  and so on), install its agent skills, then delete the code it replaces.

* Before writing any UI code, ask: does shadcn, TanStack or Paraglide already do this? If yes, use it.

* Look at it: before calling UI work done, screenshot the pages you changed with `npm run shots` (desktop and
  phone, light and dark, English and Arabic) and look at the screenshots. Checks prove behaviour; only
  the screenshots show layout, spacing, overflow and mixed-direction text; name the pages you changed
  (`npm run shots -- /formats --phone`), `--all` only for a sweep (owner, 2026-09-26: "It's
  really depressing how you can't see the look of the web site properly").

### Which TanStack library for what [#which-tanstack-library-for-what]

Checked against [tanstack.com](https://tanstack.com) on 2026-09-26. Beta and alpha libraries change
fast: read their current docs and installed skills before using them, not an agent's memory.

| Library               | Status here                               | Rule                                                                                                                                                                                                       |
| --------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Start, Router, Query  | In use everywhere                         | The app, its pages and all data fetching (with oRPC)                                                                                                                                                       |
| Form                  | In use: the reservation form only         | Every form uses it (login, signup, settings); no hand-rolled form state                                                                                                                                    |
| Pacer                 | Not in use (the docs search is Fumadocs') | Any debounce, throttle, rate limit or queue in the browser                                                                                                                                                 |
| Devtools              | In use in development                     | Keep the Router, Query and Form panels in the one Devtools                                                                                                                                                 |
| Table                 | Next: with the admin screens              | Every list with sorting, filtering or paging, through shadcn's data table                                                                                                                                  |
| Virtual               | When a list gets long                     | Lists of hundreds of rows, starting with the time zones                                                                                                                                                    |
| DB (beta)             | Not yet                                   | Only if we need offline use or live sync; Query covers today's needs                                                                                                                                       |
| AI (beta)             | Not used                                  | The docs' Ask AI is its own page (`/docs/ask`, `/dev/ask`) built from Fumadocs' AI chat pieces on the AI SDK, not an overlay: one scroll, the question at the top ([docs in your AI tools](./ai-tools.md)) |
| Hotkeys (alpha)       | Not yet                                   | Candidate for a search shortcut once it leaves alpha                                                                                                                                                       |
| Store (alpha), Charts | Not needed                                | No app-wide client state and no dashboards yet                                                                                                                                                             |

Moving a library from "Not yet" to in use follows [choose tools by survey](#choose-tools-by-survey-not-by-first-find).

## The docs and the demo move with the code [#the-docs-and-the-demo-move-with-the-code]

remy-auth is what every other Remy repo builds on, and its developer docs are what their agents and
developers read (through the `remy` skill, generated from these pages). So a feature is done when
four things are true: it is in the package for every app, remy-auth's own app uses it where anyone
can see it work, a check fails when it is skipped, and the page that describes it is updated in the
same change. Owner, 2026-10-09: "By make Remy-auth reuses all its features in its own demo it makes it
easy to onboard AI and devs ... the docs are part of what any other agent and dev in other repos get."
A page that describes what the code no longer does is a bug; fix it where you find it.

## Language: Paraglide owns it [#language-paraglide-owns-it]

Paraglide owns all language behaviour: which language a request gets, through its strategies
`url`, `cookie`, `preferredLanguage` and `baseLocale` (set in
[paraglide.mjs](https://github.com/joeblew999/remy-auth/blob/main/packages/ui/paraglide.mjs)), and the localized links, with TanStack Router
carrying them. We do not write framework-neutral layers or our own language code; when Paraglide
lacks something, use its options first and record the gap in the owning plan.

## Translations: one writer [#translations-one-writer]

English is the source; translations follow it, written by one writer at a time. Agents change these
files all the time, so translating inside every feature branch collides and leaves languages
half-updated.

* A feature agent writes English only: the English docs and the base catalog (`messages/en.json`).
  It never edits a translation (`<page>.<lang>.md` in `docs/content`) or another locale's catalog.
* Translation is its own step, on `main` after the merges: `dev:land` runs `remy i18n translate` there,
  last, when something is stale; nobody runs it by itself. The Claude agent,
  pinned in the task, translates exactly what `remy i18n check` lists, and the task commits it; **the commit
  is the mark** (git decides what is stale: a translation is stale when its English changed after the
  translation's last commit). It refuses other branches, uncommitted English, uncommitted translations
  and a second run while one holds the lock, which every worktree shares, so parallel agents cannot set
  off translations or race on them.
* The agent gets no tools: it is handed the English (and, for a stale page, the English diff and the
  current translation) and returns text; the task writes the files it asked for and nothing else.
* What the agent returns is written only when it is the page: every frontmatter field its English
  has, and text after it (`tasks/i18n/docs/page.ts`). A failed agent leaves the page as it was, says
  why and fails the run; `dev:land` then says the translation is still owed, and the next landing
  tries again. `tests/unit/i18n-docs.unit.spec.ts` holds every translated page in the repository to
  the same rule, on every check and on GitHub, the only check a translation's own commit gets. On
  2026-10-09 a failed agent's `null` was committed as a page and stopped the docs building on main.
* A translation that is not a page is deleted, never restored by hand: a missing page is translated
  from the English by the next landing, and a restored one would be marked current by its commit.
* `remy i18n check` only reads, offline, so it is safe anywhere, in any number of worktrees, and as a
  `depends`. `remy i18n translate` is never a `depends`.
* Stale or missing is a warning while pumping (`remy check` prints it) and an error at release
  (`dev:release` runs `remy i18n check` with `I18N_STRICT=1` first).
* Commit only real translation changes to a translation file: any commit to it marks it current. A
  structural change (a rename, a sweep) that touches docs translations is followed by
  `REMY_FLOW=hand npx remy i18n translate docs <file>…`, which re-checks those files against the English in full.
  Catalogs have no such redo: change a catalog's structure (every locale's file) in the same commit as
  `en.json`, so no changed English key is hidden.

The layout and the commands are in [the command line](./tasks.md#translations).

## Plans: few, short, closed [#plans-few-short-closed]

Owner, 2026-09-26: "how overwhelming and frustrating it is to have so much garbage plans". New work is
one line in `.plans/now.md`, in the order it closes. A plan file is written only for work that is big
enough to be parked or to run over weeks; research, analyses and reviews go into the plan they serve,
not a file of their own. A plan closes the day its work ships: a closing line, then `.plans/done/`. Big
features wait in `.plans/parked/`. Closing a plan needs no extra deploy or test run of its own: batch the
code into one deploy and one full run at the end.
The shared `remy plans …` tasks do the moves: `remy plans status` lists what is open, `remy plans close`, `remy plans park` and
`remy plans open` (a parked plan taken up again) add the line, move the file and repoint every link, and `remy plans check` (in the check, `dev:change`) keeps `.plans/` tidy
([tasks](./tasks.md#plans)).

## When the owner delegates decisions [#when-the-owner-delegates-decisions]

When the owner hands over decisions, for example to finish work unattended:

* Decide, and record each decision with its reasons in the plan that owns it.
* Keep every gate green; delegation never loosens a check.
* Leave a full report: what was decided, what was done, what was checked and what was not.

## The flow: four steps, and a guard that refuses the rest [#the-flow-four-steps-and-a-guard-that-refuses-the-rest]

Owner, 2026-10-09: "95% of time has been checking and 5% coding. It can't go on", "It's a question of
what you run locally and what you run in GitHub asynchronously", and "The tooling formalises what
happens at development ... make sure the developer flow is also formalised so that this stupid
fuckups can't happen. And so you and developers use that same flow so that it's fixed forever." So
the flow is code, not a rule to remember: `tasks/dev/flow.ts` is the one place its decisions live.
Its four steps are `dev:change`, `dev:land`, `dev:promote` and `dev:release` (`flow.ts`'s `Step`);
`dev:start` and `dev:done` bracket a piece of work; `dev:status` looks; a guard refuses what is not
one of them. Seven commands, nothing else. remy-auth gets it the way every app does, from the shared
tasks; `npm run setup` installs the guard.

| Step                          | Command                                | Who runs it, and when                                                                                       | What runs                                                                                                                                                                                                                  | Time                                  |
| ----------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| Look                          | `npm run dev:status`                   | everyone, first: every agent session opens with it (a hook), a developer when they sit down                 | main's last commits with GitHub's verdict, what each deployment runs, every worktree ahead and behind main, translations, pull requests, the top of `.plans/now.md`                                                        | seconds                               |
| Before a piece of work        | `npm run dev:start -- <name>`          | whoever does the work, for every piece, a one-line edit included                                            | a worktree of its own from main, installed, its own ports, the guard                                                                                                                                                       | \~1 min                               |
| After every change            | `npm run dev:change`                   | whoever is coding; the only step waited for                                                                 | the check (`remy check`): plans, types with the tasks' own, the plain-function checks (`tests/**/*.unit.spec.ts`), translation status; a build only when a route file changed, the docs only when docs changed             | \~6 s                                 |
| The change leaves the machine | `npm run dev:land -- "<what changed>"` | whoever did the work, on their own, as soon as it is finished and the check is green                        | the check, commit, fast-forward main, push, deploy staging, then translation last when stale. GitHub then runs every language, Google's audits and the consumer fixture in parallel, while you keep coding (`gh run list`) | \~1 min to staging; translation after |
| Production                    | `npm run dev:promote`                  | the owner, or an agent the owner asked in that session                                                      | `remy cloudflare deploy` and the docs Worker, from a pushed main, no gate: staging and GitHub already ran the checks                                                                                                       | \~1 min                               |
| A release                     | `npm run dev:release`                  | the owner, or an agent the owner asked in that session                                                      | `remy packages release`: every check, every language, locally, on purpose, then the tag                                                                                                                                    | \~5 min                               |
| When landed                   | `npm run dev:done`                     | whoever landed, when the piece of work is over; a worktree lives through as many landings as the work takes | the worktree and branch go                                                                                                                                                                                                 | seconds                               |

* **Who runs a step is in the table, and it is never a question.** An agent lands its own finished
  work without asking: staging is part of landing, GitHub checks every landing, and nothing reaches
  production by it. The owner's word is for production and a release, the two steps whose column says
  so, and for provisioning ([development principles](./development.md#development-principles)). An agent that
  reads the table and still asks whether to land has stopped using the flow (owner, 2026-10-10,
  after one did: "Is it absolutely clear when each should be run by you?").

* One area that looks wrong: `npm run test -- only <words>` (the browser checks whose title
  matches, en and ar, \~30 s). It is the only browser run a step does not own.

* **The guard** (`remy guard`, a Claude Code hook on every shell command an agent runs) refuses
  `remy verify`, `npm run test -- every`, `npm run test -- quick`, `npm run test -- remote`, `npm run test -- google`,
  `npm run test -- cwv`, `npm run test -- consumers`, `npm run test -- consumers`, a bare `playwright test` and
  `remy i18n translate` outside the flow, and names the step instead. Every one still runs by hand from a
  terminal, with `REMY_FLOW=hand` in front (`REMY_FLOW=hand npm run test -- every`): the command itself
  asks who is running it (the gate) and refuses a bare run, on every machine. That is a
  person's decision; the guard refuses it from an agent even so, which is the fault it exists for.
  A task whose own job ends with the verification (`npm run setup`, `npm run packages -- upgrade`,
  `npm run upgrade`) says it is asking, so those run as written.

* **These pages never give a command the tooling refuses.** A heavy command is written with
  `REMY_FLOW=hand` in front or named without `npm run`; `tests/unit/dev-flow.unit.spec.ts` reads
  every English page and every task for one run bare, on every check (2026-10-10: eight lines on six
  pages did, and `npm run setup` ended in the refusal).

* A rule that can be a function belongs in `tests/**/*.unit.spec.ts` (no build, no Worker, no
  browser), so it runs on every check for free. The browser is for what only a browser shows.

* **An agent never waits on a long-running step.** `dev:land`, `dev:promote`, `dev:release`, a
  translation, a remote suite: start it in the background, keep working or answer the owner, and act
  when the harness says it finished. Starting it in the background and then polling it in the
  foreground is the same fault (owner, 2026-10-09: "You just did it again for 2 long running
  things"). Only `dev:change` (seconds) is run and waited for.

* **Every step says how long it took.** `dev:land` ends with its parts
  (the check; commit, main and push; staging; translation) and says how long after the start staging
  was up; the translator says for every agent call how long Claude Code took, how much of that was
  the API, and how many tokens it wrote. A slow step is a number in the log, not a feeling (owner,
  2026-10-10, of a landing on staging after 40 seconds whose command ran six minutes: "how can it be
  6 minutes").

* **GitHub's answer comes to you.** A red run comments on the commit (GitHub tells its author), naming
  the run; `dev:change` starts by saying what the last run on main did; `dev:promote` refuses a commit
  whose run is not green or not finished. To reproduce a red job, a person runs
  `REMY_FLOW=hand npm run test -- <level>`, the same command GitHub ran, since the workflow holds no
  logic of its own (`remy verify tooling` checks every script a workflow names exists). An agent reads the
  run's own log (`gh run view <id> --log-failed`) and runs the one area
  (`npm run test -- only <words>`). Report what was tested and what was not; never call
  untested work verified.

* **A landing that fails after the push still finishes what it can, and says where it stopped.**
  Main is pushed before staging; when staging's deploy or its live smoke fails, `dev:land` still runs
  the translation (it is on main, owed regardless), then ends non-zero with the state in one line:
  main pushed at which commit, staging deployed or not, which smoke failed, translation done or not.
  Nothing half-done is left for the next person to discover (owner, 2026-10-10, after a landing stopped
  at the smoke with the translation silently skipped: "now you and other agents are screwed").

* Never pipe a gating command through `grep` or `tail` in a chain: the pipe hides its exit code.
  This once released a version whose checks had failed, and on 2026-10-10 hid a failed `dev:land`.

## Manual work becomes scripts over real tools [#manual-work-becomes-scripts-over-real-tools]

Owner, 2026-09-26: "You need to get to the point that your checking uses mise and the underlying tool!"
and "make sure you have something in docs about using your judgment about things that you do manually
being turned into a mise task that uses a tool ... It's vital because all our repos will be using this."

* Every check runs through a script or a `remy` command that wraps the real tool: the flow's steps and
  the checks (`npm run test`, Playwright), the live smoke after a deploy, `remy plans check`,
  `remy i18n check`, `npm run shots` to look. (The owner's words above say mise, which the scripts
  replaced on 2026-10-11; the rule is the same.)
  No `curl` loops, one-off scripts or ad-hoc greps to decide whether something works: they cannot be
  repeated, apps on the package do not get them, and nobody sees them later.
* Use judgement on everything done by hand, not only checks: the second time you type the same commands
  or reach for a throwaway script, it becomes a command of the package (`packages/ui/src/cli`, so every
  app gets it) that calls the tool that does the job: Wrangler, Playwright, gh, npm.
* No tool found? Do not write a script and move on. Add a line to `.plans/now.md` to survey for one
  ([choose tools by survey](#choose-tools-by-survey-not-by-first-find)); if the search or the change is big,
  write a plan in `.plans/` (or `.plans/parked/`) yourself. Agents create these lines and plans as they
  meet such things; the owner does not have to ask.
* Anything more than one line of a tool is TypeScript: a command of `remy` (`packages/ui/src/cli`,
  type-checked on every check) or, for what is one app's own, a `scripts/*.ts` of that app, never
  bash logic or untyped JavaScript: what the compiler cannot see, an agent misses (owner, issue #9).
  A one-line tool is a `package.json` script ([the plan](https://github.com/joeblew999/remy-auth/blob/main/.plans/tooling-in-typescript.md)).

## Branches: short-lived, deleted after merge [#branches-short-lived-deleted-after-merge]

* Work happens on branches in git worktrees (one per agent); they merge into `main` and are deleted, with
  their worktree, right after the merge. Only `main` and release tags are pushed; a branch goes to GitHub
  only for a pull request, and GitHub deletes it when the PR merges ("Automatically delete head
  branches", on for every repo, 2026-09-26).
* A leftover merged branch (local or on GitHub) is noise, not history: `main` and the tags hold it.

## Sharing one machine between agents [#sharing-one-machine-between-agents]

The machine crashed on 2026-09-25 with about ten agents building and testing at once (load 188),
and timing-sensitive checks failed well before that. So:

* An agent builds and tests only in its own git worktree, never in the main checkout: builds write
  `dist/`, and two builds in one checkout delete each other's files. `dev:start` makes the worktree.
* Each worktree has its own `PREVIEW_PORT` and `DOCS_PREVIEW_PORT`, computed from its name (never
  4190, which browsers block); `PREVIEW_PORT=4232 npm run …` overrides them.
* At most three agents run tests at the same time; research, writing and scratch spikes do not
  count. When more are needed, set `PLAYWRIGHT_WORKERS=2` for each.
* Google's level (`npm run test -- google`, `npm run test -- cwv`) takes a machine-wide lock, so a second
  run waits rather than skewing the first.
* Never chain a deploy after a check with `;`: the flow's steps run each in turn and stop at the first failure.

## Reporting to the owner [#reporting-to-the-owner]

* **After every deploy, the report starts with the live URLs of what was deployed**, whatever else it says
  (owner, 2026-09-26: "I keep telling you ... give me the url when you deploy"): the app
  [https://remy-auth.gedw99.workers.dev](https://remy-auth.gedw99.workers.dev), its staging [https://remy-auth-staging.gedw99.workers.dev](https://remy-auth-staging.gedw99.workers.dev), and for the docs Worker its guide
  [https://remy-auth-docs.gedw99.workers.dev/docs](https://remy-auth-docs.gedw99.workers.dev/docs), developer docs
  [https://remy-auth-docs.gedw99.workers.dev/dev](https://remy-auth-docs.gedw99.workers.dev/dev) and API reference
  [https://remy-auth-docs.gedw99.workers.dev/reference](https://remy-auth-docs.gedw99.workers.dev/reference). `remy cloudflare deploy` and `remy cloudflare docs-deploy` print them last.
* Every report about something the owner can look at gives its URLs: the live sites, the preview
  (`npx remy cloudflare preview` prints it) and a direct link to each page or feature discussed.
* Say what was checked and what was not.
* What is live is asked, not remembered: `npm run dev:status` prints what each deployment is running
  and how far that is from your checkout. Say it from there, not from what you last deployed.

## Many agents, one flow [#many-agents-one-flow]

Owner, 2026-10-09: "formalise the way we workflow the dev work in many agents to use the new dev
flow". Each piece of work, whoever does it, is one worktree through the same commands; main is
the only integrator, and GitHub checks every landing. Nothing here is done by hand.

|            | Command                                | What it does                                                                                                                                                                                                                                                                                                      |
| ---------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Look       | `npm run dev:status`                   | what is going on, asked from the facts' owners: main's last commits with GitHub's verdict, what each deployment runs, every worktree ahead and behind main, translations, pull requests. Every agent session opens with it (a hook `remy guard` registers); a developer runs it when they sit down                |
| Start      | `npm run dev:start -- <name>`          | a worktree on branch `<name>` from main (`.claude/worktrees/<name>`), installed (`npm ci`, the app's `local` script), with ports of its own and the flow's guard. Claude Code's own worktrees live at the same place and are only a folder: inside one, `npm run dev:start` with no name prepares it the same way |
| Code       | `npm run dev:change`                   | the check, in seconds, after every change                                                                                                                                                                                                                                                                         |
| Land       | `npm run dev:land -- "<what changed>"` | main merged in first (a conflict stops with the files named), the check, commit, fast-forward main, push, staging, then translation last when stale (the Claude subscription on this machine, never GitHub). GitHub runs the heavy checks; a red run comments on the commit                                       |
| Finish     | `npm run dev:done`                     | the landed worktree and branch go; refuses while anything is unlanded                                                                                                                                                                                                                                             |
| Production | `npm run dev:promote`                  | from main, only a commit GitHub passed, on the owner's word                                                                                                                                                                                                                                                       |

* **Split by independence.** An orchestrator splits work into parts that touch different files, and
  starts one agent per part with `dev:start`. A part that needs another's result waits for that
  landing; it does not share a worktree.
* **Spike first when the risk is unknown**: one agent proves the risky point and lands or stops.
* **Every part proves itself**: it lands with its own check in `tests/`, a plain function
  (`tests/**/*.unit.spec.ts`) when it can be one, in the browser when only a browser shows it.
* **Landings serialise themselves.** Two agents landing at once: the second's `dev:land` sees main
  moved, merges it in, checks again and lands. The translation step is one writer across worktrees.
* **Hands-on pass before production.** Passing checks is not the end: use the piece on staging in a
  real browser, on a phone's width, and fix what feels bad. Then `dev:promote`.
* **An agent never waits in the foreground** on `dev:land`, `dev:promote` or a GitHub run; it starts
  them in the background and acts on the result. Only `dev:change` is waited for.
* **Everything asynchronous comes back to everyone** ([the plan](https://github.com/joeblew999/remy-auth/blob/main/.plans/dev-feedback.md)):
  a red GitHub job writes on the commit, naming itself; `dev:land` says when main is red; `dev:promote`
  writes on the commit what went live; `dev:status` shows all of it to whoever asks, and every agent
  session asks first.
