Skip to content

Commit a57d34b

Browse files
committed
feat(create): add agents-md codemod for existing projects
New projects get AGENTS.md/CLAUDE.md from the create template, but existing projects that merely upgrade @modern-js/app-tools got the bundled docs in node_modules with nothing pointing agents at them. Add a `create agents-md` subcommand that brings an existing project up to date, idempotently: - AGENTS.md: create it if missing; if a modernjs-agent-rules marker block is present, refresh only that block in place (fixes stale paths on upgrade) while leaving the user's content outside the markers untouched; if there are no markers, append the block. - CLAUDE.md: create it as `@AGENTS.md` if missing, or add the import to an existing file (Claude Code reads CLAUDE.md, not AGENTS.md). The managed block is read from the create template, so the codemod and scaffolding share one source of truth. Adds 5 integration tests and documents the flow for existing projects in the ai-coding-agents guide. Co-Authored-By: Riff
1 parent fcb7f5a commit a57d34b

8 files changed

Lines changed: 203 additions & 4 deletions

File tree

.changeset/agent-knowledge-supply.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
'@modern-js/create': minor
44
---
55

6-
feat: agent knowledge supply — bundle version-matched English docs into the app-tools tarball (`docs/`) on publish, and generate `AGENTS.md` / `CLAUDE.md` in new projects created by `@modern-js/create` (skip with `--no-agents-md`); also fixes boolean flags swallowing the following positional argument (e.g. `create --sub my-app`)
6+
feat: agent knowledge supply — bundle version-matched English docs into the app-tools tarball (`docs/`) on publish, and generate `AGENTS.md` / `CLAUDE.md` in new projects created by `@modern-js/create` (skip with `--no-agents-md`). Existing projects can run `npx @modern-js/create agents-md` to add or idempotently refresh these files after an upgrade (managed marker block is updated in place, user content is preserved). Also fixes boolean flags swallowing the following positional argument (e.g. `create --sub my-app`).
77

8-
feat: Agent 知识供给 —— 发布时将版本匹配的英文文档打进 app-tools tarball(`docs/`),并在 `@modern-js/create` 新建项目时默认生成 `AGENTS.md` / `CLAUDE.md``--no-agents-md` 可跳过)同时修复布尔参数吞掉后续位置参数的问题(如 `create --sub my-app`
8+
feat: Agent 知识供给 —— 发布时将版本匹配的英文文档打进 app-tools tarball(`docs/`),并在 `@modern-js/create` 新建项目时默认生成 `AGENTS.md` / `CLAUDE.md``--no-agents-md` 可跳过)。已有项目可运行 `npx @modern-js/create agents-md` 在升级后补齐或幂等更新这两个文件(就地更新托管标记块,保留用户自定义内容)。同时修复布尔参数吞掉后续位置参数的问题(如 `create --sub my-app`

packages/document/docs/en/guides/get-started/ai-coding-agents.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Every new Modern.js project is agent-ready out of the box:
2525

2626
The Modern.js-managed part of `AGENTS.md` is wrapped in `<!-- BEGIN:modernjs-agent-rules -->` / `<!-- END:modernjs-agent-rules -->` markers. Anything you write outside the markers is yours and will never be touched by Modern.js tooling.
2727

28+
**Existing projects**: after you upgrade `@modern-js/app-tools`, the bundled docs show up in `node_modules` automatically; run `npx @modern-js/create agents-md` at the project root to add or refresh `AGENTS.md` / `CLAUDE.md`. The command is idempotent — an existing managed block is updated in place, your own content outside the markers is left untouched, an `AGENTS.md` without the block gets it appended, and a missing `CLAUDE.md` gets the `@AGENTS.md` import.
29+
2830
## Skills
2931

3032
Skills are on-demand AI capabilities following the [Agent Skills open standard](https://github.com/vercel-labs/skills). User-facing Skills:

packages/document/docs/zh/guides/get-started/ai-coding-agents.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Modern.js 文档遵循 [llms.txt 规范](https://llmstxt.org/),由 [`@rspress/
2525

2626
`AGENTS.md` 中由 Modern.js 管理的部分包裹在 `<!-- BEGIN:modernjs-agent-rules -->` / `<!-- END:modernjs-agent-rules -->` 标记内。标记之外的内容完全归你所有,Modern.js 工具永远不会改动。
2727

28+
**已有项目**:升级 `@modern-js/app-tools` 后随包文档会自动出现在 `node_modules` 中;在项目根运行 `npx @modern-js/create agents-md` 即可补齐或更新 `AGENTS.md` / `CLAUDE.md`。该命令是幂等的——已存在的托管标记块会被就地更新,标记之外的自定义内容不受影响;没有标记块的 `AGENTS.md` 会被追加托管块,缺失的 `CLAUDE.md` 会补上 `@AGENTS.md` 引用。
29+
2830
## Skills
2931

3032
Skills 是按需触发的 AI 辅助能力,遵循 [Agent Skills 开放标准](https://github.com/vercel-labs/skills)。用户向 Skills:
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import { i18n, localeKeys } from './locale';
4+
5+
// Codemod for existing projects: add or refresh AGENTS.md / CLAUDE.md so AI
6+
// coding agents are pointed at the version-matched docs bundled in
7+
// node_modules/@modern-js/app-tools/docs/. `@modern-js/create` only scaffolds
8+
// these for new projects; this brings existing projects up to date on upgrade.
9+
//
10+
// The Modern.js-managed rules live between these markers. Everything outside
11+
// them belongs to the user and is never touched, and the block itself is
12+
// replaced in place on re-run, so this command is idempotent.
13+
const BEGIN = '<!-- BEGIN:modernjs-agent-rules -->';
14+
const END = '<!-- END:modernjs-agent-rules -->';
15+
const CLAUDE_IMPORT = '@AGENTS.md';
16+
17+
// Read the managed block from the create template, so the codemod and the
18+
// scaffolding share a single source of truth.
19+
function readManagedBlock(templateDir: string): string {
20+
const tpl = fs.readFileSync(path.join(templateDir, 'AGENTS.md'), 'utf-8');
21+
const begin = tpl.indexOf(BEGIN);
22+
const end = tpl.indexOf(END);
23+
if (begin === -1 || end === -1 || end < begin) {
24+
throw new Error(
25+
'template/AGENTS.md is missing the modernjs-agent-rules markers',
26+
);
27+
}
28+
return tpl.slice(begin, end + END.length);
29+
}
30+
31+
function report(key: string, file: string): void {
32+
console.log(i18n.t(key, { file }));
33+
}
34+
35+
// Create AGENTS.md, refresh the managed block if present, or append it while
36+
// preserving the user's own content.
37+
function applyAgentsMd(targetDir: string, block: string): void {
38+
const file = path.join(targetDir, 'AGENTS.md');
39+
if (!fs.existsSync(file)) {
40+
fs.writeFileSync(file, `${block}\n`, 'utf-8');
41+
report(localeKeys.agentsCmd.created, 'AGENTS.md');
42+
return;
43+
}
44+
45+
const content = fs.readFileSync(file, 'utf-8');
46+
const begin = content.indexOf(BEGIN);
47+
const end = content.indexOf(END);
48+
if (begin !== -1 && end !== -1 && end > begin) {
49+
const next =
50+
content.slice(0, begin) + block + content.slice(end + END.length);
51+
if (next === content) {
52+
report(localeKeys.agentsCmd.unchanged, 'AGENTS.md');
53+
} else {
54+
fs.writeFileSync(file, next, 'utf-8');
55+
report(localeKeys.agentsCmd.updatedBlock, 'AGENTS.md');
56+
}
57+
return;
58+
}
59+
60+
// No managed block yet: append ours, keeping the existing content intact.
61+
const base = content.replace(/\s*$/, '');
62+
fs.writeFileSync(file, `${base}\n\n${block}\n`, 'utf-8');
63+
report(localeKeys.agentsCmd.appendedBlock, 'AGENTS.md');
64+
}
65+
66+
// Create CLAUDE.md as an @AGENTS.md import, or add the import to an existing
67+
// one (Claude Code reads CLAUDE.md, not AGENTS.md, so the bridge is required).
68+
function applyClaudeMd(targetDir: string): void {
69+
const file = path.join(targetDir, 'CLAUDE.md');
70+
if (!fs.existsSync(file)) {
71+
fs.writeFileSync(file, `${CLAUDE_IMPORT}\n`, 'utf-8');
72+
report(localeKeys.agentsCmd.created, 'CLAUDE.md');
73+
return;
74+
}
75+
76+
const content = fs.readFileSync(file, 'utf-8');
77+
if (content.split('\n').some(line => line.trim() === CLAUDE_IMPORT)) {
78+
report(localeKeys.agentsCmd.unchanged, 'CLAUDE.md');
79+
return;
80+
}
81+
fs.writeFileSync(
82+
file,
83+
`${CLAUDE_IMPORT}\n\n${content.replace(/^\s*/, '')}`,
84+
'utf-8',
85+
);
86+
report(localeKeys.agentsCmd.linked, 'CLAUDE.md');
87+
}
88+
89+
export function runAgentsMd(templateDir: string, targetDir: string): void {
90+
if (!fs.existsSync(targetDir)) {
91+
console.error(
92+
i18n.t(localeKeys.agentsCmd.targetNotFound, { dir: targetDir }),
93+
);
94+
process.exit(1);
95+
}
96+
const block = readManagedBlock(templateDir);
97+
applyAgentsMd(targetDir, block);
98+
applyClaudeMd(targetDir);
99+
console.log('');
100+
console.log(i18n.t(localeKeys.agentsCmd.done));
101+
}

packages/toolkit/create/src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import path from 'node:path';
33
import readline from 'node:readline';
44
import { fileURLToPath } from 'node:url';
55
import { getLocaleLanguage } from '@modern-js/i18n-utils/language-detector';
6+
import { runAgentsMd } from './agents-md';
67
import { i18n, localeKeys } from './locale';
78

89
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -186,6 +187,17 @@ async function main() {
186187
return;
187188
}
188189

190+
// Subcommand for existing projects: add/refresh AGENTS.md & CLAUDE.md so
191+
// agents pick up the bundled docs after an upgrade (idempotent).
192+
if (args[0] === 'agents-md') {
193+
const dirArg = args.slice(1).find(arg => !arg.startsWith('-'));
194+
const targetDir = dirArg
195+
? path.resolve(process.cwd(), dirArg)
196+
: process.cwd();
197+
runAgentsMd(templateDir, targetDir);
198+
return;
199+
}
200+
189201
console.log(`\n${i18n.t(localeKeys.message.welcome)}\n`);
190202
const { name: projectName, useCurrentDir } = await getProjectName();
191203
const targetDir = useCurrentDir

packages/toolkit/create/src/locale/en.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ export const EN_LOCALE = {
1818
step2: 'pnpm install',
1919
step3: 'pnpm dev',
2020
},
21+
agentsCmd: {
22+
created: '✔ Created {file}',
23+
updatedBlock: '✔ Updated the modernjs-agent-rules block in {file}',
24+
appendedBlock: '✔ Added the modernjs-agent-rules block to {file}',
25+
linked: '✔ Added the `@AGENTS.md` import to {file}',
26+
unchanged: '• {file} is already up to date',
27+
done: '✨ Done — AI coding agents will read the bundled docs in node_modules/@modern-js/app-tools/docs/.',
28+
targetNotFound: 'Error: target directory "{dir}" does not exist',
29+
},
2130
help: {
2231
title: '🚀 Modern.js Project Creator',
2332
description: 'Create a new Modern.js project with ease',
@@ -34,7 +43,8 @@ export const EN_LOCALE = {
3443
example1: ' create my-app',
3544
example2: ' create my-app --lang zh',
3645
example3: ' create my-app --sub',
37-
example4: ' create --help',
46+
example4:
47+
' create agents-md (add/refresh AGENTS.md & CLAUDE.md in an existing project)',
3848
moreInfo: '📚 Learn more: https://modernjs.dev',
3949
},
4050
version: {

packages/toolkit/create/src/locale/zh.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ export const ZH_LOCALE = {
1717
step2: 'pnpm install',
1818
step3: 'pnpm dev',
1919
},
20+
agentsCmd: {
21+
created: '✔ 已创建 {file}',
22+
updatedBlock: '✔ 已更新 {file} 中的 modernjs-agent-rules 块',
23+
appendedBlock: '✔ 已向 {file} 追加 modernjs-agent-rules 块',
24+
linked: '✔ 已向 {file} 添加 `@AGENTS.md` 引用',
25+
unchanged: '• {file} 已是最新',
26+
done: '✨ 完成 —— AI 编码助手会读取 node_modules/@modern-js/app-tools/docs/ 里的随包文档。',
27+
targetNotFound: '错误: 目标目录 "{dir}" 不存在',
28+
},
2029
help: {
2130
title: '🚀 Modern.js 项目创建工具',
2231
description: '快速创建一个新的 Modern.js 项目',
@@ -33,7 +42,8 @@ export const ZH_LOCALE = {
3342
example1: ' create my-app',
3443
example2: ' create my-app --lang zh',
3544
example3: ' create my-app --sub',
36-
example4: ' create --help',
45+
example4:
46+
' create agents-md (为已有项目补齐/更新 AGENTS.md 和 CLAUDE.md)',
3747
moreInfo: '📚 更多信息: https://modernjs.dev',
3848
},
3949
version: {

packages/toolkit/create/tests/create.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,65 @@ describe('positional argument parsing', () => {
8787
expect(fs.existsSync(path.join(workdir, 'zh'))).toBe(false);
8888
});
8989
});
90+
91+
describe('agents-md codemod (existing projects)', () => {
92+
const BEGIN = '<!-- BEGIN:modernjs-agent-rules -->';
93+
const END = '<!-- END:modernjs-agent-rules -->';
94+
const read = (name: string) =>
95+
fs.readFileSync(path.join(workdir, name), 'utf-8');
96+
97+
it('creates AGENTS.md and CLAUDE.md when neither exists', () => {
98+
runCreate(['agents-md']);
99+
100+
const agents = read('AGENTS.md');
101+
expect(agents).toContain(BEGIN);
102+
expect(agents).toContain(END);
103+
expect(agents).toContain('node_modules/@modern-js/app-tools/docs/');
104+
expect(read('CLAUDE.md').trim()).toBe('@AGENTS.md');
105+
});
106+
107+
it('refreshes the managed block in place and keeps user content', () => {
108+
fs.writeFileSync(
109+
path.join(workdir, 'AGENTS.md'),
110+
`${BEGIN}\nOLD: node_modules/@modern-js/app-tools/main-doc/docs/en/\n${END}\n\n# My custom rules\nkeep me\n`,
111+
);
112+
runCreate(['agents-md']);
113+
114+
const agents = read('AGENTS.md');
115+
// stale path replaced with the current one, user content preserved
116+
expect(agents).not.toContain('main-doc/docs/en');
117+
expect(agents).toContain('node_modules/@modern-js/app-tools/docs/');
118+
expect(agents).toContain('# My custom rules');
119+
expect(agents).toContain('keep me');
120+
});
121+
122+
it('appends the managed block when AGENTS.md has no markers', () => {
123+
fs.writeFileSync(path.join(workdir, 'AGENTS.md'), '# My rules\ndo X\n');
124+
runCreate(['agents-md']);
125+
126+
const agents = read('AGENTS.md');
127+
expect(agents).toContain('do X');
128+
expect(agents).toContain(BEGIN);
129+
expect(agents).toContain('node_modules/@modern-js/app-tools/docs/');
130+
});
131+
132+
it('adds the @AGENTS.md import to an existing CLAUDE.md', () => {
133+
fs.writeFileSync(
134+
path.join(workdir, 'CLAUDE.md'),
135+
'# existing claude config\nsome rule\n',
136+
);
137+
runCreate(['agents-md']);
138+
139+
const claude = read('CLAUDE.md');
140+
expect(claude.split('\n').some(l => l.trim() === '@AGENTS.md')).toBe(true);
141+
expect(claude).toContain('some rule');
142+
});
143+
144+
it('is idempotent on a second run', () => {
145+
runCreate(['agents-md']);
146+
const first = read('AGENTS.md') + read('CLAUDE.md');
147+
runCreate(['agents-md']);
148+
const second = read('AGENTS.md') + read('CLAUDE.md');
149+
expect(second).toBe(first);
150+
});
151+
});

0 commit comments

Comments
 (0)