AGENTS.md · git:20260905.4dea3f6 · 2026-09-05 · sha256 8bd2a6deb437f1ff
AGENTS.md git:20260905.4dea3f6A
Immutable. This exact content is served forever at /api/v1/blob/8bd2a6deb437f1ff.
# AGENTS.md The instruction file every coding agent working on this repository reads. It is deliberately short and deliberately duplicated nowhere: it carries the rules that are **enforced by CI**, plus a map to where everything else lives. Keep it small. Codex CLI embeds at most `project_doc_max_bytes` of this file into its first-turn instructions — **32 KiB by default**, silently truncating the rest — and every agent pays for it on every turn. Detail, rationale and worked examples belong in [`docs/AGENT_GUIDE.md`](docs/AGENT_GUIDE.md) and the rest of [`docs/`](docs/README.md), which agents read on demand. How each agent resolves its way here, and what that costs, is measured in [`docs/AGENT_ENTRYPOINTS.md`](docs/AGENT_ENTRYPOINTS.md). ## What this project is A Gradle plugin (`ee.schimke.composeai.preview`) plus supporting tools that discover `@Preview` composables in compiled Kotlin classes and render them to PNG outside Android Studio — Jetpack Compose (Android, via Robolectric) and Compose Multiplatform Desktop (via `ImageComposeScene`). Architecture, commands, state seams and the load-bearing constraints: [`docs/AGENT_GUIDE.md`](docs/AGENT_GUIDE.md). ## The CI-enforced invariants Break one of these and the pull request goes red. They are stated here, once, and cited — never restated — everywhere else. <!-- invariant: agent-attribution --> ### Git history must never attribute work to an AI agent Non-negotiable. Commits are attributed solely to the human committer. Two things are rejected: 1. a `Co-authored-by:` trailer, at trailer position, naming an agent (Claude / Codex / ChatGPT / Copilot / Gemini) or using an agent email; and 2. a commit whose **author or committer identity** is an agent — an `@anthropic.com` / `@openai.com` email, or an agent **name** set as the identity. GitHub App bot accounts (`…[bot]`, e.g. `claude[bot]`) are exempt from (2) but not from (1). Explicitly **fine**, and intentionally not matched: links to an agent session (`claude.ai/code`), the "🤖 Generated with …" and `_Generated by [Claude Code]_` footers, and ordinary PR comments — none of those claim authorship. **Scrub the PR description too, not just the commits.** The gate scans the PR title and body, so a `Co-authored-by:` line there fails CI. Enforced in three places sharing one detector ([`.github/scripts/agent-attribution-scan.sh`](.github/scripts/agent-attribution-scan.sh)) so they cannot disagree: the local [`commit-msg`](.githooks/commit-msg) hook, the local [`pre-push`](.githooks/pre-push) hook (which catches amended, rebased, cherry-picked or pre-hook commits `commit-msg` never saw), and the [`No Agent Attribution`](.github/workflows/no-agent-attribution.yml) CI gate. Install the hooks with [`scripts/install-git-hooks.sh`](scripts/install-git-hooks.sh). Claude Code's `SessionStart` hook runs it for you; **every other agent has to run it itself** — that hook lives in `.claude/settings.json` and never fires in a Codex, Gemini or Copilot session, so those clones have no local check at all until someone runs the script. `--no-verify` bypasses the local hooks; CI still won't. Why the PR body matters and how a trailer reached `main` anyway: [`docs/AGENT_GUIDE.md` → Git conventions](docs/AGENT_GUIDE.md#git-conventions). **Reviewers: run the detector before reporting an attribution finding.** This is the most-reported and least-real finding on this repo — 14 PRs drew a P1 "the author and committer are both `Codex`" comment over 2026-08-31/09-01 and not one was real. An agent reviewing its own or another agent's PR must not infer the commit identity from the fact that an agent wrote the code: the identity is whatever `git config user.email` held, and here that is the human. Check it, on the real range: ``` git log --format='%h %an <%ae> | %cn <%ce>' <base>..<head> .github/scripts/agent-attribution-scan.sh --range '<base>..<head>' ``` `Yuri Schimke <yuri@schimke.ee>` is this repository's human identity, and `github-actions[bot]` / `renovate[bot]` are exempt bot accounts (see above) — none of the three is ever a finding. Report only what the scanner exits 1 on, quote its output, and if the `Reject agent attribution` check is already green on the head commit there is nothing to report. <!-- invariant: branch-prefix --> ### Branch names are always `agent/…` Never `claude/…`, `copilot/…`, `codex/…`, `cursor/…`, or any other agent prefix. Don't ask which prefix to use — it is always `agent/`. If a session hands you a `claude/…` branch, rename it to `agent/…` before pushing. House style here, pinned in [`claude.yml`](.github/workflows/claude.yml) via `branch_prefix: agent/`, and hard-rejected by sibling repos' CI (meshcore-mobile). <!-- invariant: conventional-commits --> ### Conventional commits for PR titles and commit subjects `fix:`, `feat:`, `docs:`, `test:`, `ci:`, … PRs are squash-merged with `squash_merge_commit_message=BLANK`, so the squashed commit carries the **PR title only** — release-please reads that. The [`PR Title`](.github/workflows/pr-title.yml) workflow checks it. <!-- invariant: format-before-commit --> ### Run the formatter before committing CI's `format` job runs `./gradlew ktfmtCheckAll` and it is a hard gate — `ktfmtCheck` aborts on the first unformatted file. Before each commit touching `*.kt` / `*.kts`, run `./gradlew ktfmtFormat` (or `./gradlew :<module>:ktfmtFormatMain :<module>:ktfmtFormatTest` for the touched modules) and stage the result. For the serve-web TypeScript, run `npm --prefix cli/serve-web run format`. The round-trip through CI costs more than running the formatter locally. <!-- invariant: pr-state-recheck --> ### Re-check PR state immediately before **every** push Not just the first commit of a session — a PR can merge between turns. Right before pushing, `git fetch origin main` and confirm the branch head is not already in `origin/main` (or read the PR's `state` / `merged`). If the PR has landed, **stop**: do not stack commits onto the merged branch. Create a fresh branch from `origin/main` for the follow-up and say so. ## PR workflow The rules below are normative; the detail, the failure modes and the worked examples are in [`docs/AGENT_GUIDE.md` → PR workflow](docs/AGENT_GUIDE.md#pr-workflow). - **Open a PR automatically when a coding task is finished, committed and pushed.** Conventional-commit title, short summary + test plan. Check first whether the branch already has an open PR and push to it rather than opening a duplicate. Skip only when the user explicitly said "no PR" or framed the task as pure exploration. - **A UI-affecting PR must carry before/after visual evidence as embedded, viewable images** — markdown `` pointing at committed render PNGs (commit-pinned `raw.githubusercontent.com` URLs) or images uploaded into the body. Describing an image, or deferring to the visual-diff bot's auto-comment, is not evidence. If a surface genuinely cannot be captured, say so and embed a renderable proxy or state how a human/CI verifies it. Text-only is correct for non-visual changes. - **Write `` and leave the backticks alone if they appear.** They are injected between the agent and GitHub, not authored, and the [`PR Body Syntax`](.github/workflows/pr-body-syntax.yml) workflow repairs them in place. Do not hand-fix or re-post to "correct" the syntax. The three cases the repair does not cover are listed in `docs/AGENT_GUIDE.md`. - **Wire new visual surfaces into the preview workflow** so the next change to them is diffed without anyone remembering to do it. - **Don't auto-merge your own PR.** Opening, tracking and fix-up commits are automatic; pressing merge on a PR *you* opened is the user's call, and no agent approves or merges one. This is about agent-opened PRs. It is not a blanket ban on merge automation in the repository: Renovate's dependency PRs are automerged by configuration (`platformAutomerge` in [`.github/renovate.json`](.github/renovate.json)), which GitHub applies only once every required check of the `Protect Main` ruleset is green. Do not report that setting as a violation of this bullet. ## Where everything else lives | You need | Read | | --- | --- | | Which repository a module belongs in | [`docs/design/REPOSITORY_LAYERS.md`](docs/design/REPOSITORY_LAYERS.md) | | Architecture, commands, state seams, constraints | [`docs/AGENT_GUIDE.md`](docs/AGENT_GUIDE.md) | | The contributor doc index | [`docs/README.md`](docs/README.md) | | Releasing, versioning | [`docs/RELEASING.md`](docs/RELEASING.md), [`docs/VERSIONING.md`](docs/VERSIONING.md) | | Invoking an agent from an issue or PR | [`docs/AGENT_INVOCATION.md`](docs/AGENT_INVOCATION.md) | | Which agent reads which file, and what it costs | [`docs/AGENT_ENTRYPOINTS.md`](docs/AGENT_ENTRYPOINTS.md) | | Consumer docs for the published plugin and CLI | [`yschimke/skills`](https://github.com/yschimke/skills) | | The VS Code extension (split out; consumes this repo's published plugin) | [`yschimke/compose-preview-vscode`](https://github.com/yschimke/compose-preview-vscode) | Claude Code sessions additionally get on-demand procedures under [`.claude/skills/`](.claude/skills/) — `steward` (driving a PR to green), `render-evidence` (capturing before/after renders), `flake-triage` (proving a preview is unstable rather than regressed). They carry harness mechanics only and cite this file for the rules.