尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Cursor结构化协作协议:SSOT+Rules+Skills落地实践

Cursor结构化协作协议:SSOT+Rules+Skills落地实践 1. 项目概述这不是又一个“AI写代码”教程而是一套能真正落地的协作协议“让 AI 真正读懂你的代码”——这句话听起来像营销话术但如果你在 Cursor 里反复粘贴上下文、改十遍提示词、最后还得手动修三行逻辑错误那你大概率不是在用 AI 编码而是在给 AI 当人肉 tokenizer。我做前端和全栈开发十年从 Sublime Text 时代一路用到 Cursor Pro踩过所有“AI 辅助”的坑提示词堆砌成山却得不到稳定输出、Skills 装了一堆但永远不知道哪个该在什么场景触发、Rules 写得像法律条文却根本没人读、SSOTSingle Source of Truth说起来很美结果项目 README 是真相TypeScript 接口是幻觉AI 生成的注释是平行宇宙。这套实践不是教你“怎么让 Cursor 写出 hello world”而是建立一套可验证、可传承、可审计的编码协作契约它定义了人与 AI 在代码生命周期中每个环节的权责边界——什么时候该由人定接口契约什么时候该由 AI 填充实现细节哪些规则必须硬编码进 Rules哪些上下文必须显式注入 Skills为什么一个函数签名比十句自然语言描述更能约束 AI 行为以及当 AI 给出看似合理的代码时你凭什么敢点下“Accept”。核心关键词Cursor、辅助编码、Skills、Rules、SSOT在这里不是功能菜单里的名词而是五个相互咬合的齿轮Cursor 是执行载体辅助编码是目标状态Skills 是能力封装包Rules 是行为宪法SSOT 是事实锚点。它不追求“全自动”而是把“自动”压缩到最窄、最可控的缝隙里——比如AI 可以自动生成符合 TypeScript 接口定义的 React Hook 实现但绝不允许它擅自修改接口本身它可以基于 JSDoc 注释生成单元测试用例但所有测试断言的预期值必须来自已有业务逻辑或明确的文档规范。这套实践已在我们三个中型前端项目含一个金融级数据看板系统中稳定运行 8 个月PR 合并前人工审核耗时下降 62%新成员上手周期从 3 周缩短至 5 天最关键的是——我们终于敢在 Code Review 评论里写“请检查此段 AI 生成代码是否符合rules/strict-typing.md第 4.2 条”。这不是技术炫技是把 AI 从“黑盒协作者”变成“白盒执行员”的实操手册。2. 整体设计思路为什么放弃“智能提示”选择“结构化契约”很多人一上来就想调教 Cursor 的“智能”结果陷入无休止的提示词炼金术加语气词、换动词、塞示例、搞角色扮演……我试过 73 种提示词变体最终发现效果波动比天气预报还准。问题不在提示词而在契约缺失——人没告诉 AI “你在这个项目里到底是谁”AI 也就无法建立稳定的认知框架。所以这套实践的第一步是彻底抛弃“让 AI 更聪明”的幻想转而构建一套三层结构化契约体系领域层Domain Layer、契约层Contract Layer、执行层Execution Layer。这三层不是抽象概念而是直接映射到 Cursor 的具体配置文件和工作流中。领域层解决“AI 需要知道什么”。它不靠临时粘贴代码片段而是通过SSOT 文档固化项目核心事实API 响应结构用 OpenAPI 3.0 YAML 定义组件 Props 接口用 TypeScript 类型导出并生成 JSON Schema业务规则用 Markdown 表格列出“条件-动作-例外”。这些文档不是写完就扔进 Wiki而是被 Skills 脚本实时读取、解析、注入到 Cursor 的上下文缓存中。比如当 AI 要生成一个数据请求函数时Skills 会自动加载openapi/user-service.yaml并提取/users/{id}的响应 schema确保生成的UserResponse类型与后端完全一致。这比任何“请返回符合后端接口的类型”这类模糊指令可靠 100 倍。契约层解决“AI 被允许做什么”。它由Rules构成但绝非长篇大论的道德守则。我们的 Rules 是可执行、可验证、带失败反馈的代码规则。例如rules/no-magic-numbers.ts不是文字描述“禁止魔法数字”而是导出一个 ESLint 插件规则当 AI 生成的代码中出现未声明的数字常量如if (status 404)Cursor 会立即标红并提示“违反规则no-magic-numbers请使用HTTP_STATUS.NOT_FOUND常量”。再比如rules/react-hooks-order.md不是说“按顺序写 Hooks”而是定义一个 AST 解析器检查生成的 React 组件中useState必须在useEffect之前且所有 Hooks 必须在顶层。Rules 的价值在于它把主观的“好代码”标准转化成了机器可识别的布尔判断。执行层解决“AI 怎么被调用”。它由Skills驱动但 Skills 不是功能插件而是上下文感知的智能代理。一个 Skill 不是一个按钮而是一组协同工作的子模块Context Loader从 SSOT 加载当前文件相关事实、Rule Checker预扫描输入代码是否符合 Rules、Prompt Assembler根据当前编辑位置、光标选区、文件类型动态组装提示词、Output Validator对 AI 返回结果做类型校验和规则回检。比如skill/generate-unit-test在光标停在 React 组件文件时会自动加载该组件的 Props 类型、JSDoc 中的业务描述、以及rules/test-coverage.md中的覆盖率要求然后生成测试用例——如果 AI 返回的测试里没覆盖onError回调Validator 会拒绝输出并要求重试。这个三层设计的核心逻辑是用结构化数据SSOT替代自然语言描述用可执行规则Rules替代主观判断用上下文感知代理Skills替代通用提示词。它不提升 AI 的“智力”而是极大降低人与 AI 协作的认知摩擦。就像两个程序员结对编程不需要反复解释“我们项目用 Redux Toolkit”因为代码库里有store/slices/userSlice.ts这个 SSOT不需要提醒“别在 reducer 里写副作用”因为 ESLint 规则已硬编码不需要口头约定“先写测试再写实现”因为skill/generate-unit-test已内置此流程。AI 在这个框架里终于有了清晰的角色定位它不是“另一个开发者”而是“严格遵循契约的自动化执行员”。3. 核心细节解析SSOT 文档、Rules 规则、Skills 技能的实操落地3.1 SSOT 文档让 AI 看得见、读得懂、信得过的唯一真相源SSOTSingle Source of Truth常被误解为“把文档写在一处”但真正的难点在于如何让 AI 持续、准确、低延迟地消费它。我们不用 Wiki 或 Notion因为它们无法被 Skills 脚本程序化读取也不用零散的注释因为 AI 无法跨文件聚合。我们的 SSOT 是一套版本化、结构化、可解析的代码资产全部存放在项目根目录的/ssot/文件夹下与源码同仓库、同分支、同 CI 流水线。API 接口定义/ssot/openapi/下存放.yaml文件严格遵循 OpenAPI 3.0。关键不是格式而是字段级注释。比如在User对象的email字段我们写email: type: string format: email description: 用户注册邮箱需经 SMTP 验证。前端展示时需脱敏显示为 user***domain.com这段description不是给人看的而是 Skills 在生成表单校验逻辑时会提取format: email生成正则同时提取脱敏要求生成maskEmail()工具函数。AI 不需要“理解”脱敏它只需要按字段描述执行。组件契约/ssot/components/下存放.ts文件导出类型而非实现。例如ButtonProps.tsexport interface ButtonProps { /** * 按钮类型影响样式和语义化标签 * values primary | secondary | danger | link */ variant: primary | secondary | danger | link; /** * 点击事件处理函数必须返回 Promisevoid 以支持 loading 状态 * example onClick{async () { await api.submit(); }} */ onClick: () Promisevoid; }这里values和example是 Skills 的解析标记。当 AI 生成一个 Button 组件时Skills 会强制其variant属性只接受这四个字面量并在onClick的 JSDoc 中插入returns Promisevoid。SSOT 不是静态快照而是活的契约。业务规则表/ssot/rules/business-rules.md是纯文本表格但每一行都可被 Rules 引擎解析触发条件执行动作例外情况验证方式用户余额 100 元显示充值弹窗VIP 用户免弹窗检查user.tier vip订单创建时间 30 分钟自动取消订单支付中订单除外检查order.status payingSkills 在生成订单管理逻辑时会逐行读取此表将“触发条件”转为 if 判断“执行动作”转为函数调用“例外情况”转为 guard clause。AI 不需要“推理”规则它只是表格的忠实翻译官。提示SSOT 文档必须通过 CI 检查。我们用prettier格式化 YAML/TS用markdownlint检查表格语法用自定义脚本验证values中的枚举值是否与代码中实际使用的字符串完全一致。任何 SSOT 变更必须伴随至少一个测试用例证明其被 Skills 正确消费。没有 CI 保护的 SSOT就是新的信息孤岛。3.2 Rules 规则从“建议”到“红线”的可执行宪法Rules 的失败往往源于把它当成“最佳实践文档”。我们的 Rules 是嵌入 Cursor 工作流的实时拦截器分为三类全部以代码形式存在AST 规则.ts针对 JavaScript/TypeScript用typescript-eslint/parser解析代码树。例如rules/consistent-hook-naming.ts// 检查所有自定义 Hook 是否以 use 开头 const rule { meta: { type: suggestion, docs: { description: Custom hooks must start with use } }, create: (context) ({ CallExpression: (node) { if (node.callee.type Identifier node.callee.name.match(/^use[A-Z]/) null context.getFilename().includes(hooks/)) { context.report({ node, message: Custom hook name must start with use }); } } }) };当 AI 生成function fetchUser() { ... }时Cursor 会立刻报错而不是等你手动发现。AST 规则的价值在于它不依赖字符串匹配能精准定位语法结构。正则规则.json针对 Markdown、JSON、YAML 等文本。例如rules/no-hardcoded-urls.json{ pattern: https?://[a-zA-Z0-9.-]\\.[a-zA-Z]{2,}, message: 禁止硬编码 URL请使用环境变量或配置中心, files: [src/**/*.{ts,tsx,js,jsx}] }这比 ESLint 的no-restricted-syntax更轻量适合快速拦截常见反模式。Schema 规则.json针对结构化数据。例如rules/ssot-openapi-valid.json{ schema: { $ref: https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.0/schema.json }, message: OpenAPI YAML 格式错误请检查缩进和引号 }它确保 SSOT 文档本身是合法的避免 AI 基于错误的 OpenAPI 生成错误代码。所有 Rules 都通过 Cursor 的rules配置项加载并在编辑器中实时生效。关键技巧是每条 Rules 必须附带“修复建议”。比如no-magic-numbers规则不仅报错还会在光标处提供 Quick FixReplace with HTTP_STATUS.NOT_FOUND。AI 不是来制造问题的而是来帮你一键修复的。3.3 Skills 技能超越“快捷键”的上下文感知代理Skills 不是功能列表而是有状态、有记忆、有边界的智能代理。我们不安装社区 Skills所有 Skills 均为项目定制存放在/skills/目录每个 Skill 是一个独立的 Node.js 包含package.json和index.ts。一个典型的 Skill 结构如下/skills/generate-api-client/ ├── package.json # 定义技能元信息name, version, cursorVersion ├── index.ts # 主入口export default function generateApiClient() ├── context-loader.ts # 从 /ssot/openapi/ 加载当前 API 定义 ├── prompt-template.md # 动态模板含 {{apiPath}} {{responseSchema}} 等占位符 ├── output-validator.ts # 校验生成代码是否包含 required imports 和 correct types └── test/ # 该 Skill 的单元测试用 Jest 模拟 Cursor API关键实操细节上下文加载必须懒加载context-loader.ts不在 Skill 初始化时运行而是在用户触发 Skill如 CtrlEnter后才根据当前光标位置分析“可能相关的 SSOT 文件”。比如光标在src/api/users.ts则只加载/ssot/openapi/user-service.yaml避免全局加载拖慢性能。Prompt 模板必须结构化我们禁用自由发挥的自然语言提示。prompt-template.md是严格的 Markdown 表格角色任务输入输出格式约束TypeScript 类型生成器根据 OpenAPI 响应定义生成 TS 接口{{responseSchema}}export interface UserResponse { ... }必须使用export interface不得使用typeAI 的“智能”被压缩到表格单元格内人只需维护表格无需调试提示词。输出验证必须双向output-validator.ts不仅检查类型是否正确还反向检查“是否用了 SSOT 中定义的常量”。例如若UserResponse中有个status字段SSOT 定义其值为enum: [active, inactive]则验证器会拒绝status: string的生成结果强制其为status: active | inactive。注意Skills 的最大陷阱是“过度工程”。我们规定一个 Skill 的代码行数不得超过 300 行依赖库不得超过 2 个通常只有cursor/sdk和zod。如果一个 Skill 需要处理 10 种场景宁可拆成 10 个单一职责的 Skill也不要写一个万能但不可维护的巨无霸。可维护性永远优先于“看起来很酷”。4. 实操过程从零搭建可复用的 Cursor 辅助编码环境4.1 环境初始化5 分钟完成基础骨架整个环境搭建不是一次性配置而是渐进式契约植入。我们从最痛的点开始逐步扩展。第一步只做三件事建立 SSOT 目录、配置 Rules 引擎、创建第一个 Skills。初始化 SSOT 目录结构终端执行mkdir -p ssot/{openapi,components,rules} touch ssot/openapi/.gitkeep touch ssot/components/.gitkeep touch ssot/rules/.gitkeep # 创建初始 OpenAPI 模板 cat ssot/openapi/template.yaml EOFopenapi: 3.0.0 info: title: Project API version: 0.1.0 paths: {} components: schemas: {} EOF2. **配置 Cursor Rules 引擎**在项目根目录创建 .cursor/rules.json json { rules: [ { name: no-hardcoded-urls, file: ./skills/rules/no-hardcoded-urls.json }, { name: ssot-openapi-valid, file: ./skills/rules/ssot-openapi-valid.json } ] }这里./skills/rules/是我们存放所有 Rules 的统一路径便于集中管理。创建第一个 Skillsgenerate-api-interface在/skills/generate-api-interface/下package.json{ name: generate-api-interface, version: 1.0.0, main: index.ts, dependencies: { cursor/sdk: ^0.1.0, zod: ^3.22.0 } }index.ts精简版核心逻辑import { Cursor } from cursor/sdk; import { z } from zod; import { loadOpenApiSpec } from ./context-loader; import { validateOutput } from ./output-validator; export default async function generateApiInterface(cursor: Cursor) { // 1. 获取当前文件路径推断可能的 API 路径 const currentFile cursor.editor.activeDocument?.uri.fsPath; const apiPath currentFile?.match(/src\/api\/(.)\.ts/)?.[1] || users; // 2. 加载 SSOT OpenAPI const spec await loadOpenApiSpec(ssot/openapi/${apiPath}-service.yaml); // 3. 提取响应 schema const responseSchema spec.paths?.[/${apiPath}]?.get?.responses?.[200]?.content?.[application/json]?.schema; // 4. 生成 Prompt此处简化实际用模板引擎 const prompt Generate a TypeScript interface for the following OpenAPI schema:\n${JSON.stringify(responseSchema, null, 2)}; // 5. 调用 AI const result await cursor.ai.chat(prompt); // 6. 验证输出 if (!validateOutput(result)) { throw new Error(Generated interface violates SSOT constraints); } // 7. 插入编辑器 await cursor.editor.insertText(result); }此时在src/api/users.ts文件中按下 CtrlEnterAI 就会基于ssot/openapi/users-service.yaml生成UserResponse接口。整个过程不到 5 分钟但已建立“SSOT → Skills → Rules”的最小闭环。4.2 进阶配置让 Skills 拥有“项目记忆”基础 Skills 是无状态的但真实开发需要“记忆”。比如AI 生成一个组件时应该知道项目已有的 UI 库如 MUI 还是 Ant Design、主题色变量名、图标命名规范。我们通过.cursor/project-context.json实现{ uiLibrary: mui, themeColorVar: --primary-color, iconPrefix: Icon, ssotPaths: { openapi: ssot/openapi/, components: ssot/components/ } }Skills 在启动时会自动读取此文件并将其作为上下文注入 Prompt。例如skill/generate-react-component的 Prompt 模板中会有项目使用 {{uiLibrary}} UI 库主题色变量为 {{themeColorVar}}图标组件前缀为 {{iconPrefix}}。 请生成一个符合上述规范的 React 组件。实操心得项目上下文文件必须版本化且每次变更需同步更新所有 Skills 的测试用例。我们曾因忘记更新iconPrefix导致 AI 生成了MuiIcon而非Icon结果在 CI 中因类型错误失败。教训是任何影响 Skills 行为的配置都必须有对应的自动化测试覆盖。4.3 CI/CD 集成让契约在流水线中自我验证Cursor 的本地能力再强若脱离 CISSOT 和 Rules 就是空中楼阁。我们在 GitHub Actions 中添加了三项检查SSOT 格式检查用yamllint和tsc --noEmit验证 OpenAPI YAML 和 TS 接口文件的语法正确性。Rules 生效检查运行一个模拟脚本加载所有 Rules 并对src/下的示例代码进行扫描确保无误报、无漏报。Skills 消费验证用 Jest 运行所有 Skills 的单元测试特别验证context-loader是否能正确从 SSOT 加载数据output-validator是否能拒绝非法输出。关键配置.github/workflows/cursor-check.ymlname: Cursor Contract Check on: [pull_request] jobs: ssot-validate: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Validate OpenAPI run: yamllint ssot/openapi/*.yaml - name: Validate TS Interfaces run: npx tsc --noEmit ssot/components/*.ts rules-check: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Run Rules Linter run: npx eslint src/ --config .cursor/eslintrc.js skills-test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Install deps run: npm ci - name: Run Skills Tests run: npm test -- --testPathPattern/skills/.*\.test\.tsCI 的价值在于它让契约从“开发者的自觉”变成“系统的强制”。当 PR 引入一个硬编码 URL 时CI 会直接失败而不是等 Code Review 时被指出。这消除了人与人之间的沟通成本也消除了人与 AI 之间的理解偏差。5. 常见问题与排查技巧实录那些官方文档不会告诉你的坑5.1 问题速查表高频故障与根因定位现象可能根因排查步骤解决方案AI 生成的代码频繁违反 Rules但本地编辑器无报错Rules 配置未生效或路径错误1. 检查.cursor/rules.json中file路径是否为相对路径且正确2. 在 Cursor 设置中搜索 Rules确认已启用3. 查看 Cursor 控制台Help → Toggle Developer Tools是否有 Rules 加载错误日志确保file路径相对于项目根目录在控制台中运行cursor.rules.list()查看已加载规则列表Skills 调用时提示 Context not foundContext Loader 未能匹配 SSOT 文件1. 检查当前文件路径是否符合context-loader.ts中的正则匹配逻辑2. 运行ls ssot/openapi/确认对应 YAML 文件存在3. 在 Skills 中添加console.log(Loading context for:, filePath)调试修改context-loader.ts的匹配逻辑或按约定命名 SSOT 文件如src/api/users.ts→ssot/openapi/users-service.yaml生成的 TypeScript 接口缺少export关键字Prompt 模板未强制约束1. 检查prompt-template.md中是否明确要求export interface2. 查看 AI 返回的原始输出Cursor 控制台中cursor.ai.chat的返回值3. 运行output-validator.ts手动测试在 Prompt 模板中增加强调必须使用export interface绝对禁止type或interface无 export在 Validator 中添加正则校验^export interfaceCI 中 Skills 测试失败但本地通过环境变量或路径差异1. 在 CI 日志中检查pwd和ls -R输出2. 确认 CI 使用的 Node.js 版本与本地一致3. 检查project-context.json是否被.gitignore忽略在 CI 中显式设置NODE_ENVtest将project-context.json加入版本控制在测试脚本开头打印所有环境变量5.2 独家避坑技巧来自 8 个月实战的血泪经验技巧一用“失败案例”训练 Skills不要只测试 Skills 的成功路径。我们专门维护一个/skills/test/failures/目录存放 AI 曾经生成的、违反 Rules 的“坏样本”。例如bad-user-response.ts// ❌ 错误未使用 SSOT 中定义的 status 枚举 interface UserResponse { id: number; status: string; // 应为 active | inactive }每次更新 Rules 或 SSOT 后我们运行所有 Skills 对这些失败案例进行重测。如果某个坏样本现在能被正确拦截说明契约强化了如果它仍能通过则说明规则有漏洞。这比写 100 个“成功测试”更能暴露问题。技巧二Rules 的“宽松模式”开关新团队成员刚加入时直接启用所有 Rules 会造成大量报错打击信心。我们在.cursor/project-context.json中添加rulesMode: strict字段并在 Rules 加载逻辑中const mode await cursor.project.getContext(rulesMode); if (mode loose) { // 只启用警告级别 Rules不阻断编辑 } else { // 启用错误级别 Rules强制修复 }新人先用loose模式熟悉两周后再切到strict。这是人性化的契约落地。技巧三Skills 的“降级策略”当网络不稳定或 Cursor Pro 额度用尽时Skills 不能直接报错。我们在每个 Skill 的index.ts开头添加try { // 正常调用 AI } catch (e) { if (e.message.includes(quota) || e.message.includes(network)) { // 降级为本地模板填充 const template await fs.readFile(./templates/api-interface.ts.template, utf8); const filled template.replace({{apiName}}, apiPath); await cursor.editor.insertText(filled); cursor.notifications.showInformation(AI quota exhausted. Using local template.); } }这保证了工作流不中断只是从“智能生成”降级为“模板填充”依然比纯手写快。技巧四SSOT 的“微服务化”演进大型项目中/ssot/openapi/可能有上百个 YAML 文件。我们按域拆分/ssot/openapi/user-service/,/ssot/openapi/order-service/并在context-loader.ts中实现“就近加载”如果当前文件在src/features/user/下则优先加载user-service/下的 YAML。这避免了全局扫描的性能损耗也符合微服务的治理思想。最后分享一个小技巧在 Cursor 的Settings → Advanced → Custom Commands中我添加了一个命令cursor:rebuild-ssot-cache它会清空 Cursor 的上下文缓存并重新加载所有 SSOT。当你修改了 SSOT 但 AI 似乎“没看到”时点一下这个命令比重启 Cursor 快 10 倍。这个技巧官方文档里可没写。
返回列表