用 Codex Skill 管理项目文档:让 Code Agent 读懂你的业务
Code Agent 的上限不在模型能力,而在你的仓库里有没有它能读懂的知识。
目录
- Agent 为什么会把需求做烂
- 为什么选 Skill 而不是 MCP 或 SDD
- NVC Practice 的知识架构全景
- 第一层:repo-map 做路由
- 第二层:7 个 Skill 覆盖全部业务域
- 第三层:References 按需展开
- 渐进式披露的上下文经济学
- 从"有什么知识"到"怎么用知识"
- 防腐化:让知识不会越用越错
- 实操建议
Agent 为什么会把需求做烂
NVC Practice 是一个基于非暴力沟通理论的 AI 练习平台,用户选择场景后和 AI 角色扮演对话,系统按 NVC 四步法(观察→感受→需要→请求)实时评分。后端用 Spring Boot 4 + Spring AI 2.0,涉及多 Agent 协同评分、RAG 知识库检索、场景关卡引擎等模块。
项目不大,但业务耦合度不低。刚开始用 Code Agent 做需求时碰到一个问题:改练习评分逻辑,Agent 找到了评分引擎的代码,按"每个维度独立打分"的思路写了一段实现。看起来没毛病,但它不知道这个项目里 NVC 四步评分不是孤立的——观察、感受、需要、请求四个维度之间有依赖关系:如果用户根本没表达感受,那"需要"和"请求"的评分权重应该降权而不是独立计算。这个规则写在 PRD 里,没写在代码注释里,Agent 自己翻不到。
类似的坑不止一个。练习会话有"闯关"和"自由练习"两种模式,状态机不同;场景关卡的解锁依赖前置场景的通过,但解锁逻辑散落在ScenarioService和PracticeSessionService两个类里;sessionId在练习域和用户画像域里含义不同,混用会导致数据归属错误。
这些问题的根因一样:领域知识没有被结构化地记录在仓库里。代码告诉你"怎么做",但说不清"为什么这样做"、“哪些约束不能碰”。
参考阿里妈妈团队在 AI Coding 实践中的总结——Code Agent 在复杂项目中撞墙,根因是人读不到的知识,Agent 也读不到。NVC Practice 的做法是把项目知识写进仓库,用 Codex Skill 作为载体。
为什么选 Skill 而不是 MCP 或 SDD
MCP 接外部文档
试过,效果不好。项目文档本身有大量过时内容,而且缺乏结构化组织,一次读取直接撑爆上下文窗口。
** SDD(Spec-Driven Development)** 也考虑过。从零开始的新项目用 SDD 效果不错,但 NVC Practice 是持续迭代的存量项目——一个两三百行的代码改动,spec 要写五六百行,维护成本倒挂。spec 随用随弃,知识不累积。
Skill 的渐进式披露机制天然适合这个场景:元数据常驻用于路由判断,主文件按需加载,引用详情延迟读取。“大量知识但每次只需要一部分”——这正好是复杂项目里 Agent 的工作模式。
NVC Practice 的知识架构全景
整个知识体系分三层,对应 Agent 拿到需求后的认知路径:
第一层:AGENTS.md + repo-map Skill(全局索引与路由) ↓ 读完这层,Agent 知道该去哪个业务域 第二层:6 个业务域 Skill(模块核心知识) ↓ 读完这层,Agent 理解业务语义和代码结构 第三层:references/ 目录下的详情文件(按需展开) Agent 判断需要更多信息时才读加上一个跨域术语字典 Skill,总共 7 个 Skill、约 30 个引用文件。下面逐层展开。
第一层:repo-map 做路由
AGENTS.md是 Agent 拿到需求后读的第一个文件,Token 消耗很少,但完成最关键的决策:我应该读哪个模块的知识。
nvc-repo-mapSkill 承担路由层。核心是一张需求关键词到业务域的映射表:
"登录、注册、token、当前用户、权限" → nvc-auth-user "画像、MBTI、沟通风格、用户分析" → nvc-profile-domain "场景、关卡、解锁、专题、进度" → nvc-scenario-domain "练习、对话、轮次、闯关、自由模式" → nvc-practice-domain "NVC 评分、四步法、维度打分、雷达图" → nvc-scoring-engine "知识库、RAG、文档上传、向量检索" → nvc-knowledge-base "接口、DTO、配置一起改、跨模块" → nvc-change-playbook附带反面判定规则,防止误路由:
- "看起来像对话"不一定归练习域——如果它涉及场景解锁逻辑,主域是场景
- "看起来像评分"不一定归评分引擎——如果它涉及练习会话的状态推进,主域是练习
- "看起来像用户"不一定归用户模块——如果它涉及画像分析和 MBTI 推断,主域是画像
第二层:7 个 Skill 覆盖全部业务域
每个 Skill 是一个 SKILL.md 主文件加上一组 references/ 引用文件。主文件控制在 3000 字以内,覆盖四个维度。
业务域 Skills
以nvc-practice-domain为例:
路由判定
:需求命中练习会话创建、对话轮次推进、闯关/自由模式切换、练习报告生成时使用。
使用顺序
:先看lifecycle.md确认会话处于哪个阶段,再看round-pipeline.md确认单轮对话的完整流程,改状态推进时看state-machine.md,改报告生成时看practice-report.md。
关键入口
:直接列出代码入口的完整路径——
app/src/main/java/nvc/practice/modules/practice/api/PracticeController.java app/src/main/java/nvc/practice/modules/practice/service/PracticeSessionService.java app/src/main/java/nvc/practice/modules/practice/service/PracticeRoundService.java app/src/main/java/nvc/practice/modules/practice/service/PracticeReportService.java必守约束
:
- 练习会话的"闯关模式"和"自由练习"是两套状态机,不要混写
- 一轮对话里 AI 的回复和用户的回复必须成对存在,不能只存一半
- 练习报告的雷达图数据来源于每轮评分的维度均值,不是最后一轮的分数
- 闯关模式下,只有当轮评分全部达标才能解锁下一轮,不能跳过
评分引擎 Skill
nvc-scoring-engine是项目里最复杂的模块。主文件要覆盖:
四步评分的依赖关系
:观察→感受→需要→请求不是四次独立调用。如果用户没表达感受,需要和请求的评分应该降权。评分引擎内部有一个NvcDependencyGraph定义维度间的依赖关系。
Multi-Agent 编排
:5 个 Agent(观察官、感受官、需要官、请求官、综合官)的执行顺序和并行策略。观察官和感受官可以并行,但需要官必须等感受官的结果。
结构化输出契约
:每个 Agent 返回的 JSON 结构、评分范围(0-10)、反馈字段格式,必须和前端的雷达图组件对齐。
跨域支撑 Skills
nvc-change-playbook:当改动同时涉及接口、DTO、评分逻辑和数据库表结构时,提供影响检查清单和回滚策略。
第三层:References 按需展开
以nvc-practice-domain为例,引用文件覆盖练习生命周期的每个切面:
| 文件 | 内容 |
|---|---|
lifecycle.md | 会话状态(CREATED → IN_PROGRESS → COMPLETED / ABANDONED)和推进链路 |
round-pipeline.md | 单轮对话执行顺序:幂等检查 → AI 生成回复 → 用户回复 → 四步评分 → 推进轮次 |
state-machine.md | 闯关模式和自由练习两套状态机的完整定义 |
practice-report.md | 报告生成逻辑:维度均值计算、雷达图数据结构、AI 总结生成 |
gotchas.md | 易错点:sessionId 归属校验、轮次编号连续性、评分回滚 |
Agent 不会一开始就加载全部文件。改评分逻辑只读round-pipeline.md,改状态推进只读state-machine.md。
渐进式披露的上下文经济学
Skill 的三级加载设计直接决定了上下文消耗:
| 层级 | 何时加载 | Token 消耗 | 作用 |
|---|---|---|---|
| 元数据(name + description) | 始终可见 | ~100 字 | 路由判断:这个 Skill 跟当前需求有关吗? |
| 主文件正文(SKILL.md) | Skill 被触发时 | ~3000 字 | 理解业务语义、代码结构、制定方案 |
| 引用详情(references/) | Agent 判断需要时 | 按需 | 展开具体流程、API 定义、易错点 |
对比"把所有知识塞进一个大 prompt",渐进式披露让 Agent 在合适的时机获取正确的知识。上下文窗口不会被撑爆,注意力不会被无关信息稀释。
从"有什么知识"到"怎么用知识"
NVC Practice 的 Skills 经历了一轮关键迭代。
最初版本只写了业务规则,没有标注这些规则对应代码中的哪些流程。Agent 需要自行推理"这条规则大概和哪段代码有关"——大部分时候能猜对,但准确率不稳定。比如它知道"NVC 评分要考虑维度间的依赖",但不知道这个依赖关系定义在NvcDependencyGraph里,可能会去改NvcScoreService的打分逻辑而不是调整依赖图。
改进后,每个 Skill 的主文件直接列出关键入口的完整代码路径,引用文件中的每条业务规则都标注了对应的代码位置。round-pipeline.md里写的不是"评分前要检查依赖关系",而是:
1. 调 NvcDependencyGraph.resolveDependentDimensions(currentDim) 获取前置维度 2. 检查前置维度的评分是否已产出,未产出则标记为降权 3. 调 ObservationAgent / FeelingAgent / NeedAgent / RequestAgent 分别评分 4. 调 SummaryAgent 汇总,生成结构化评分结果 5. 成功后写入 practice_turn 表,失败则回滚本轮状态Agent 读完这层,不需要猜,直接知道改哪里。
防腐化:让知识不会越用越错
过时的知识比没有知识更危险。当 Skill 文件说评分流程是 A→B→C,而实际代码已经改成了 A→C,Agent 会按错误流程写代码,而且不会怀疑知识的正确性。
使用时反向校验
Agent 读完 Skill 再去看实际代码时,天然处于一个能交叉比对的位置。发现知识描述和代码现状不一致,就在输出方案的同时标注差异。几乎没有额外成本——Agent 本来就要读知识、读代码,比对只是顺带完成。
变更时同步更新
nvc-change-playbook的主文件里有一条硬约束:改完评分逻辑或场景配置后,必须检查对应的 Skill 文件是否需要同步更新。nvc-scoring-engine的 scripts 目录下放一个extract_agent_contracts.py,从评分 Agent 的代码中提取输出字段契约。跑一次脚本,生成的契约如果和 Skill 里写的不同,说明知识该更新了。
这套机制不是为了自动化,而是为了在知识和代码之间建一个一致性锚点。
实操建议
从一个模块开始。
NVC Practice 的 7 个 Skill 不需要一次性写完。先写练习域(最复杂的那个),跑通几个真实需求后,再扩展到评分引擎、场景域、画像域。
触发描述比正文更重要。
元数据里的description字段决定了 Agent 会不会读这个 Skill。写得太窄会漏触发,写得太宽会误触发。做法是把触发条件写成"需求命中某个 API 路径或涉及某个业务动作",同时附带反面条件。
统一流程命名。
每段最小划分的业务逻辑确定唯一的流程名词。“单轮对话评分"在练习域、评分引擎域、变更剧本里都叫同一个名字,不叫"轮次评估"也不叫"维度打分”。跨模块推理时 Agent 靠名字匹配关联关系,命名不统一直接导致推理断裂。
把约束写成人话。
比起"评分维度间存在依赖关系,需在评分前解析依赖图","如果用户没说感受,需要和请求的分数要往下降"更容易被 Agent 正确执行。口语化的约束比格式化的规范文档更有效。
脚本是校验锚点,不是替代品。
自动生成的索引文件用来和手工维护的知识交叉比对。两者不一致,以代码为准,更新 Skill。
写在最后
NVC Practice 的 7 个 Skill、约 30 个引用文件,回答的是一个问题:如果明天换一个完全不了解这个项目的人(或 Agent)来改代码,他需要知道什么才能不犯错?
答案不在代码注释里,不在 wiki 里,也不在每次口头交接的记忆里。答案在仓库里,在 Agent 每次动手前能读到的那份结构化知识里。