Skip to content

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

Where project conversion sits in a team rollout

Section titled “Where project conversion sits in a team rollout”

Conversion is the per-repository step of the adoption roadmap. It comes after you have measured a baseline and before you reshape delegation and review. Two neighbouring pages go deeper on parts of it:

  • 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

How does each tool load the converted context?

Section titled “How does each tool load the converted context?”

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.

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

Section titled “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

Section titled “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
import { existsSync, readFileSync } from 'node:fs';
const scripts = Object.keys(JSON.parse(readFileSync('package.json', 'utf8')).scripts ?? {});
const doc = readFileSync('AGENTS.md', 'utf8');
// Only commands inside `code spans` count, so prose such as "pnpm only." is ignored.
const named = [...doc.matchAll(/`(?:npm|pnpm|yarn|bun) (?:run )?([\w:-]+)[^`]*`/g)].map((m) => m[1]);
const builtins = new Set(['i', 'install', 'ci', 'add', 'remove', 'update', 'exec', 'dlx', 'x', 'create', 'init']);
const unique = [...new Set(named)];
// Skip flags such as `pnpm --filter api build` or `npm run -w web test`.
const missing = unique.filter((s) => !s.startsWith('-') && !builtins.has(s) && !scripts.includes(s));
// Script names say nothing about the package manager, so check the lockfile separately.
const wrongManager = existsSync('pnpm-lock.yaml')
? [...doc.matchAll(/`((?:npm|yarn) [^`]*)`/g)].map((m) => m[1])
: [];
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:

TicketToolRan the check command unpromptedEdited only the expected modulesCheck green at hand-offHuman corrections (before → after)
SHOP-412Claude Codeyesyesyes4 → 1
SHOP-388Codexyesno (pages/checkout/)yes3 → 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.
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Let Claude Code correct AGENTS.md in place
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
# 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?

Section titled “What breaks when you convert a project for agents?”

Minimum for an agent-ready project

  • A one-command check exists and is green on the main branch
  • AGENTS.md drafted from the repository, every command proven, TODO: confirm lines resolved
  • CLAUDE.md starts with @AGENTS.md; Cursor rules point at the core instead of copying it
  • Path-specific guidance lives in scoped rules, not in the root file
  • The probe prompt passes in every tool the team uses
  • scripts/check-agents-commands.mjs runs in CI on every pull request
  • Highest-risk untested paths have characterization tests before any refactor
  • The pilot scorecard shows fewer corrections on at least two of three baseline tickets
  • The refresh workflow opens a pull request and never pushes to the main branch

Where to go next after converting a project

Section titled “Where to go next after converting a project”