Reactive Resume 的 AGENTS.md 详解:为 pnpm + Turborepo 简历构建器编写可执行的 AI Agent 协作手册
本篇以 Reactive Resume 仓库根目录的 AGENTS.md 为主体,拆解这份"面向 AI Agent 的工程手册"的完整结构:从 monorepo 架构总览、各包职责地图、包边界规则与决策树,到数据库迁移、环境变量、常用命令与陷阱清单。读完你可以掌握在大型 pnpm + Turborepo 项目中如何为 Agent(及人类贡献者)组织一份"可直接执行、可验证"的工程说明文档,并能按其中的命令表在本仓库完成开发环境搭建。
一、AGENTS.md 的定位与整体结构
AGENTS.md 是放在仓库根目录的 Agent 协作约定文件(配套的 CLAUDE.md 与其内容一致,供不同 Agent 客户端读取)。它不是产品文档,而是一份"施工规范":告诉任何接手代码的 Agent 项目长什么样、规则是什么、命令怎么跑、坑在哪里。
从文档结构看,它由几个用 HTML 注释标记的"可插拔区块"加上主体内容组成:
<!-- intent-skills -->区块(Skill Loading):要求在开始实质性修改前,先运行pnpm dlx @tanstack/intent@latest list查看本地可用技能,若技能与任务匹配则先pnpm dlx @tanstack/intent@latest load <package>#<skill>加载对应SKILL.md再动手;跨包任务优先加载被修改包对应的本地技能。<!-- caveman -->区块:一组可选的"精简回复风格"规则(保留全部技术实质、去掉冗余修饰),并明确边界——代码、commit、PR 一律正常书写;安全警告、不可逆操作时自动恢复清晰模式。<!-- graphify -->区块:约定代码库问答优先使用知识图谱工具。当graphify-out/graph.json存在时,先用graphify query "<question>"获取作用域子图;用graphify path "<A>" "<B>"查关联、graphify explain "<concept>"查概念;仅在宽泛架构审查时才读GRAPH_REPORT.md;改完代码后运行graphify update .保持图谱最新(纯 AST,无 API 成本)。- 主体:
# AGENTS.md标题之后的工程约定,包括 Agent skills、Cursor Cloud 专属说明(架构总览、前置条件、代码库地图、Web 应用约定、包边界、数据库、环境、常用命令、陷阱)。
这种"注释区块 + 主体"的分层写法,让不同 Agent 工具可以只识别自己关心的部分,主体规则对所有工具生效。
二、架构总览:两个可部署应用 + 一批 source-consumed 内部包
文档"Overview"一节给出的架构判断,可与仓库实际文件相互印证:
- Reactive Resume 是一个 pnpm monorepo(Turborepo),工作区定义在根 package.json 的
workspaces字段:apps/*、packages/*、tooling(见 pnpm-workspace.yaml)。 - 两个可部署应用:
apps/web:TanStack Start / React 19 / Vite 前端,负责路由、简历编辑器 UI、PWA 与 oRPC 浏览器客户端;apps/server:Hono / Node.js 生产服务,负责挂载 API / auth / MCP / OpenAPI / 静态上传路由,并直接托管构建好的 web 产物。
- 生产 Docker 镜像运行单 Node.js 进程、监听 3000 端口,由
apps/server同时承载 API 与静态站点。这一点可从 Dockerfile 印证:ARG NODE_VERSION=24、runtime 阶段基于node:24-slim,启动脚本为node apps/server/dist/index.mjs(见根 package.json 的start脚本)。 - 一个关键约定:内部包通过
package.json的 export map 直接指向src源码进行消费,不要假设存在包本地dist产物(除非该包显式添加)。这解释了为何仓库中大量测试直接引用源码路径。
三、前置条件:Node 24、Docker 与 pnpm
文档 Prerequisites 一节要求:
- Node.js 24——与 Dockerfile 中
ARG NODE_VERSION=24对齐;可用nvm install 24 && nvm use 24切换。 - Docker——用于运行 PostgreSQL(开发基础设施整体由 compose.dev.yml 编排,见下文"数据库"一节)。
- pnpm——文档写的是 11.21.0;需要注意的是,当前根 package.json 的
packageManager字段已声明pnpm@11.24.0,实际执行时以packageManager声明(corepack/直装 pnpm 会遵循它)为准。
四、Codebase map:一份可以据此定位任意改动的"职责地图"
这是 AGENTS.md 信息密度最高的部分,逐包说明了所有权。以下按文档原意整理,并标注仓库中可对应的实体:
| 位置 | 职责 |
|---|---|
apps/web |
TanStack Start 路由、Vite 配置、PWA、oRPC 浏览器客户端接线、web 功能与简历编辑器 UI |
apps/server |
生产 Hono 应用、路由组合、auth/RPC/MCP/OpenAPI 处理、静态上传、schema JSON、web-dist 回退托管、启动检查 |
packages/api |
oRPC routers、DTO、限流;按功能组织于 packages/api/src/features/*;@reactive-resume/api/routers 导出聚合这些功能路由供 /api/rpc 使用 |
packages/auth |
Better Auth 配置、认证辅助函数、导出类型;服务端适配层 apps/server/src/http/auth.ts 委托给 auth.handler |
packages/db |
Drizzle 客户端与 schema(见 packages/db/src/client.ts);迁移文件位于仓库根 migrations/ 目录(当前含 26 个时间戳迁移目录) |
packages/env |
服务端环境变量校验,并为 app/server 代码自动加载根 .env(实现见 packages/env/src/server.ts:通过 process.loadEnvFile 加载根 .env,再经 @t3-oss/env-core 的 createEnv 做 Zod 校验) |
packages/schema |
Zod schema 与带类型的 resume/page/template 模型 |
packages/pdf |
React PDF 文档、字体注册、共享模板原语、各模板实现、浏览器/服务端 PDF 生成适配器;PDF.js 查看器 UI 留在 apps/web |
packages/resume |
纯简历领域行为:JSON Patch 辅助、社交网络图标映射 |
packages/docx |
DOCX 导出生成 |
packages/mcp |
MCP 工具、prompts、resources、server-card 生成与工具元数据 |
packages/ui |
共享的 Base UI / shadcn 风格组件与 hooks |
packages/fonts、packages/email、packages/import、packages/ai、packages/utils、packages/config |
聚焦的支撑面;优先复用其现有导出,不要新增跨包捷径 |
tooling/ |
仅开发期脚本。放在 packages/ 之外,保证包内只有会被应用/运行时打包的代码 |
这张地图的实践价值在于:任何"这个改动应该放哪里"的问题,都能先查表再动手,避免在错误的包里堆代码。
五、Web 应用约定:SSR 边界与路由规则
文档对 apps/web 的约定逐条对应真实文件:
- 基于文件的路由位于
apps/web/src/routes;apps/web/src/routeTree.gen.ts 由 TanStack Router 工具生成,禁止手工编辑。 - 服务端 HTTP 行为集中在
apps/server/src/{http,rpc,mcp,openapi,static,startup};API/RPC/auth/MCP/静态路由的接线不得下沉到 web 路由里。 - apps/web/src/router.tsx 初始化路由上下文,携带
queryClient、orpc、theme、locale、session、flags;应复用路由上下文,而不是在各处临时重复拉取这些数据。 - SSR 边界:builder 壳位于
apps/web/src/routes/builder/$resumeId,其嵌套预览路由是纯客户端的(ssr: false);公开简历路由apps/web/src/routes/$username/$slug.tsx使用ssr: "data-only"。浏览器专属的简历预览代码放在apps/web/src/features/resume/preview,公开 PDF 查看器放在apps/web/src/features/resume/public;PDF.js / canvas / 浏览器 API 不得进入 SSR 路径,也不得进入packages/pdf。 - 同构 oRPC 客户端位于
apps/web/src/libs/orpc/:服务端调用走进程内 router 客户端,浏览器调用走携带 credentials 的/api/rpc。 - 组件 props 风格:显式 props 的 React 组件优先使用具名 TypeScript props 类型,而非在函数签名里内联对象标注(props 超过一个字段或涉及泛型时尤其如此)。文档给出的示例:
type IntentSelectFieldProps<TValue extends string> = {
label: string;
id: string;
value: TValue | undefined;
options: readonly ComboboxOption<TValue>[];
onChange: (value: TValue | undefined) => void;
};
function IntentSelectField<TValue extends string>(props: IntentSelectFieldProps<TValue>) {
// ...
}
六、包与功能边界:turbo boundaries、导出子路径与决策树
这部分是 AGENTS.md 中最"可执行"的规则集,且能与根 turbo.json 的配置一一对上:
-
工作区依赖必须走包名 + export map。禁止通过仓库路径、
@reactive-resume/*/src/*或 TypeScript path alias 直接 import 其他工作区的src树。 -
turbo boundaries是可执行的边界检查。各工作区的turbo.json声明粗粒度 tag,根 turbo.json 中boundaries.tags(约 L14-L46)定义了依赖拒绝规则,例如:app:server与runtime:server:denyapp:web、runtime:browser;runtime:browser:denyapp:server、runtime:server;runtime:universal:denyapp:web、app:server、runtime:server;role:domain:deny 两个 app、role:adapter、role:infra;role:ui同理。
也就是说,"universal 包不得依赖任何运行时专属包"这类约束不是口头约定,而是
pnpm exec turbo boundaries会拦截的硬性规则。 -
运行时专属代码放在显式导出子路径后,如
@reactive-resume/pdf/browser、@reactive-resume/pdf/server、@reactive-resume/env/server;根导出保持环境中立(除非包本身就是 server-only)。 -
通配导出仅限"文件型"叶子库:当前只有
@reactive-resume/ui/components/*、@reactive-resume/ui/hooks/*以及 schema 的 resume 模型文件;拥有运行时行为的包优先显式导出。 -
新功能归属规则(文档原文要点):
- 新 API procedure 与业务逻辑放进所属
packages/api/src/features/*模块,按功能聚合路由接线、DTO、辅助函数与服务,只通过packages/api/package.json暴露有意为之的公开面;认证 procedure 优先用packages/api/src/context.ts的protectedProcedure。 - 新增数据库列/表先改
packages/db/src/schema/*,再用dotenvx run -f .env.local -- pnpm db:generate生成根级迁移。 - 简历数据形状变更先在
packages/schema/src/resume/*,再更新 API DTO、importer、PDF 渲染与 web 表单。 - 新增/重命名模板要同步四处:
packages/schema/src/templates.ts、packages/pdf/src/templates/index.ts、packages/pdf/src/templates/<name>/模板源码、apps/web/public/templates/{jpg,pdf}静态预览。 - 简历 JSON Patch 行为归
@reactive-resume/resume/patch,DOCX 导出归@reactive-resume/docx——都不要放进@reactive-resume/utils。 - 共享 PDF 分区过滤在
packages/pdf/src/templates/shared/filtering.ts;模板特例留在模板目录内。 packages/pdf/src/hooks/use-register-fonts.ts独占 React PDF 字体注册、标准字体处理、CJK 回退栈与全局连字行为。- MCP 实现归
@reactive-resume/mcp,app 包不得从另一个 app 的源码树 import MCP 实现。 packages/utils只暴露窄导出;需要工具时添加显式导出路径,而非 import 私有文件。
- 新 API procedure 与业务逻辑放进所属
-
放置决策树(Placement decision tree),文档给出的 9 步判断顺序:
- web 路由 / route loader / 面向用户的 web 工作流 →
apps/web/src/routes或apps/web/src/features; - 服务端 HTTP 路由/适配、启动检查、静态处理、MCP 传输、OpenAPI/well-known →
apps/server/src; - 需要认证的 API 行为 → 所属
packages/api/src/features/*模块; - 不依赖 DB/HTTP/DOM/PDF 渲染器的纯简历数据行为 →
packages/resume; - 渲染简历 PDF → 共享 React PDF/模板代码进
packages/pdf,PDF.js 查看器/canvas UI 进apps/web/src/features/resume; - DOCX 导出 →
packages/docx; - MCP 工具/prompts/resources →
packages/mcp; - 通用 UI 原语/hook →
packages/ui,工作流专属 UI 留在所属 web feature; - 窄的横切辅助函数 → 确认没有更合适的领域包之后,才加显式
packages/utils导出。
- web 路由 / route loader / 面向用户的 web 工作流 →
七、数据库:compose 起 Postgres,drizzle-kit 不读 .env
文档 Database 一节的关键点:
- PostgreSQL 通过 Docker Compose 启动:
sudo docker compose -f compose.dev.yml up -d postgres
- 开发默认连接串:
postgresql://postgres:postgres@localhost:5432/postgres。compose.dev.yml 中postgres服务(postgres:latest,POSTGRES_USER/PASSWORD=postgres,仅绑定127.0.0.1:5432)与之对应;同文件还编排了redis(redis://redis:6379,appendonly 模式)与seaweedfs(S3 兼容存储,S3_ENDPOINT=http://seaweedfs:8333)及一次性建桶任务seaweedfs_create_bucket。 - 重要陷阱:
drizzle-kit(pnpm db:migrate的底层)直接从process.env读DATABASE_URL,不会自动加载.env文件。因此迁移命令要通过dotenvx执行,例如dotenvx run -f .env.local -- pnpm db:migrate,确保变量进入进程环境。 - 生产服务器在启动阶段先跑迁移再对外服务。这一点可由源码印证:apps/server/src/startup/checks.ts 第 34 行附近调用
migrate(db, { migrationsFolder: resolveWorkspaceFolder("migrations") }),迁移目录即仓库根migrations/。因此手动pnpm db:migrate主要用于首次初始化、迁移调试,或不启动应用直接应用迁移。
八、环境配置:.env.example、必填三项与可选依赖
文档 Environment 一节的完整要求如下:
- 将 .env.example 复制为
.env.local。三个必填变量:APP_URL(默认http://localhost:3000);DATABASE_URL(默认postgresql://postgres:postgres@localhost:5432/postgres);AUTH_SECRET(任意非空字符串;.env.example 注释建议用openssl rand -hex 32生成)。
- S3 / SeaweedFS 可选:当
S3_ACCESS_KEY_ID、S3_SECRET_ACCESS_KEY、S3_BUCKET全部设置时启用 S3 兼容存储。仓库检入的.env.example预置了 SeaweedFS 默认值,因此要么把seaweedfscompose 服务一起启动,要么注释掉这些 S3 变量以使用<workspace>/data下的本地文件系统存储。LOCAL_STORAGE_PATH若设置必须是绝对路径——这与 packages/env/src/server.ts 中的校验一致(z.string().min(1).refine(isAbsolute, ...),约 L66),而 packages/env/src/server.ts 同时展示了整套服务端变量的 Zod 校验规则(如DATABASE_URL必须为 postgres 协议 URL、SERVER_PORT默认 3001)。 REDIS_URL与ENCRYPTION_SECRET对核心简历流程是可选的,但已保存的 AI provider 与认证后的/agent工作区两者都要求。开发这些功能时启动rediscompose 服务并在.env.local中设置两者;宿主机开发用REDIS_URL=redis://localhost:6379,容器内应用用REDIS_URL=redis://redis:6379(与 compose.dev.yml 中 app 服务的REDIS_URL一致)。源码层面可印证强度要求:packages/env/src/server.ts 约 L76 规定ENCRYPTION_SECRET至少 32 个字符。- dotenvx 前缀规则:跑开发服务器或迁移命令时加
dotenvx run -f .env.local --前缀,例如dotenvx run -f .env.local -- pnpm dev;测试、typecheck、lint、边界检查与pnpm build默认不需要该前缀,若因缺某个环境变量失败,再补前缀重试。
补充一个机制细节:packages/env/src/server.ts 在模块加载时会用 process.loadEnvFile 自动加载根 .env(已有进程环境变量优先),这正是文档 Codebase map 中"packages/env 自动加载根 .env"一句的出处。
九、常用命令表(完整继承自原文档)
| 任务 | 命令 |
|---|---|
| 安装依赖 | pnpm install |
| 仅启动 Postgres | sudo docker compose -f compose.dev.yml up -d postgres |
| 启动 Postgres + SeaweedFS | sudo docker compose -f compose.dev.yml up -d postgres seaweedfs seaweedfs_create_bucket |
| 启动完整开发基础设施 | sudo docker compose -f compose.dev.yml up -d postgres redis seaweedfs seaweedfs_create_bucket |
| 生成迁移 | dotenvx run -f .env.local -- pnpm db:generate |
| 执行迁移 | dotenvx run -f .env.local -- pnpm db:migrate |
| 开发服务器 | dotenvx run -f .env.local -- pnpm dev(监听 3000 端口) |
| 仅 web 开发服务器 | dotenvx run -f .env.local -- pnpm dev:web |
| Lint/格式化 | pnpm check(Biome) |
| 边界检查 | pnpm exec turbo boundaries |
| 测试 | pnpm test(Vitest) |
| 构建 | pnpm build |
| 类型检查 | pnpm typecheck |
命令表与根 package.json 的 scripts 完全对应(build/typecheck/test 均委托 turbo run,db:generate/db:migrate 通过 --filter=@reactive-resume/db 定向到 db 包)。
聚焦验证时优先用包过滤器而非全仓命令:
pnpm --filter web typecheck
pnpm --filter @reactive-resume/pdf test
pnpm --filter @reactive-resume/api test
pnpm exec turbo boundaries
通过 pnpm --filter <package> test -- <path> 运行 Vitest 时,测试路径是包内相对路径。
十、Gotchas:从启动行为到 Turborepo 严格环境模式
文档陷阱清单逐条都有仓库证据可查:
- 启动即迁移:服务端启动路径会在对外服务前自动执行迁移,所以
pnpm db:migrate主要用于首次设置、迁移调试或不启动应用直接应用迁移(见上文 apps/server/src/startup/checks.ts 的migrate调用)。 - 邮件需要 SMTP 配置:未配置时邮件被打印到控制台,开发阶段可接受——验证链接会出现在服务器日志里。
- pre-commit hook:lefthook.yml 的 pre-commit 会对暂存文件跑
biome check,提交前先跑pnpm check以免 hook 失败。 pnpm check具有写能力:根 package.json 中定义为biome check --write --unsafe . && markdownlint-cli2 --fix && github-actionlint ...,会实际修改文件;需要只读检查时应使用更窄的 Biome 命令。- Biome 风格约定(见 biome.json):tab 缩进、双引号、行宽 120、整理 import 分组、对
clsx/cva/cn排序 Tailwind 类。 - 多数包用
tsgo --noEmit做类型检查、vitest run --passWithNoTests跑测试。 - 工作树里可能有无关的本地改动:动手前先
git status --short,避免回滚你没动过的文件。 - 新增环境变量必须在
turbo.json中登记。Turborepo 2.x 默认运行在严格 env 模式——会过滤未列入globalEnv(或任务级env/passThroughEnv)的变量。任何加到 packages/env/src/server.ts 的新变量,都必须同步加入根 turbo.json 的globalEnv数组(约 L47-L91,当前已包含DATABASE_URL、AUTH_SECRET、REDIS_URL、ENCRYPTION_SECRET、全部 S3 变量与FLAG_*特性开关等),否则变量在子进程运行时为undefined——即使它在 OS/容器环境中已正确设置。
十一、Agent skills:Issue tracker 与多上下文领域文档
文档还引用了两份 Agent 技能文档,构成"工程上下文检索"的配套机制:
- Issue tracker:docs/agents/issue-tracker.md 规定 issue 与 spec 统一跟踪在 GitHub Issues,全部操作走
ghCLI——创建(heredoc 多行 body)、阅读(gh issue view <number> --comments)、列表(--json+jq过滤)、评论、加/删标签、带评论关闭;并约定 PR 不作为外部 feature request 的受理面,以及/wayfinder场景下 map/child ticket/blocking/frontier/claim/resolve 的完整操作语义。 - 领域文档:docs/agents/domain.md 定义多上下文 domain-doc 布局——仓库根
CONTEXT-MAP.md是上下文边界的权威来源,各上下文可有CONTEXT.md词汇表与上下文内docs/adr/决策记录;全局系统级决策在docs/adr/(当前含 0001-workspace-boundaries.md 与 0002-agent-ai-sdk-adoption.md 两篇)。要求:探索前先读相关上下文文档、输出使用词汇表术语、与既有 ADR 冲突时显式声明而不是静默覆盖。
十二、小结:这份 AGENTS.md 值得借鉴的三件事
- 一切规则都挂到可执行物上:包边界规则对应
turbo boundaries的真实 deny 配置(turbo.json),命令表对应根 package.json 的真实 scripts,架构描述对应 Dockerfile、compose.dev.yml 与apps/server/src/startup/checks.ts的源码事实——Agent 可以逐条验证,而不是照单全收。 - 用"所有权地图 + 决策树"替代口头架构:Codebase map 回答"东西在哪",Placement decision tree 回答"新东西放哪",两者配合消除了 monorepo 最常见的分歧来源。
- 陷阱清单比功能清单更有价值:drizzle-kit 不读
.env、pnpm check会写文件、Turborepo 严格 env 模式会吞变量——这三条都是只会在运行时暴露的隐性故障,写在文档里直接省掉一轮排障。
适用前提说明:文中版本与命令以当前仓库状态为准(仓库根 package.json 声明版本 5.2.9、packageManager 为 pnpm@11.24.0、turbo 2.x);AGENTS.md 中"pnpm 11.21.0"为文档写作时点的固定要求,与当前 packageManager 字段存在版本差,实际以 package.json 声明为准。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00