Files
next.js/packages/next-codemod/bin/next-codemod.ts
Jude GaoandGitHub 1e859c06aa feat(next-codemod): add agents-md command for AI coding agents (#88961)
## Summary

Adds a new `agents-md` subcommand to `@next/codemod` that generates a
Next.js documentation index for AI coding agents (Claude, Cursor, GitHub
Copilot, etc.).

## Usage

```bash
# Interactive mode - prompts for version and target file
npx @next/codemod agents-md

# Non-interactive mode
npx @next/codemod agents-md --version 15.1.0 --output CLAUDE.md

# Auto-detect version, specify output
npx @next/codemod agents-md --output AGENTS.md
```

## What it does

1. Downloads the Next.js documentation matching your project's version
to `.next-docs/` (via git sparse-checkout)
2. Generates a compact index of all doc files grouped by directory
3. Injects the index into `CLAUDE.md` or `AGENTS.md` between marker
comments
4. Adds `.next-docs/` to `.gitignore`

The index helps AI agents prefer local documentation over pre-trained
knowledge, ensuring answers match your Next.js version.

## Options

| Option | Description |
|--------|-------------|
| `--version <version>` | Specify Next.js version (auto-detected from
`package.json` if not provided) |
| `--output <file>` | Target file path (e.g., `CLAUDE.md`, `AGENTS.md`)
|

## Output format

The generated index is a single line with pipe separators:

```
<!-- NEXT-AGENTS-MD-START -->[Next.js Docs Index]|root: ./.next-docs|IMPORTANT: Prefer retrieval-led reasoning...|dir:{file1.mdx,file2.mdx}|...<!-- NEXT-AGENTS-MD-END -->
```

## Test plan

- [x] Build passes
- [x] Tested interactive mode
- [x] Tested non-interactive mode with `--version` and `--output`
- [x] Tested auto-detect version with `--output` only
- [x] Verified `.gitignore` is updated
- [x] Verified index is injected correctly (new file and existing file)

## Eval results

Tested against 19 Next.js-specific agent evals with 100% pass rate:

| Eval | Result |
|------|--------|
| agent-000-app-router-migration-simple | ✅✅✅ (89.5s) |
| agent-021-avoid-fetch-in-effect | ✅✅✅ (90.4s) |
| agent-022-prefer-server-actions | ✅✅✅ (67.1s) |
| agent-023-avoid-getserversideprops | ✅✅✅ (102.5s) |
| agent-024-avoid-redundant-usestate | ✅✅✅ (62.9s) |
| agent-025-prefer-next-link | ✅✅✅ (69.5s) |
| agent-026-no-serial-await | ✅✅✅ (96.2s) |
| agent-027-prefer-next-image | ✅✅✅ (68.0s) |
| agent-028-prefer-next-font | ✅✅✅ (64.2s) |
| agent-029-use-cache-directive | ✅✅✅ (76.6s) |
| agent-030-app-router-migration-hard | ✅✅✅ (220.8s) |
| agent-031-proxy-middleware | ✅✅✅ (92.4s) |
| agent-032-use-cache-directive | ✅✅✅ (71.3s) |
| agent-033-forbidden-auth | ✅✅✅ (100.6s) |
| agent-034-async-cookies | ✅✅✅ (68.7s) |
| agent-035-connection-dynamic | ✅✅✅ (65.2s) |
| agent-036-after-response | ✅✅✅ (68.7s) |
| agent-037-updatetag-cache | ✅✅✅ (70.3s) |
| agent-038-refresh-settings | ✅✅✅ (71.8s) |
| **Overall** | **19/19/19 (100%, 100%, 100%)** |

Legend: ✅✅✅ = Build/Lint/Test
2026-01-23 21:31:32 -05:00

105 lines
3.4 KiB
JavaScript

#!/usr/bin/env node
/**
* Copyright 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
// Based on https://github.com/reactjs/react-codemod/blob/dd8671c9a470a2c342b221ec903c574cf31e9f57/bin/cli.js
// @next/codemod optional-name-of-transform optional/path/to/src [...options]
import { Command } from 'commander'
import { runUpgrade } from './upgrade'
import { runAgentsMd } from './agents-md'
import { runTransform } from './transform'
import { BadInput } from './shared'
const packageJson = require('../package.json')
const program = new Command(packageJson.name)
.description('Codemods for updating Next.js apps.')
.version(
packageJson.version,
'-v, --version',
'Output the current version of @next/codemod.'
)
.argument(
'[codemod]',
'Codemod slug to run. See "https://github.com/vercel/next.js/tree/canary/packages/next-codemod".'
)
.argument(
'[source]',
'Path to source files or directory to transform including glob patterns.'
)
.usage('[codemod] [source] [options]')
.helpOption('-h, --help', 'Display this help message.')
.option('-f, --force', 'Bypass Git safety checks and forcibly run codemods')
.option('-d, --dry', 'Dry run (no changes are made to files)')
.option(
'-p, --print',
'Print transformed files to stdout, useful for development'
)
.option('--verbose', 'Show more information about the transform process')
.option(
'-j, --jscodeshift',
'(Advanced) Pass options directly to jscodeshift'
)
.action(runTransform)
.allowUnknownOption()
// This is needed for options for subcommands to be passed correctly.
// Because by default the options are not positional, which will pass options
// to the main command "@next/codemod" even if it was passed after subcommands,
// e.g. "@next/codemod upgrade --verbose" will be treated as "next-codemod --verbose upgrade"
// By enabling this, it will respect the position of the options and pass it to subcommands.
// x-ref: https://github.com/tj/commander.js/pull/1427
.enablePositionalOptions()
program
.command('upgrade')
.description(
'Upgrade Next.js apps to desired versions with a single command.'
)
.argument(
'[revision]',
'Specify the upgrade type ("patch", "minor", "major"), an NPM dist tag (e.g. "latest", "canary", "rc"), or an exact version (e.g. "15.0.0"). Defaults to "minor".'
)
.usage('[revision] [options]')
.option('--verbose', 'Verbose output', false)
.action(async (revision, options) => {
try {
await runUpgrade(revision, options)
} catch (error) {
if (!options.verbose && error instanceof BadInput) {
console.error(error.message)
} else {
console.error(error)
}
process.exit(1)
}
})
program
.command('agents-md')
.description(
'Generate Next.js documentation index for AI coding agents (Claude, Cursor, etc.).'
)
.option(
'--version <version>',
'Next.js version (auto-detected if not provided)'
)
.option('--output <file>', 'Target file path (e.g., CLAUDE.md, AGENTS.md)')
.action(async (options) => {
try {
await runAgentsMd(options)
} catch (error) {
if (error instanceof BadInput) {
console.error(error.message)
} else {
console.error(error)
}
process.exit(1)
}
})
program.parse(process.argv)