Skip to main content

add-rule

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

Informações da origem

Repositório
ghost-him/ZeroLaunch-rs
Última atividade na origem
18 de julho de 2026 às 13:09
Idioma detectado do SKILL.md
chinês
Estrelas
572
Forks
30

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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` 数组的每个条目都能编译通过(非空、非纯空白、有效的正则语法)
Ver no GitHub