Rule 规则系统
Rule 是让 AI 遵守你的编码规范的方式。不同于传统在 README 里写"请遵循某某规范"——那种做法 AI 记不住。Rule 是系统级约束,Qoder 会根据规则类型自动或按需注入到提示中。
但好消息是:你不用关心规则文件怎么写,告诉 AI 你想要什么就行。
使用篇
1. 创建 Rule
在 Growth 项目中创建一条规则,只需要在对话里说一句:
创建一个 rule,内容为:所有接口方法必须有 XML 文档注释
AI 会自动在 .qoder/rules/ 下生成对应的 .md 文件。你不需要打开文件管理器,不需要写 YAML,不需要知道目录结构。
想加什么约束,直接告诉 AI:
- "创建 rule:函数不超过 30 行"
- "创建 rule:变量命名用 camelCase"
- "创建 rule:每次 commit 前先跑测试"
AI 会帮你写好 frontmatter(name、description、trigger 类型),放到正确的位置。
2. 修改和删除 Rule
修改也是一句话的事:
把 rule
coding-conventions里的函数行数限制改成 50 行
删除同理:
删除 rule
release-checklist,用不上了
不需要自己去找文件在哪,不需要手动编辑 YAML。Rule 的维护完全可以通过对话完成。
3. Rule 文件是什么样的
虽然你不用手动写,但了解它的结构有助于理解"AI 到底在读什么"。一个 Rule 文件由三部分组成:
markdown
---
trigger: always_on # 生效方式:always_on / model_decision / manual / file_glob
---
## 规则内容
- 项目符号、编号列表、代码示例
- AI 会严格按这些规则执行元数据只有一个关键字段:
| 字段 | 作用 |
|---|---|
trigger | 规则何时生效(always_on / model_decision / manual / file_glob) |
globs | trigger=file_glob 时,匹配的文件路径模式 |
description | trigger=model_decision 时,告诉 AI 什么场景触发 |
实际项目中最常见的三种类型:
| 类型 | 适用场景 | 例子 |
|---|---|---|
always_on | 全局必须遵守的规范 | 编码规范、技术栈 |
model_decision | 特定场景触发 | 创建模块、生成测试 |
manual | 偶尔使用的检查清单 | 架构审查 |

实践篇
4. Rule 示例:code-check
以下是在 Growth Components 项目中积累的规则汇总,命名为 code-check。你可以把它作为一个模版,根据自己项目的实际情况调整:
markdown
---
trigger: manual
---
# 代码检查清单
## Early Return(提前返回)
- 先排除错误,再做正事
- 函数开头通过卫语句排除边缘情况后,主逻辑保持在最外层
- 消除深层 if-else 嵌套
## 异常处理
- 顶层类捕获异常,记录日志,返回失败结果
- 内部业务类不包 try-catch,异常透明上抛
- 必需字段校验失败直接抛出 `InvalidOperationException`
## Logger 规范
- 统一使用 Serilog + LogFormatHelper + LoggerConfig
- 每个类定义 `LoggerName`
- EnterScope / ExitScope 成对出现
- Debug 记中间过程,Info 记关键路径,Warn 记边缘情况,Error 记严重错误
## 访问修饰符
- Interfaces/ 下为 public
- Implementations/ 下为 internal
- 业务代码为 internal
- Module 入口类为 public
## 接口命名
- 接口名:`I` + 功能名 + `Service`
- 实现类名:功能名 + `ServiceImpl`
- 方法名使用动词开头(Get/Find/Create/Update/Delete)
- 异步方法以 Async 结尾,返回 `Task` 或 `Task<T>`
## 文档注释
- 所有接口方法必须有 XML 文档注释
- 说明功能、参数和返回值5. Growth 中的 Rule 体系
在 Growth 项目中,我们建了 14 条规则,按功能分四类:
全局规范(always_on)——每次对话自动加载:
| 规则 | 作用 |
|---|---|
tech-stack | 项目技术栈(.NET 8、Serilog、EF Core 等) |
project-overview | 项目架构概览(21 个项目、模块分层) |
coding-conventions | 编码规范(Early Return、Logger、Try-Catch) |
场景触发(model_decision)——AI 判断场景后自动加载:
| 规则 | 触发描述 |
|---|---|
ai-agent | 修改 Agent 逻辑 |
workflow-node | 创建/修改工作流节点 |
repository-pattern | 添加数据库持久化 |
new-module-template | 创建新模块 |
test-execution | 运行或编写测试 |
document-generation | 生成需求文档 |
文件匹配(file_glob)——编辑特定文件时自动加载:
| 规则 | 匹配模式 |
|---|---|
interface-rules | **/Interfaces/*.cs |
module-entry-files | **/*Module.cs、**/LoggerConfig.cs、**/LogFormatHelper.cs |
node-executor-files | **/Enhanced*Executor.cs |
手动调用(manual)——需要时通过 @rule 触发:
| 规则 | 作用 |
|---|---|
module-architecture-check | 模块架构检查清单 |
debug-common-pitfalls | 常见调试陷阱 |
这 14 条规则构成了 Growth 项目的"编码宪法"——AI 在做任何修改时,都被这层约束框在团队规范内。
6. 唯一最佳实践
能用场景触发就别用全局加载。
always_on的规则每次对话都会加载,占用上下文- 只有所有对话都必须遵守的规范(如技术栈、编码基础)才用
always_on - 特定场景的规范用
model_decision或file_glob,让 AI 按需加载
实践中,3 条 always_on + 若干场景规则的组合效果最好。多了反而浪费上下文窗口。
知识篇
7. Rule 的运行原理
Rule 的本质是条件注入。Qoder 在每次调用模型时,按规则类型决定是否把规则内容注入到系统提示中:
always_on:每次调用都注入model_decision:AI 评估描述后决定file_glob:当前文件匹配 glob 模式时注入manual:用户通过@rule手动引用时注入
所有激活的规则合计不超过 100,000 字符,超出部分被截断。
8. Rule vs Skill
| Rule | Skill | |
|---|---|---|
| 本质 | 约束(不能怎么做) | 指令(帮我去做什么) |
| 生效 | 自动或半自动 | 用户通过 / 主动触发 |
| 场景 | 编码规范、命名约定 | 代码审查、文档生成 |
| 数量 | 10~20 条 | 可以很多 |
简单理解:Rule 是贴在墙上的团队规范,Skill 是操作手册。Rule 让 AI "不做错事",Skill 让 AI "高效做事"。
9. 从框架角度理解 Rule
如果你熟悉 Microsoft AI Agent 框架(Microsoft.Agents.AI + Microsoft.Extensions.AI),你会发现 Qoder 的 Rule 系统本质上就是该框架 AIContextProvider 机制的自然语言版本。两者的底层逻辑完全一致——在大模型执行前,按条件向上下文中注入约束信息。
AIContextProvider:框架中的 Rule
在微软框架中,AIContextProvider 是一个抽象接口,定义了 Agent 执行前/后两个生命周期阶段:
csharp
public interface AIContextProvider
{
// Agent 执行前调用——相当于 Qoder 的规则注入
Task InvokingAsync(AgentRequest request, IAgentContext context, ...);
// Agent 执行后调用——相当于规则执行后的清理
Task InvokedAsync(AgentRequest request, AgentResponse response, ...);
}每个 Provider 在 InvokingAsync 中判断条件,决定是否向 context.Instructions 中注入内容。这正是 Qoder Rule 做的事情——只不过 Qoder 通过自然语言 + LLM 判断,而微软框架通过 C# 代码 + 类型系统来实现。
映射关系
| Qoder Rule | Microsoft AIContextProvider |
|---|---|
规则文件 .md | Provider 类(CustomXxxProvider) |
trigger: always_on | Provider 注册时不加条件,始终执行 |
trigger: model_decision | 在 InvokingAsync 内写判断逻辑 |
trigger: file_glob | 在 InvokingAsync 内检查文件名模式 |
trigger: manual | ToolApprovalAgent 审批模式 |
| 注入到系统提示 | context.Instructions.Add(...) |
| Tick 优先级控制 | 规则类型间的加载优先级 |
条件注入的两种实现
微软框架(代码实现):
csharp
public class FileBasedProvider : AIContextProvider
{
public async Task InvokingAsync(AgentRequest request, IAgentContext context, ...)
{
// 文件名条件注入
if (request.Text.Contains("Interfaces/"))
{
context.Instructions.Add(new AgentMessage
{
Role = ChatRole.System,
Content = "接口命名:I + 功能名 + Service"
});
}
}
}Qoder(自然语言实现):
markdown
trigger: file_glob
globs: "**/Interfaces/*.cs"
---
接口命名:I + 功能名 + Service
实现类命名:功能名 + ServiceImpl两种方式做的是同一件事:当编辑接口文件时,把接口命名规范注入到对话中。区别只是一个用 if 判断文件名,一个用 globs 匹配文件名。
Tick 机制与规则优先级
微软框架通过 Tick 值控制 Provider 的执行顺序:
csharp
provider.Use<AgentInstructionsProvider>(tick: 10); // 先执行
provider.Use<ChatHistoryMemoryProvider>(tick: 20); // 后执行
provider.Use<AgentModeProvider>(tick: 30); // 更后执行Qoder 虽然没有显式的 Tick,但规则加载顺序隐含了优先级:always_on 最先注入(始终存在),然后是 model_decision(AI 判断后追加),接着是 file_glob(文件匹配后追加)。这个顺序是合理的——全局规范先确立框架,场景规范再补充细节。
理解了这个映射,你就能理解
- 为什么 recommends 用
always_on而不是model_decision——因为框架中每个 Provider 都会执行InvokingAsync,全部执行完才调模型;同理,always_on规则每次对话都会加载。 - 为什么
file_glob比always_on更节省上下文——因为框架中 Provider 按需注册,不是每个请求都触发所有 Provider。 - 为什么规则多了会影响响应速度——因为 Provider 多了
InvokingAsync的执行时间也会变长。 - 为什么说两种框架设计哲学一致——微软框架用代码写条件注入,Qoder 用自然语言写条件注入,都是"不到执行的那一刻,绝不加载全部能力"。
这种映射关系也解释了为什么 Skill 章节引入了微软框架的装饰器管道——Qoder 的整个上下文工程体系(Rule + Skill + Agent)和 Microsoft AI Agent 框架的 Provider + Decorator + Agent 架构是一脉相承的。自然语言是新的类型系统,LLM 是新的运行时。
写在最后
Rule 是 Qoder 框架中最"隐形"的基础设施。它不像 Skill 那样需要主动触发,也不像 Agent 那样有完整的角色定义。但正是这些简单的约束文件,在每一次对话中静默地工作,确保 AI 的输出始终符合团队标准。
最好的标准,是让 AI 不需要被提醒。你把规范写进 Rule 文件,就再也不用在每次对话中反复交代"请注意我们的编码规范"。