Skip to main content

add-rule

分析规则需求,设计 TTSR 规则的 frontmatter(condition、scope),生成 .omp/rules/ 下的规则文件

소스 정보

저장소
ghost-him/ZeroLaunch-rs
최근 소스 활동
2026년 7월 18일 13:09
감지된 SKILL.md 언어
중국어
스타
573
포크
31

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
add-rule
description
分析规则需求,设计 TTSR 规则的 frontmatter(condition、scope),生成 .omp/rules/ 下的规则文件
argument-hint
无直接参数 — 通过自然语言描述规则内容来触发
# add-rule — 新增 TTSR 规则 分析用户描述的约束或模式,设计合适的 `condition` 和 `scope`,生成带 frontmatter 的 `.omp/rules/*.md` 规则文件。 ## 触发方式 在对话中出现以下表述时触发: - "添加以下的规则:..." - "将xxx沉淀成一个规则" - "根据以上的内容,将xxx写成规则" - 用户描述了需要固化的编码约束 ### 不适合作规则的场景(应拒绝或建议替代方案) 遇到以下情况,**不建议**生成规则文件: - **一次性约束**:只在某个 PR 里有效的模式,规则文件会永久存在 - **过于具体的匹配**:condition 匹配的是 UUID、特定变量名、临时 hack 等不具备通用性的标识 - **工具链相关**:某个 linter/clippy 已经能检查的,规则是重复劳动 - **高度易变的路径**:scope 指向的目录结构可能经常变,规则跟不上 - **无法自动检测的模式**:需要人工理解业务语义才能判断的(如"这个算法的时间复杂度不能超过 O(n²)") → 以上情况建议用户改用 conversation 中的一次性提醒。 ## 执行流程 ### 第一步:理解规则内容(需求分析) 通读用户描述的规则内容,将零散的需求翻译成结构化的分析结果。按以下维度逐一确认: **1. 约束性质** — 这条规则是在约束什么? - ☐ 代码模式(禁止某种写法、强制某种写法) - ☐ 命名约定(文件命名、函数命名、类型命名) - ☐ 架构边界(层间依赖、模块可见性、禁止循环引用) - ☐ 数据流规范(序列化/反序列化方式、状态管理) - ☐ 全局常识(项目架构说明、编码风格指南) **2. 触发时机** — 用户希望规则在什么场景下"跳出来"? - 助手正在写某种代码时(如写 `unsafe` 块、写测试、写某种 import) - 助手正在调用某个工具时(如调用 `read` 读某个配置文件) - 助手在自然语言中提到某个概念时 - 任何时候都应该存在的知识(全局参考) → 这个直接决定用 `text` / `thinking` / `tool:<name>` 哪个 scope token **3. 约束对象的具体特征** — 选一个"最独特"的标识符: - 函数/方法名(`extract_value`、`Bun.sleep`、`setTimeout`) - 类型名(`MyType`、`Result`、`Optional`) - 关键字(`unsafe`、`unwrap`、`todo!`) - import 路径(`@core/`、`crate::utils`) **4. 文件范围** — 规则约束应用在哪些文件上? - 全局(所有文件)→ 不做路径限制 - 特定目录 → 用 glob 缩小(`src/commands/**/*.rs`) - 特定文件类型 → 用扩展名 glob(`**/*.test.ts`) - 单个文件 → 精确路径(`src/main.rs`) → 这个直接决定 `tool:<name>(<glob>)` 中的 glob 在分析过程中如果用户描述的规则有歧义(例如"测试里不能用 unwrap",但没说是 Rust 还是 JS),先确认再继续。分析完成后,将以上结构化结果带入第二步映射到 frontmatter 字段。 ### 第二步:设计 frontmatter 根据规则内容,设计规则文件中的 frontmatter 字段。TTSR(Time-Traveling Stream Rules)通过这套元数据决定**何时**匹配(condition)和**在哪**匹配(scope)。 --- **先理解 TTSR 是怎么工作的 —— 数据流向:** ``` 助手在"说话"(生成回复) │ ├─ 写自然语言(text)──→ text 缓冲区 ├─ 思考过程(thinking)──→ thinking 缓冲区 └─ 调用工具(tool)──→ tool 缓冲区 │ ▼ 每个缓冲区的内容不断累积, 每来一段新内容就拿 condition 正则去匹配 │ 匹配上了? ├─ 否 → 继续流 └─ 是 → scope 允许在这个场景触发吗? ├─ 否 → 继续流 └─ 是 → 中断助手 → 注入规则内容 → 重试 ``` **拆开解释几个术语:** - **"流"(stream)**:助手生成回复不是一次性给的,是一段一段(像水流一样)陆续产生的。这个持续输出的过程就叫"流"。 - **三种流来源(source)**: - `text` — 助手写的自然语言正文(就是你看到的对话回复) - `thinking` — 模型的内心独白/思考过程(如果你开了思维链) - `tool` — 助手调用工具时传的参数(例如 `edit` 工具传的补丁内容、`write` 工具传的文件内容) - **"流缓冲区"(stream buffer)**:流过来的内容不会丢掉,而是暂存在一个"缓冲区"里。每次新内容到达,系统把缓冲区里的**全部内容**拿去和 condition 正则做匹配。这样可以匹配到跨段的模式(比如一段话前后各一半)。 - **"标准化快照"(matcherDigest)**:当助手调用 `edit`/`write` 这类工具时,参数可能是补丁格式(hashline),不是直接的可读代码。`matcherDigest` 是工具提供的一个"翻译器",能把补丁格式还原成**最终要写入的源码**。这样你写 condition 时就可以直接写源码里出现的函数名,而不用关心补丁格式长什么样。 **一句话总结**:condition 正则匹配的是"助手正在说/写的内容"(不是文件名,不是文件内容快照),scope 控制的是"在哪种流场景下才允许触发"。 --- #### `condition` — 触发正则表达式 `condition` 是一个正则表达式(或表达式数组),匹配**流缓冲区内容**(stream buffer): - **工具参数流**(`tool:edit`、`tool:write`、`tool:read` 等):匹配工具调用的参数原始 JSON,如果工具提供了 `matcherDigest`(如 edit/write 的源码快照还原器),则匹配还原后的**标准化源码快照** - **助手的自然语言输出**(`text`):匹配助手生成的 prose 文本 - **思考过程**(`thinking`):匹配模型的 thinking/chain-of-thought 文本 当任意 condition 命中缓冲区时,规则触发中断当前流,注入规则内容后重试。 类型:`string | string[]`(数组 = OR 语义,任一匹配即触发) 设计原则: - 选择规则约束范围内**最独特**的标识符(函数名、类型名、宏调用、特定关键字、import 路径、API 调用) - 优先用单一名词或短模式,避免过长正则 - 如果有多个入口点,用数组传递多个条件,或用 `|` 合并(注意转义) - 如果规则需要始终存在(全局架构指南), condition 可以用 `".*"`,匹配所有流内容(注意会增加 token 消耗) 匹配内容随流来源变化,请根据规则的实际触发场景选择: | 约束场景 | condition 匹配的内容 | 推荐 condition | 说明 | | ------------------------------ | --------------------- | ------------------------------------------------ | --------------------------------- | | 约束 helper 函数调用 | edit/write 的源码快照 | `"extract_value"` | 函数名出现在写入的代码中 | | 约束 import 模式 | edit/write 的源码快照 | `"from '@core/parser'"` | import 语句出现在写入的源码中 | | 约束工具行为(如禁用某工具) | 工具参数 JSON | `'"tool_name"'` | 工具名称出现在参数 JSON 中 | | 约束框架用法(如 unsafe 代码) | 写作的 prose/代码 | `"unsafe"` | 助手在生成代码时出现关键字 | | 约束 serde 反序列化 | edit/write 的源码快照 | `"serde_json::from_str\|serde_json::from_value"` | 反序列化调用写入代码时触发 | | 约束全局架构 | 所有流 | `".*"` | 始终触发(注意会增加 token 消耗) | > **注意**: > > - condition 匹配的不是文件名,而是**流内容**。文件路径约束由 `scope` 控制 > - 如果 condition 值看起来像文件 glob(如 `*.rs`、`src/**/*.ts`),系统会自动将其推导为 `tool:edit(<glob>), tool:write(<glob>)` scope,并将 condition 设为 `".*"`。这是一种简写形式,手动编写时建议明确写 condition + scope > - 支持 PCRE 风格的头部内联 flag:`(?i)`(忽略大小写)、`(?m)`(多行)、`(?s)`(单行/DOTALL),会自动翻译为原生 JS RegExp flags #### `scope` — 触发的流范围 > `scope` 定义哪些**流来源**(text / thinking / tool)在哪些**路径**上会触发规则检查。 类型:`string | string[]`,每个 token 为以下格式之一: | Token | 含义 | 示例 | | --------------------- | ------------------------------------- | -------------------------------------- | | `text` | 匹配助手自然语言输出(prose) | `text` | | `thinking` | 匹配思考过程文本 | `thinking` | | `tool` / `toolcall` | 匹配所有工具调用 | `tool` | | `tool:<name>` | 匹配指定工具的所有调用 | `tool:edit`、`tool:write`、`tool:read` | | `tool:<name>(<glob>)` | 匹配指定工具中路径匹配 glob 的调用 | `tool:edit(src/**/*.rs)` | | `<bare_name>` | 裸工具名(等同于 `tool:<bare_name>`) | `edit`、`write` | 设计原则: - 精确限定触发场景。大多数规则只需要 `tool:edit(<glob>)` 和/或 `tool:write(<glob>)`,不要用 `tool:edit(**/*.rs)` 覆盖整个项目,尽量缩小到规则真正约束的目录 - 如果规则既要限制 prose 中提及某 API,也要限制代码中使用,加 `text` token - 如果规则是只读参考(项目总览类),用 `text` 让写作时触发 - 如果规则约束所有文件类型,省略 glob 默认行为:scope 为空时,系统自动启用 `text` + `tool`(所有 prose 和工具调用,排除 thinking) Scope 示例: | 适用范围 | 推荐 scope | | ----------------------- | ----------------------------------------------------------------- | | 单个模块的代码写操作 | `"tool:edit(src/path/to/mod.rs), tool:write(src/path/to/mod.rs)"` | | 某个目录下所有代码操作 | `"tool:edit(src/commands/**/), tool:write(src/commands/**)"` | | 分散文件 + prose 中提及 | `"text, tool:edit(src/a.rs), tool:edit(src/b.rs)"` | | 所有 .rs 文件的编辑 | `"tool:edit(**/*.rs)"` | | 全局(所有流) | `"text, thinking, tool"` | #### 完整 frontmatter 示例 ```yaml --- name: my-rule description: 禁止在 Rust 测试中使用 unwrap() condition: - "\.unwrap\(\)" - "(?i)unwrap" scope: - tool:edit(**/*.rs) - tool:write(**/*.rs) --- ``` > 提示:生成规则文件时,直接使用 frontmatter YAML 格式。`condition` 和 `scope` 支持 YAML 序列(数组形式)或逗号分隔的字符串。 ### 第三步:生成规则文件 在 `.omp/rules/` 下创建 `<short-name>.md`,格式: ```markdown --- name: <规则文件名> description: <一句话描述规则约束什么> condition: <触发正则,支持数组或字符串> scope: <流范围 token,支持数组或逗号分隔> --- # <规则标题> <规则正文,包含具体的行为约束、原因、示例> ``` **文件命名规则**: - 使用 `kebab-case`(短横线命名),例如 `no-unwrap-in-tests.md` - 文件名去掉 `.md` 扩展名后即为规则的 `name`,会被 `sanitizeRuleName()` 清洗(只保留字母数字和连字符) - 同名规则按优先级覆盖:项目规则 > 用户规则 > 内置默认规则(同名时优先级高的胜出) **YAML 中的正则转义注意事项**: - YAML 双引号字符串:`\.`、`\(`、`\)` 这类序列**不是** YAML 的合法转义序列,所以会原样保留,传给正则引擎后含义正确(`\.` → 匹配点号,`\(` → 匹配左括号)。**不需要额外转义**。 - 只有 `\\`(YAML 双引号中代表一个反斜杠)需要留意——如果正则要匹配一个**字面反斜杠**,YAML 双引号里要写 `\\\\`(YAML → 两个反斜杠字符串 → 正则里匹配一个反斜杠)。 - 最安全的做法:**用 YAML 数组形式 + 单引号**。YAML 单引号不处理任何转义,正则里的反斜杠写起来和纯正则完全一致: ```yaml condition: - '\.unwrap\(\)' - "(?i)unwrap" ``` 这样写,`\.unwrap\(\)` 传到正则引擎就是 `\.unwrap\(\)`,没有歧义。 - 如果 YAML 解析遇到问题,系统有 fallback 按行解析 frontmatter,但不要依赖这个 **规则正文编写规范**: - 有禁有导:"不要做 X,应该做 Y" 是标准格式 - 解释原因:每条约束说明为什么 - 给出示例:正确写法 + 错误写法 - 保持简短:超过 200 行考虑拆分 ### 第四步:自我校验 在生成后确认: - [ ] `condition` 正则在作用范围内足够独特(不会误触发) - [ ] `scope` 覆盖规则约束的所有流场景和文件路径(不会漏触发) - [ ] 如果规则引用了具体文件路径,这些路径在磁盘上存在 - [ ] 规则正文的路径和目录名与项目实际结构一致(检查 `.omp/AGENTS.md` 或实际文件树) - [ ] YAML 中的正则转义正确——YAML 双引号中 `\.`、`\(` 会原样传递到正则(不需要额外转义);如果用单引号+数组形式,完全不用操心转义 - [ ] scope 中的 glob 路径在项目中确实能匹配到文件(用 `glob` 快速验证) - [ ] `condition` 数组的每个条目都能编译通过(非空、非纯空白、有效的正则语法)
GitHub에서 보기