# Development rules

These are the rules for writing code in this repository. They apply to people and to AI agents alike. Each rule is an instruction, the reason for it, and the check that catches a break: a guard test, a CI workflow, or "review only" when a reviewer has to catch it.

A guard that fails names its rule. Fix the cause and leave the guard alone. Several guards keep a list that may only shrink. To grow one, put `[ratchet +N: why]` in a commit message and get a reviewer's yes (preflight R1).

The rules come from the spec ([principles](https://vyre.run/docs/architecture/spec.md#2-principles), [security floor](https://vyre.run/docs/architecture/spec.md#11-the-security-floor), [engineering rules](https://vyre.run/docs/architecture/spec.md#14-engineering-rules)), the ADRs in `docs/adr/`, the guard tests in `test/`, `scripts/team/preflight.mjs`, and the owner's standing rulings. When this file and the code disagree, open an issue. One of them is wrong.

When you add, change or retire a rule, change it here in the same commit as its guard. A rule here with neither a guard nor "review only" is a bug.

Groups: [Architecture](#architecture), [Security](#security), [UI and design](#ui-and-design), [Data and records](#data-and-records), [Flows](#flows), [Agents and prompts](#agents-and-prompts), [Writing](#writing), [Tests and CI](#tests-and-ci), [Releases](#releases), [Git and commits](#git-and-commits).

## Architecture

1. **Route every action on people, records, secrets and models through the kernel.** One place decides who may do what. Check: `kernel/contracts/contracts.test.js`, `kernel/size.test.js`, `test/boundaries.test.js`.
2. **Call other parts through the registry and events. Do not import their files.** Anyone may import the kernel (`core/config`, store, events, modules, presence, daemon) and `lib/<name>`. A lib imports only the kernel and other libs. This keeps any module removable. Check: `test/boundaries.test.js`.
3. **Build every part as a module** with a `module.json` and an entry file: core services, harness pieces, surfaces and third-party additions. One loader means one set of checks. Check: `test/module-sdk.test.js`, `test/module-sdk-conform.test.js`, `test/module-api-compat.test.js`, `test/modules-boot.test.js`.
4. **Define a tool once.** The definition becomes the MCP tool, the HTTP route and the CLI command, and anything the app does the `vyre` CLI does too. Surfaces then cannot drift apart. Check: `test/mcp-server-tools.test.js`, `test/app-tool-names.test.js` (every tool name a screen sends exists).
5. **Declare every event a module emits** under `watches.emits`. The loader refuses an undeclared event, but only when the line runs. Check: `test/events-declared.test.js` (the `KNOWN` list shrinks only).
6. **Declare every input a tool reads.** The registry refuses undeclared keys from non-module callers, so an undeclared input passes a unit test and fails over the wire. Do not name an input `approval`, the envelope key of a held act's retry. Check: `test/declared-inputs.test.js`, `test/approval-reserved.test.js`.
7. **Declare `projectArg` or `cwdArg` on any tool that takes a project or folder.** The registry then checks the agent's grant once. Without it the tool has no grant check. Check: `test/project-arg.test.js`.
8. **Keep a read free of writes.** A tool that declares `effect: "read"` writes nothing, emits nothing and calls no write tool. A tool that must write declares `effect: "write"` and says who may call it. Check: `test/reads-are-reads.test.js` (`KNOWN_WRITERS` shrinks only).
9. **State what each raw route does.** Declare whether it reads or which methods write, and answer only those. Any page can send a GET, so a route with a side effect must refuse GET. Check: `test/route-declared.test.js`.
10. **Let the registry set the origin of a relayed call.** A module that queues work for later keeps `ctx.origin()` and replays it with `ctx.withOrigin`. Otherwise a relay turns a model's call into the box's own. Check: `test/module-origin.test.js`.
11. **Only append to a module's migration list.** Do not move, edit or remove a released step. An upgraded box has already run the earlier ones. Check: `test/migrations-append-only.test.js` (hashes in `test/migrations.released.json`).
12. **Use the one mechanism for each job, and extend it before you add another.** Two answers to one question cause security bugs. Check, by owner: `test/one-mechanism.test.js` (role list, secret detector, grant model, approval path), `test/one-role-list.test.js`, `test/one-person-surfaces.test.js`, `test/one-identity.test.js`, `test/one-registry.test.js`, `test/ids-single.test.js` (`lib/id.js`), `test/scrub-single.test.js` (`lib/scrub.js`), `test/http-single.test.js` (`lib/http.js`), `test/netguard-single.test.js` (`lib/netguard.js`), `test/cron-single.test.js` (`lib/cron.js`), `test/ws-single.test.js` (`lib/ws.js`), `test/bytes-single.test.js` (`lib/bytes.js`, `kernel/core/canonical.js`), `test/credential-shapes-single.test.js` (`lib/credential-shapes.js`). Anything else: review only.
13. **Read a list from its owner. Do not copy it into a surface.** This covers tool names, role ids, AI providers, model names and tool-policy sets. A copy goes stale the day the owner changes. Check: `test/a5-no-copies.test.js`, `test/cohesion-drift.test.js` (both lists shrink only). Where a copy cannot import `lib/`, generate it with `scripts/sync-copies.mjs` and leave the output alone: `test/generated-copies.test.js`.
14. **Store business data as a record type.** A module's own database holds private operating state only: tokens, caches, per-computer rows, queues, rebuildable indexes. A record type is shared and permissioned. A private table is neither. Check: `test/private-stores.test.js` (a module that opens SQLite states why its data is operational).
15. **Split a source file before it passes 2,000 lines.** Files that large stop getting reviewed. A listed file may not grow. Check: `test/file-size.test.js` (`test/file-size.json`). Added kernel lines need `[kernel +N: why]` after the lead's ruling (preflight K1), and the trusted base has a cap (`kernel/size.test.js`).
16. **Justify a new dependency in one line** in `ALLOWED-NEW.md`, and say in the changelog why it is worth having. The codebase stays small and readable. Check: `test/dependency-guard.test.js` (`test/allowed-dependencies.json`).
17. **Write plain JavaScript:** Node 22.5 or newer, ES modules, JSDoc types with `// @ts-check`, `node:sqlite`, `node:test`, no build step for the core. Anyone can read it. Check: `test/dependency-guard.test.js` for dependencies, review only for the rest.
18. **Race a promise against a clock with `lib/within.js`.** A hand-rolled race with an unref'd timer once let the event loop drain with the call still pending. Check: `test/within-hygiene.test.js`.
19. **Attach an error listener to a child's stdin** before you write to it or end it. An unhandled EPIPE takes vyred down, and try/catch does not see it. Check: `test/stdin-hygiene.test.js`.
20. **Keep idle cheap.** vyred idles under 0.9 percent of one core (p95) and under 1.8 percent sustained (5-sample mean), holds under 60 MB of heap after a full GC, and sets no recurring timer faster than 60 seconds. Heavy work runs at low priority. Vyre runs all day on the person's machines. Resident size is reported but does not gate on Node 24. Check: `npm run perf-check` (`scripts/perf-check`, `BUDGET`) in CI (`perf-idle.yml`), `test/bundle-size.test.js` for the web bundle.
21. **Use public Claude Code surfaces only:** the plugin system and documented CLI flags. Read transcripts only in `core/transcripts`, and fall back to "no history" when that fails. An undocumented behavior can change without notice. Check: `test/provider-adapters.test.js` keeps a provider's formats, flags and files inside `core/sessions/drivers/<provider>` (its list shrinks only). The rest: review only.
22. **Give every setting a reader.** If nothing reads a key declared in `module.json`, wire it or remove it. A toggle that does nothing misleads the person. Check: `test/settings-have-readers.test.js` (`DEAD` shrinks only).
23. **Read behavior switches through `devSwitch()`** (`kernel/devbuild.js`), so a release build ignores the environment. Use the loopback seam in `lib/http.js` in tests only. Check: preflight S1, `test/http-seam.test.js`.
24. **Write an ADR in `docs/adr/` for any decision a later contributor would question:** a trade-off, a boundary, a path you rejected. Number it next in sequence and add it to `docs/nav.json`. The reasoning outlives the author. Check: `scripts/docs-check` for the nav, review only for the content.

## Security

1. **Refuse when you cannot tell who is calling.** If the chain or a definition is unreadable, refuse. Do not fall back to a broader reading. A fallback caused advisory GHSA-32h6-gq86-772f. Check: `test/peer-race.test.js`, the golden tests in `kernel/golden/`. The principle itself: review only.
2. **Hold every outward act at the Gate until the person has seen the final words.** Mark each outward tool in its `module.json` (`outward: true` or a Gate kind). When unsure, mark it outward. The outward moment reads that flag and nothing else. Check: `test/outward-flags.test.js` (each exception carries a reason), `test/presence-bypass.test.js`, `test/one-yes-floor.test.js`.
3. **Treat `mcp` and `harness`, in any form, as model callers. They are never the person.** Ask `lib/caller.js` (`isPerson`, `modelKey`) and write no local helper. An unnamed `mcp` once became the person in chrome, hands and memory. A model, agent, hook or guest never approves its own held act. Check: `test/model-is-never-person.test.js` (`FROZEN` shrinks only), `test/person-only-guard.test.js`, `test/stage-gate-model-paths.test.js`, `test/thread-label-hygiene.test.js`, `test/person-label-hygiene.test.js`.
4. **Take the actor from the kernel's chain, not from a call's input.** A field that names a person (`created_by`, `owner`, `as`, `approver`) is a target or a lookup, and a reviewer signs off on it. A forged field would forge an identity. Check: `test/identity-from-input.test.js`.
5. **Write `reach` on every tool.** An "anyone" entry names the guard that limits it, and a person-only tool says so in the registry as well as in presence. A forgotten line is an open door. Check: `test/reach-explicit.test.js`, `test/reach-registry.test.js`, `test/reach-anyone.test.js`, `test/reach-anyone-behaviour.test.js`, `test/reach-person-only.test.js`, `test/reach-person-split.test.js`, `test/reach-classes.test.js`, `test/agent-reach-classes.test.js`, `kernel/retrofit/agent-reach.test.js`.
6. **Block a plain model session from write and outward tools.** Refuse the call, hold it for the person, or list the tool with a reason. A model's shell can claim any caller label. Check: `test/plain-session-writes.test.js` (`test/plain-session-writes.json`).
7. **Keep sealed fields and vault values away from models, agents, logs and events.** Reveal a value only to a person who just proved presence on their own device, for that one value. Check: the tests under `kernel/seal/`, `test/vault-model-item.test.js`, `test/vault-thread-gate.test.js`, `test/vault-member-gate.test.js`, `test/relay-never-reads.test.js`.
8. **Store a reference to a credential. Do not store the key.** Flows, kits, skills, drafts and configs name `vault://item` or a Connection, because stored definitions get copied, shared and read by models. Check: `test/references-not-values.test.js`.
9. **Define each vendor credential shape once**, in `lib/credential-shapes.js`. A new vendor key is one row. Two detectors disagree. Check: `test/credential-shapes-single.test.js`, `test/credential-pins.test.js`.
10. **Keep added modules away from pairing and setup.** No added module can pair, admit or drop a device, or set the server up, because those change who can reach the server. Check: `test/added-module-never.test.js`, `test/names-reach.test.js`.
11. **Reserve account changes and history rewrites for the person.** Adding or removing accounts and rewriting session history are reach person and PERSON_ONLY (ruling of 13 Oct 2026). Check: `core/modules/account-tools-person-only.test.js`.
12. **Send security code to trust before it lands**, and tag the branch `[reviewed: trust]` (or `[reviewed: lead]`). That code is the floor. Check: preflight R2.
13. **Keep third parties out of the product.** The relay reads no client content, and nothing shipped loads from a public CDN, font host or analytics host. Vendor files, pinned and hashed. Vyre keeps nothing about people on its own servers. Check: `test/relay-never-reads.test.js`, `test/no-cdn.test.js`, preflight S2 (`scripts/team/cdn-hosts.mjs`).
14. **Run no command from a folder someone else can write** through vyred's git. A planted hook or filter driver would run. Check: `test/safe-git.test.js`.
15. **Keep a vyred on a temp home away from real boxes.** It does not look for them or pair with them. A stress run once paired with a live box. Check: `test/link-guard.test.js`.
16. **Make every refusal say what to do next.** A literal message names a real tool, says who decides, or gives an instruction. Give every error code a person can meet an entry in `lib/error-catalogue.js`, so no caller guesses. Check: `test/errors-teach.test.js`, `test/error-catalogue.test.js` (baseline shrinks only).
17. **Ship no other VPN product.** The built-in network is Wink (owner ruling of 4 Oct 2026). Check: the VPN guard in `test/`. Its file name and ratchet carry the other product's word, which this file cannot. The ratchet shrinks only.

## UI and design

1. **Take every component from `@vyre/ui` (`apps/app/ui`).** Screens import no other UI library. The look stays one look. Check: `apps/app/scripts/check-ui-imports.mjs` (`npm run lint:ui` in `apps/app`), which bans `@radix-ui`, `lucide-react`, `nativewind` and the rest outside `ui/`.
2. **Change the library component when a screen needs a change. Do not copy it into the screen.** A copy forks the look, and the next fix misses it. Check: `test/design-rules.test.js` (`second-primitive` fails an import of a second Button, Card, Row, IconButton, Banner, Icon, Avatar, List or Tag; baseline `apps/app/scripts/design-rules.baseline.json` shrinks only).
3. **Add a component to `@vyre/ui` when none covers the need,** then use it from there. The library grows from real needs and stays the one place. Check: review only. `test/design-rules.test.js` counts hand-built layouts (`StyleSheet.create`), so a bypass shows.
4. **Take colour, type and layout from tokens.** Set no colour literal, `fontSize`, `fontFamily`, `fontWeight`, `lineHeight` or `letterSpacing` in a screen. A space's look, the paper scheme and a person's contrast choice reach screens only through tokens. Check: `test/design-rules.test.js` (`raw-colour`, `type-literal`), `apps/app/src/theme/raw-colours.test.js`, `test/theme-defaults.test.js`, `test/tokens.test.js`.
5. **Keep the retired first accent colour retired.** The owner picked Bone on 30 Sep 2026: no accent hue, cream on dark, ink on paper. Check: the colour guard in `test/` (its file name carries the retired colour's word, which this file cannot).
6. **Draw Vyre's own screens from blocks.** A new route under `apps/app/app/u` reaches `BlockScreen`, or `test/key-screens.test.js` lists it as hand-written with a reason. That forces a decision for each new screen. Check: `test/key-screens.test.js`.
7. **Use a button where you would show a terminal command.** No string a person reads contains a `vyre ...` command. People should not need a shell. Check: `test/design-rules.test.js` (`command-line`).
8. **Format time with `lib/time`, in a zone the caller names:** the viewer's clock, and the space's. Times follow the person's zone and the space's. Set no `toLocaleTimeString`, `toLocaleDateString`, `getHours` or `getMinutes` in a screen. Check: `apps/app/src/time/show.test.js` ("no screen formats a time by itself"; form inputs and the hidden Glass are exempt).
9. **Keep internal words off screens.** Write "Vyre", "the box" and "the Vyre app". Do not write "vyred", "switchboard", "no such tool", "Deck" or "as Claude Code does". When a literal must carry one, end its line with `// internal-word: <why>`. Check: `test/no-internal-words.test.js`, `test/site-words.test.js`, `test/no-deck.test.js`.
10. **Hold the web bundle to its budget.** The first load stays under the ceiling and grows no more than 10 percent over the recorded baseline. A heavy first load is a slow product. Check: `test/bundle-size.test.js` (`.github/scripts/bundle-size.mjs`).

## Data and records

1. **Give each record type one keeper.** `kernel/gateway/ownership.js` holds the one rule for which module keeps a type and who may write a project, seat or task. Check: `kernel/gateway/ownership.test.js`.
2. **Write nothing for one industry.** Code, schema, defaults and copy stay neutral across trades. Templates and kits carry the specifics. Vyre is one product for any team. Check: review only. `test/hygiene.test.js` keeps real businesses out of shipped code.
3. **Permission records at the kernel.** A protected type is read through no route that lets a member reach another person's records. Check: the tests in `kernel/gateway/` (for example `aggregate-visibility.test.js`, `attr-filter.test.js`).
4. **Delete when a person says forget.** The record, its events, its history and any task text derived from it are gone, including after a restart. Check: `test/records-forget.test.js`.
5. **Keep placeholders out of stored data.** No record, task or logged event holds "[object Object]" or the text "undefined" where a value belongs. A task once carried `flow: "[object Object]"`. Check: `test/flows-data-hygiene.test.js`.
6. **Mint record ids with `lib/id.js`.** Ids sort by time. Keep secrets, tickets, nonces and passwords as random bytes with no time in them. Check: `test/ids-single.test.js`.
7. **Read a person's identity from `spaces.identity.id`.** Pairing and install derive no id of their own, since two ids split one person's data. Check: `test/one-identity.test.js`, `test/identity-one.test.js`, `test/one-registry.test.js`.

## Flows

1. **Guard every tool a Flow may run.** A tool listed under `flow.steps` in `module.json` checks the person's chain itself, and `test/flow-step-guards.json` names that guard. The Flows runner does not consult the kernel's action table, so a read tool has no other gate. Check: `test/flow-step-guards.test.js` (a stale line fails too).
2. **Do not republish a change the Flows host published.** A task change that re-enters the bus loops forever, and stop() once never returned. Check: `test/flows-publish-once.test.js`.
3. **Name credentials by reference in a Flow.** See Security rule 8. Check: `test/references-not-values.test.js`.
4. **Parse cron in `lib/cron.js` only,** so a watcher and a Flow with the same schedule fire together. Check: `test/cron-single.test.js`.
5. **Keep a model out of a stage gate.** When a person must do a gate's task, no path through a model skips it. Check: `test/stage-gate-model-paths.test.js`.

## Agents and prompts

1. **Keep one instructions file per agent and per project: `AGENTS.md`.** A session starts in its project folder and reads it on start. Add no second file. Check: review only. `vyre module new` scaffolds one.
2. **Give the person's assistant the person's reach, minus listed exceptions with a reason.** `core/modules/agent-reach.js` classifies tools as open or person-only, and no tool sits in both. Check: `test/agent-reach-classes.test.js`, `kernel/retrofit/agent-reach.test.js`.
3. **Keep a tool description near 25 words.** Every listed tool costs every session tokens. Long legacy ones sit in a baseline that shrinks; a new tool meets the cap. Check: `test/description-lint.test.js` (`test/description-baseline.json`).
4. **Keep the always-offered tools within about 30 tools and 6,000 tokens.** Everything else goes through `tools_find` and `tools_call`. Check: `test/tools-budget.test.js`, `test/tools-find-quality.test.js`.
5. **Name tools by the one grammar.** Names are an interface for models, and the rename table must stay sound. Check: `test/tool-names.test.js`, `test/tool-names-gates.test.js`, `test/tools-text-names.test.js`.
6. **Write `docs/agents` short, true and for agents.** Each page states a token budget and a `when`, no human page links to one, and the site does not publish them. Generated blocks match the code (`scripts/gen-agent-docs.mjs`). An agent pays for each token it reads. Check: `test/agent-docs.test.js`.
7. **Run a woken or lent agent inside the reach of the person it acts for.** An agent is never the person. Check: `test/one-yes-floor.test.js`, `test/agent-change-daemon.test.js`, the `test/reach-*.test.js` set.
8. **Call models through the one inference door** (`model.call`, `kernel/contracts/model.d.ts`), and take model names from `sessions.models`. Do not hardcode a model name in a surface. Check: `test/cohesion-drift.test.js`.

## Writing

1. **Do not write an em dash or the section-sign character.** Use a colon, a comma or two sentences. Write "Section 5.1". Check: `scripts/docs-check` (the `characters` check in `scripts/lib/docs/check.js`, run by `test/docs-check.test.js`) covers published docs and their includes. Code, commit messages and this file: review only.
2. **Put no personal brand, real name, real business or secret in the repo.** That covers code, docs, tests, fixtures and commits. Use the sample world: the user alex, Juniper Studio, Northwind Bakery, the agents juno and kit, and `example.com`. Vyre is a product, not a portfolio. Check: `test/hygiene.test.js` and `scripts/lib/hygiene.js`, which `scripts/docs-check` shares and extends to emails and IP addresses outside the example and documentation ranges.
3. **Write plain product words, free of any industry.** Use the second person, short paragraphs and fenced commands. Skip marketing adjectives, "simply" and "seamless". The reader could be on any team. Check: review only, plus the sample-world and word checks above.
4. **Say Personal, My Cloud and Cloud for spaces.** Never "Basic" or "Pro" in text people read. Private chats are encrypted to the people in them, times follow the person's zone and the space's, and replies quote in the same timeline. These are the 0.2.9 docs rulings. Check: `test/docs-rulings.test.js`.
5. **Ship a doc page with each feature,** in the same change. The page has front matter and a place in `docs/nav.json`, and its links and anchors resolve. A feature without a page does not exist for users. Check: `npm run docs:check`, `test/docs-check.test.js`, `test/docs-build.test.js`.
6. **Name only commands, tools, config keys and variables that exist.** A stale hint misleads more than no hint. Check: `test/docs-index.test.js` (the terms index in `scripts/lib/docs/terms.js`) and the `stale` check in docs-check.
7. **Regenerate generated pages. Do not edit them.** Run `npm run docs:ref`, `scripts/gen-architecture-map.mjs` and `scripts/gen-agent-docs.mjs`. Hand edits get overwritten. Check: `test/architecture-map.test.js`, `test/agent-docs.test.js`, preflight G1.
8. **Keep the architecture map a 30-minute read** that names every top-level folder, kernel folder and module. Check: `test/architecture-map.test.js`.
9. **Say "the Vyre app" in docs people read, not "the Deck".** History, ADRs and release notes are exempt. Check: `test/no-deck.test.js`.
10. **Apply the same word list to the app, the site and `llms.txt`.** Check: `test/no-internal-words.test.js`, `test/site-words.test.js`, `test/installer-copy.test.js`.

## Tests and CI

1. **Write the test that would have caught the bug, and confirm it fails without your fix.** A test that never failed proves nothing. Tag a behavior-free change `[refactor]`. Check: preflight T5.
2. **Put tests next to their code,** run by `node --test`, with cross-module tests in `test/`. Get a temp `VYRE_HOME` from `tempHome` and make folders only under `SCRATCH` (`test/scratch.mjs`). Tests must never touch a real `~/.vyre`, real transcripts or the screen. Check: `test/tmp-guard.test.js` (the leak guard `test/tmp-guard.mjs` runs before and after `npm test`), `test/temp-home-claude.test.js`.
3. **Import the Mac guard in every test file** (`scripts/mac-test-guard.mjs`), so a test never runs on a person's Mac. A test once reached a live machine. Check: `test/mac-test-guard.test.js`, `scripts/check-test-guard.mjs`.
4. **Place every test file under a glob the runner reads** (`scripts/test-counts.mjs`). A file outside every glob is a green that never saw it. A file that runs elsewhere appears in `EXTERNAL` with the workflow that runs it. Check: `test/every-test-runs.test.js`.
5. **Run every test a file recorded.** Fewer tests than declared, or than the last recorded run, fails (`test/test-counts.json`). A crash that skips the rest looks green otherwise. Check: `test/test-counts.test.js` and the runner.
6. **Skip nothing.** Do not add `test.skip`, `test.todo`, `it.skip` or `skip: true`. A test of one platform's own behavior runs on that platform's CI. Tests listed in `test/must-run.json` fail on a skip when `VYRE_MUST_RUN=1`. A skip hides a red. Check: preflight T1, `scripts/test-counts.mjs`.
7. **Release with zero failing tests.** Keep no list of known failures. Fix a red; do not carry it. App type errors count wherever they appear (owner ruling of 10 Oct 2026). Check: preflight T2 and T3, the `node.yml` shards, `test-sweep.yml`.
8. **Delete a test, guard or piece of test machinery only with `[delete: why]`.** A merge once dropped the headless Chrome helper. Check: preflight D1.
9. **Run anything that talks to Claude Code or a network against the real thing once before merge.** A fake proves the logic, not the integration. Point fakes through `VYRE_CLAUDE_BIN`, `VYRE_SSH_BIN`, `VYRE_OPEN_BIN` and `VYRE_HANDS_BIN`. Check: review only.
10. **Run daemon tests on a test box or a runner, not on a Mac.** Preflight refuses to run tests on macOS and offers `--static` there. Check: preflight P1.
11. **Fix the cause when a guard fails. Do not loosen it.** A ratchet list shrinks only. Check: preflight R1 (`scripts/team/ratchets.mjs`), `test/ratchets.test.js`.
12. **Refresh the golden decisions in CI.** The `golden-refresh` workflow writes `kernel/golden/golden.json`, `allow.json` and `presence.json` when you push a commit containing `[golden-update]`. A test box gives other answers than CI. A widening needs a written ruling. Check: `kernel/golden/allow.test.js`, `kernel/golden/golden.test.js`, `test/golden-widenings.test.js`, `.github/workflows/golden-refresh.yml`.
13. **Give each seam a contract test.** Every file in `test/contracts` guards both sides of its seam. Check: preflight runs them all.
14. **Measure performance in CI.** Run `npm run perf-check`. Check: `.github/workflows/perf-idle.yml`, `runner-perf.yml`.

## Releases

1. **Sign each release with the key in the `release` environment** and check the pinned public key and the lineage. An update must come from us. Before approving the environment, read the diff of any signing file. Check: `test/release-key.test.js`, `test/sign-manifest.test.js`, `test/release-lineage.test.js`, `test/release-verify-workflow.test.js`, `test/release-workflow.test.js`, `.github/workflows/release.yml`, `release-verify.yml`.
2. **Build the release from what the tests proved.** Check the dist and the served files. The package holds what the box and the image need, including the golden dump. Check: `test/check-release-dist.test.js`, `test/check-served.test.js`, `test/package-files.test.js`, `test/golden-pack.test.js`, `test/box-pack-covers-dockerfile.test.js`.
3. **Use the bump script and the release counter.** Do not hand-edit versions. Check: `test/release-counter.test.js`, `test/bump-version.test.js`, `test/patch-release.test.js`.
4. **Freeze released migrations.** After a release, record them with `node scripts/record-migrations.mjs`. See Architecture rule 11. Check: `test/migrations-append-only.test.js`.
5. **Ship with zero failing tests** and list no known failures in the notes. See Tests and CI rule 7. Check: preflight T2 and T3.
6. **File every bug as a GitHub issue** in `vyre-ai/vyre`, with the `bug` label and a milestone. Send a security bug to a private advisory, not a public issue. One list shows what is broken, and nothing sensitive goes public. Check: review only.
7. **Write down what the release does not do** in `docs/known-gaps.md`, and keep the roadmap true. The README and docs must tell the truth. Check: `scripts/docs-check` for links and stale mentions, review only for the content.
8. **Prove the install on a real machine before calling a release ready:** a fresh machine, the real installer, the real onboarding. Green unit tests missed a failed first install. Check: `.github/workflows/install-proof.yml`, `packaged-boot.yml`, `matrix.yml`, `journeys.yml`.

## Git and commits

1. **Write conventional commits, one concern each:** `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, plus `ci:` and `security:` where history uses them. A readable history helps every later reader. Check: review only.
2. **Add a line to `CHANGELOG.md` in the same commit as any code change,** under `## Unreleased`, in plain sentences that say what changed and why. A new dependency says why it is worth having. The changelog is the release notes. Check: review only.
3. **Commit by path:** `git commit -m "..." -- <paths>`. Parallel sessions then never sweep up each other's files. A pathspec protects the index but not a shared append-only file such as `CHANGELOG.md`. Check: review only.
4. **Credit the repository owner only.** Leave out `Co-Authored-By: Claude`, "Generated with Claude" lines and session trailers in commits, merges, pull requests and release notes. Check: preflight G3.
5. **Do not use `git stash`, force-push, amend a pushed commit or pass `--no-verify`.** The stash stack is shared across worktrees, and the others rewrite history that other people rely on. Use a WIP commit or a second worktree. Check: review only.
6. **Leave generated files out of your commits.** The merge queue regenerates them. Leave no conflict markers. Check: preflight G1 and G2.
7. **Tag a ruling in the commit that needs it:** `[kernel +N: why]` for kernel growth, `[ratchet +N: why]` for a growing list, `[reviewed: trust]` for security paths, `[delete: why]` for removed test machinery, `[refactor]` for a behavior-free change, `[golden-update]` to refresh the golden decisions in CI. Check: preflight K1, R1, R2, D1, T5.
8. **Land work through the queue.** Run `node scripts/team/preflight.mjs` on a test box before you queue (only `--static` runs on a Mac). When you edit another team's paths (`scripts/team/owners.json`), talk to that team first. Check: preflight, `.github/workflows/merge-queue.yml`.
