Project conversion is the procedure that makes an existing repository usable by coding agents: one AGENTS.md drafted from the real code and corrected by a human, thin per-tool adapters for Claude Code and Cursor, a CI check that keeps its commands true, and a before/after pilot on real tickets that proves agents now follow the project’s conventions.
You point an agent at a three-year-old Next.js repository and ask for a feature. It adds a state library you removed last quarter, puts the route handler in the wrong directory, and runs npm test in a repository that moved to pnpm a year ago. The code is not bad. The agent has no idea how your project works, and a better prompt will not fix that. This playbook is for the tech lead who owns that repository and has to make it work for eight people and three tools.
A canonical AGENTS.md generated from the repository, with every command proven to run
Thin adapters so Claude Code, Codex, and Cursor all load the same core instead of three drifting copies
Four copy-paste prompts: draft AGENTS.md from the repository, probe what a session loaded, audit a legacy codebase, and turn the audit into a phased plan
A deterministic CI check that fails when AGENTS.md names a command that no longer exists
A pilot scorecard that shows, on three closed tickets, whether the conversion changed agent behaviour
Making a codebase agent-ready scores the repository properties (one-command checks, deterministic tests, enforced boundaries) that let checks replace reading diffs. Run that audit in step 1 below.
Shared agent rules is the full reference for how each tool discovers instruction files and how to probe that every agent loaded them. This page uses its adapter pattern without repeating it.
The order matters. Context files written before the checks exist tell the agent to run commands that do not work, and a pilot run before the context files land measures nothing.
Baseline before you change anything. Pick three closed tickets that touched different parts of the codebase. For each, record what a fresh agent session does with the original ticket text today: which command it runs to check its work, which directories it edits, and how many corrections you had to give. Also run the agent-readiness audit and note the one-command check, or its absence.
Make one check command exist. If pnpm check (or make check, or ./gradlew check) does not already run lint, types, and tests in one step, add it first. Every later instruction says “done means this command passes”, so it has to exist and be green on the main branch.
Draft AGENTS.md from the repository, then correct it. Use the prompt below in whichever tool you have open. Review the draft against reality, answer or delete each TODO: confirm line, and commit it in its own pull request so reviewers can check the conventions it asserts.
Add the adapters and scoped rules. Wire each tool to the same core (next section). Put path-specific guidance, such as “API handlers validate with the Zod schemas in lib/schemas/”, into scoped rules that load only for those paths, not into the root file.
Re-run the baseline tickets and sign off. Run the same three tickets in fresh sessions and fill in the pilot scorecard (see how to prove the conversion worked). The tech lead signs off the conversion when the scorecard improves, not when the files exist.
The last instruction matters most. A draft that names npm test in a pnpm repository reads fine and fails on first use, so make the agent prove each command before you review the prose.
Here is the shape of a good result for a Next.js application: concrete, short, and checkable.
# Acme Storefront
Next.js App Router application. React Server Components by default. pnpm only.
## Commands
- Check (lint + types + tests): `pnpm check`. A task is done only when this passes.
- Dev server: `pnpm dev` · Unit tests: `pnpm test` · Migrations: `pnpm db:migrate`
## Conventions
- Server Components by default; add `'use client'` only for interactivity.
- Route handlers live in `app/api/<resource>/route.ts`, one folder per resource.
- Data access goes through `lib/db/`; never import the Prisma client in a component.
- Money is integer cents everywhere. See `lib/money.ts`.
## Boundaries (ask first)
-`prisma/migrations/`, `infra/`, anything under `generated/`
## Traps
-`pages/checkout/` is being migrated to the App Router. Do not extend it.
The core is identical for all three tools. The adapters differ because each tool discovers instruction files differently. The full discovery rules and probes are on shared agent rules; this is the conversion-day minimum.
Add a CLAUDE.md next to each AGENTS.md whose first line imports it. Claude-specific lines go below the import.
@AGENTS.md
## Claude Code only
- Use plan mode for changes under `services/billing/`.
The @AGENTS.md import works on every release channel and provider. Claude Code also reads AGENTS.md (or .claude/AGENTS.md) directly when there is no CLAUDE.md, but only from v2.1.277 (v2.1.281 on Bedrock, Google Cloud, Microsoft Foundry, LLM gateways, and telemetry-off sessions). Both are on the latest and stable channels since stable reached v2.1.287 (checked 2026-10-12). Two traps follow from the lookup rule:
Any CLAUDE.md, .claude/CLAUDE.md, or CLAUDE.local.md in the working directory or above switches direct reading off, so a CLAUDE.md without the import hides AGENTS.md completely. An engineer’s private CLAUDE.local.md does the same for that engineer. That is the default: to keep a private CLAUDE.local.md and still load AGENTS.md, set Project instructions to claude-md-and-agents-md in /config (v2.1.277+).
A CLAUDE.md that only says “read AGENTS.md” in words does not work reliably: Claude sees the file only if it decides to open it.
For path-scoped guidance, use .claude/rules/*.md with a paths list in the frontmatter. These rules load when Claude reads a matching file, not at launch:
---
paths:
- "app/api/**/*.ts"
---
- Validate request bodies with the Zod schemas in `lib/schemas/`.
- Wrap handlers in `withErrorHandler` from `lib/api/errors.ts`.
Subdirectory CLAUDE.md files also load on demand, when Claude reads files in that directory. Confirm what loaded with /context in a fresh session: the files appear under Memory files.
Codex reads AGENTS.md natively, so the core needs no adapter. Two behaviours change how you lay out a monorepo (checked against the agents_md.rs and config_toml.rs sources and Codex CLI 0.157.1 on 2026-09-26):
Codex collects AGENTS.md files from the project root down to the directory where the session starts, and concatenates them root first. A packages/api/AGENTS.md loads only when Codex starts in packages/api/ or below, for example with codex -C packages/api. It does not load because the agent later edits a file there.
project_doc_max_bytes caps the whole chain at 32 KiB by default. Past the cap, the deepest files are cut first.
Check what Codex will see without running a task:
Terminal window
# Terminal, from the directory where engineers start Codex
codexdebugprompt-input"noop"|grep-c"pnpm check"
If your repository already has a canonical CLAUDE.md that cannot move yet, project_doc_fallback_filenames = ["CLAUDE.md"] in config.toml makes Codex read it in directories that have no AGENTS.md. Treat that as a bridge, not the end state. Since Codex 0.150.0, untrusted projects do not supply project-level AGENTS.md, so trust the project first.
Cursor applies project instructions through Rules (cursor.com/docs/rules, checked 2026-10-12). Project rules are .mdc files in .cursor/rules (a plain .md file there is ignored), and Cursor also supports AGENTS.md in the project root and in subdirectories. This page does not reproduce the rule file syntax; check the Rules page before you write rule files. Whatever format you use, the probe prompt below is the acceptance test: a Cursor adapter is done when a fresh Agent chat answers all four questions correctly, whatever the documentation says.
If a fresh Agent chat on your Cursor 3.x build answers the probe prompt correctly, add nothing.
Otherwise, add one always-applied project rule that points the agent at AGENTS.md and holds only Cursor-specific lines. Re-run the probe.
Put path-specific guidance in rules scoped to those paths, the same content as the Claude Code .claude/rules/ files.
If the repository already has Cursor project rules, move their durable content into AGENTS.md and shrink the rules to pointers and path-scoped lines. Do not paste the core into a rule: a copy is how a stale npm test survives a move to pnpm.
Run the probe in a fresh session of each tool, from the directory where engineers usually start. Four correct answers mean the tool loaded the core. “No instruction” on any line means the adapter is missing or the file is not on that tool’s discovery path.
Converting a legacy codebase with thin documentation
An older project needs two more passes before step 3: a map of what really exists, and a plan that puts tests before refactors. Drive both with the agent, then check the output against the code.
Land Phase 1 first so every later task benefits from it. Phase 2 is the one teams are tempted to skip: without characterization tests, a refactor in Phase 3 has no oracle and you are back to reading every diff. Once Phase 2 is in, fan the independent Phase 3 tasks out across Codex cloud tasks, Claude Code sessions in separate worktrees or on the web (claude.ai/code), or Cursor Cloud Agents. For a multi-quarter migration, run it as a program instead: see legacy modernization with agents.
Prove the conversion worked without reading every diff
A converted repository is one where agents behave differently, not one where files exist. Verify at two levels.
Deterministic checks on every pull request. These need no model, so no prompt can argue them into passing. The first fails when AGENTS.md names a package script that package.json does not define. It matches only commands inside code spans, so prose such as “pnpm only.” cannot fail CI, and it skips flags such as --filter or -w that follow the package manager. A script name says nothing about the package manager, though: npm test passes the name check whenever a test script exists. So the last part fails separately when pnpm-lock.yaml exists and a code span starts with npm or yarn. It was tested with Node.js 22 on 2026-09-26 against the sample AGENTS.md above, with a wrong script name and with npm test in a pnpm repository.
// scripts/check-agents-commands.mjs: run from the repository root in CI
if (missing.length) console.error(`AGENTS.md names scripts that package.json does not define: ${missing.join(', ')}`);
if (wrongManager.length) console.error(`pnpm-lock.yaml exists, but AGENTS.md says: ${wrongManager.join(', ')}`);
if (missing.length|| wrongManager.length) process.exit(1);
console.log(`AGENTS.md commands OK (${unique.length} checked)`);
Add it to the same CI job that runs your check command, next to the adapter and size check from shared agent rules. On the pull_request trigger GitHub runs the workflow file and this script from the pull request itself, so a pull request can weaken the check in the same diff as the AGENTS.md change; put /.github/, scripts/ and AGENTS.md under CODEOWNERS and require code-owner review before you count it as a gate.
A pilot scorecard on the baseline tickets. Re-run the three tickets from step 1 in fresh sessions and fill in one row per ticket per tool. The two rows below are an illustration of the format, not measured results:
Ticket
Tool
Ran the check command unprompted
Edited only the expected modules
Check green at hand-off
Human corrections (before → after)
SHOP-412
Claude Code
yes
yes
yes
4 → 1
SHOP-388
Codex
yes
no (pages/checkout/)
yes
3 → 2
Our recommended sign-off rule for the tech lead: every tool answers the probe prompt correctly, the deterministic checks are green, and the corrections column goes down on at least two of the three tickets. Agent runs vary, so one run per ticket can move the count on its own: run each ticket three times per tool and compare the medians. Three tickets is a smoke test, not a measurement. A row that does not improve points at a missing convention; add it to AGENTS.md and re-run that ticket only.
A stale context file tells the agent to do the wrong thing with full confidence, such as running a removed script. The deterministic check above catches broken commands on every pull request. For slower drift, such as renamed directories or a replaced library, run a scheduled refresh that opens a pull request for a human to approve.
The workflow splits the job in two. The agent job holds the API key but only read access to the repository. The pull-request job holds write access but never runs the agent. The if: guard on the agent job means it runs only on the default branch, whether started by the schedule or by hand, so it never executes contributor code, hooks, or MCP configuration with the secret in the environment. --strict-mcp-config also ignores MCP servers declared in the repository.
.github/workflows/refresh-agent-context.yml
name: Refresh agent context
on:
schedule:
- cron: '0 6 * * 1'# Mondays 06:00 UTC
workflow_dispatch:
permissions: {}
jobs:
draft:
# A manual run from another branch would load that branch's hooks and MCP config with the key set.
# Pinned; bump deliberately after reading the changelog.
npm install -g @anthropic-ai/claude-code@2.1.283
claude -p "Compare AGENTS.md with the current repository. Edit AGENTS.md only where \
a command, version, path or convention no longer matches the code. Change nothing else." \
--permission-mode acceptEdits \
--allowedTools "Read,Glob,Grep,Edit" \
--disallowedTools "Bash" \
--permission-prompts none \
--strict-mcp-config \
--max-budget-usd 2
git diff -- AGENTS.md > context.patch
- uses: actions/upload-artifact@v7
with:
name: context-patch
path: context.patch
open-pr:
needs: draft
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
name: context-patch
- name: Apply the proposed change
run: |
if [ -s context.patch ]; then git apply context.patch; fi
rm context.patch
- uses: peter-evans/create-pull-request@v8
with:
commit-message: 'docs: refresh AGENTS.md from the codebase'
title: 'docs: refresh agent context'
branch: chore/refresh-agent-context
--allowedTools pre-approves reading and editing, --disallowedTools "Bash" removes the shell, --permission-prompts none denies anything else that would ask for permission, and --max-budget-usd caps the spend per run. Every flag in this step, including --permission-mode and --strict-mcp-config, is in claude --help for v2.1.283. To run the draft job with Codex instead, replace the Claude Code step with openai/codex-action@v1, passing openai-api-key: ${{ secrets.OPENAI_API_KEY }}, permission-profile: ":workspace" and the same prompt. :read-only cannot edit files, so :workspace is required, but unlike the Claude Code step it also lets Codex run shell commands, so the two jobs are not equal in privilege. The patch step still limits the output to AGENTS.md, because only git diff -- AGENTS.md reaches the pull-request job. For more headless patterns, see headless agents in CI.
What breaks when you convert a project for agents?