news 2026/10/1 1:54:34

Yaak 项目 AI 辅助开发规则深度解读:插件系统、MCP Server 与 Rust 类型生成的最佳实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Yaak 项目 AI 辅助开发规则深度解读:插件系统、MCP Server 与 Rust 类型生成的最佳实践
  • 开发工具
  • 接口测试
  • 桌面应用

【免费下载链接】yaak

The most intuitive desktop API client. Organize and execute REST, GraphQL, WebSockets, Server Sent Events, and gRPC 🦬

项目地址:https://gitcode.com/GitHub_Trending/ya/yaak
点击查看免费下载

导读

.claude/rules.md是 Yaak 桌面 API 客户端仓库(Rust 内核 + TypeScript 前端 + 插件生态的混合工程)专门面向 AI 辅助开发协作而维护的一份"契约式"规则文档。它浓缩了该仓库在多语言、多进程、多包(npm workspaces + Cargo crates)协作开发中最容易踩坑的六类硬约束:提交纪律、构建与静态检查、插件后端的UpdateSource语义、时间戳所有权、MCP Server 的上下文模型,以及 Rust 类型到 TypeScript bindings 的再生成流程。阅读本文后,你将掌握这些规则背后的源码级原理(对应UpdateSource::Plugin的调用链、upsert_date的时间戳策略、MCP 工具如何获取 workspace 上下文、ts-rs 如何驱动gen_*.ts生成),并能在开发或审查 AI 生成的代码时准确执行这些约束。

一、规则文档的定位:面向 AI 助手的协作契约

在 Yaak 仓库中,人类开发者与 AI 助手的协作规则被刻意拆成了三层,各司其职:

文件定位核心内容
.claude/rules.md面向 AI 辅助开发的强制性执行规则提交确认、lint/bootstrap 时机、插件后端约束、MCP 上下文限制、bindings 再生成
AGENTS.md面向 Agent 的仓库级约束tag 安全(v*与yaak-api-*的区别)、未经明确批准不得 commit/push/tag
CONTRIBUTING.md面向社区贡献者的流程约定仅接受 bug fix PR;非 bugfix 改动需先获得作者许可

三者共同构成"规则金字塔":CONTRIBUTING.md决定能否贡献,AGENTS.md约束 Git 操作安全,而.claude/rules.md则聚焦到每次修改后的技术动作。本文后续所有内容均以.claude/rules.md为主线展开,并用仓库源码逐一印证。

二、通用开发纪律与构建/静态检查流程

2.1 提交纪律:未经明确确认,绝不 commit / push

.claude/rules.md的第一条规则是NEVER commit or push without explicit confirmation,这与 AGENTS.md 中"未经明确批准不得 commit、push 或打 tag"的要求互为印证。之所以在 AI 协作语境下被反复强调,是因为 Yaak 的发布模型对 tag 极为敏感:应用与 CLI 共用v*tag(CLI 与应用版本锁定、随每次应用 tag 发布到 npm),而@yaakapp/api使用独立的yaak-api-*tag。AI 助手如果自主执行 Git 写操作,极易在错误的 tag 上引发发布事故。因此该规则要求:任何 Git 写操作都必须先获得人类开发者显式批准。

2.2 修改 TS/JS 后必须运行npm run lint

规则要求修改任何 TypeScript 或 JavaScript 文件后运行npm run lint。查看根目录 package.json 的 scripts 可以发现这条命令并非单一步骤,而是一个并行管道:

"lint": "run-p lint:*", "lint:vp": "vp lint", "lint:workspaces": "npm run --workspaces --if-present lint"

即npm run lint同时执行两条链:

  • lint:vp:使用vp(Vite Plus 的命令行工具)对仓库根级配置与代码做静态检查;
  • lint:workspaces:递归进入 package.jsonworkspaces字段声明的全部 workspace(包括packages/*、plugins/*、plugins-external/*、crates-tauri/*、crates/*、apps/*等近 60 个包),对各自声明了 lint 脚本的包逐一执行。

因此,AI 助手在修改任意一个插件(如plugins/template-function-json)或前端包(如apps/yaak-client)后,都必须通过这条命令确保整仓不引入 lint 错误。

2.3 修改插件运行时或 MCP Server 代码后必须运行npm run bootstrap

规则要求在修改plugin runtime 或 MCP server 代码后运行npm run bootstrap。bootstrap在 package.json 中定义为一组串行任务:

"bootstrap": "run-s bootstrap:*", "bootstrap:install-wasm-pack": "node scripts/install-wasm-pack.cjs", "bootstrap:build": "npm run build", "bootstrap:vendor": "npm run vendor"

其链路为:安装wasm-pack(负责编译 Rust 到 WebAssembly)→ 构建所有 workspace → 执行 vendoring(vendor-plugins将插件打包为发布产物、vendor-protoc处理 gRPC 的 protoc 工具链,见 scripts/vendor-plugins.cjs 与 scripts/vendor-protoc.cjs)。

之所以这两类改动需要全量 bootstrap,是因为插件运行时依赖 Rust 编译出的 wasm 产物(如 crates/yaak-wasm 与 crates/yaak-templates 的pkg/目录),而 MCP Server 插件(plugins-external/mcp-server)运行时会通过 scripts/vendor-node.cjs 打入 Node 运行时。仅改前端文件时不需要走这条重链路,这也是规则把 bootstrap 单独列出的原因。

三、插件系统后端约束:UpdateSource::Plugin与时间戳所有权

这是.claude/rules.md中技术含量最高的部分,三条约束共同回答了同一个问题:插件进程写入数据库时,谁能决定哪些字段。

3.1 数据库写操作必须使用UpdateSource::Plugin

规则原文:Always useUpdateSource::Pluginwhen calling database methods from plugin events。

UpdateSource定义在 crates/yaak-models/src/util.rs,是一个带标签的枚举:

pub enum UpdateSource { Background, Import, Plugin, Sync, Window { label: String }, }

它标明了每次数据变更的来源渠道。在 crates/yaak/src/plugin_events.rs 中,插件事件的UpsertModelRequest与DeleteModelRequest分派逻辑可以完整看到这一约束的落地:无论插件写入的是HttpRequest、GrpcRequest、WebsocketRequest、Folder、Environment还是Workspace,统一走with_tx(|tx| tx.upsert_xxx(m, &UpdateSource::Plugin));删除操作同理,例如delete_http_request_by_id(&req.id, &UpdateSource::Plugin)(crates/yaak/src/plugin_events.rs)。同文件的单元测试也严格遵循:种子数据用UpdateSource::Sync写入,而模拟插件写入时全部显式传入UpdateSource::Plugin,例如upsert_and_delete_model_are_shared_handled测试(crates/yaak/src/plugin_events.rs)。

为什么要区分来源:UpdateSource会直接影响后续的同步(sync)与冲突处理逻辑——来自Sync的写入需要走双向同步管道,来自Plugin的写入则需要被重新广播给同步层。如果 AI 助手在生成插件事件处理代码时漏掉UpdateSource::Plugin或错用UpdateSource::Sync,轻则导致本地变更无法正确上链同步,重则引发数据环回。

3.2 时间戳由 Rust 后端控制,TypeScript 永不发送

规则原文:Never send timestamps (createdAt,updatedAt) from TypeScript - Rust backend controls these。

这条规则的直接技术依据在 crates/common/yaak-database/src/traits.rs 的upsert_date函数:

pub fn upsert_date(update_source: &UpdateSource, dt: NaiveDateTime) -> SimpleExpr { match update_source { UpdateSource::Sync | UpdateSource::Import => { if dt.and_utc().timestamp() == 0 { Utc::now().naive_utc().into() } else { dt.into() } } _ => Utc::now().naive_utc().into(), } }

其语义非常清晰:

  • 对于Sync/Import来源,保留传入的时间戳(仅在时间戳为 0、即未设置时回退为当前 UTC 时间),以保证跨设备同步时原始创建/修改时间不被覆盖;
  • 对于包括Plugin在内的其他来源,一律由后端覆盖为当前 UTC 时间——插件传入的任何时间戳都会被丢弃。

因此从 TypeScript 侧发送createdAt/updatedAt不仅是多余的,还会造成误导:调用方以为时间戳由自己控制,实则后端_ => Utc::now().naive_utc().into()分支会直接忽略它们。正确做法是只携带业务字段(id、name、url等),让 Rust 后端统一生成时间戳。

3.3 后端使用NaiveDateTime(无时区),避免发送 ISO 时间戳字符串

规则原文:Backend usesNaiveDateTime(no timezone) so avoid sending ISO timestamp strings。

在 crates/yaak-models/src/models.rs 中,几乎所有模型的时间字段都声明为NaiveDateTime。以Settings为例:

#[ts(export, export_to = "gen_models.ts")] pub struct Settings { pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, ... }

NaiveDateTime是 chrono 中不带时区信息的时间表示(仅含日期与时刻)。这意味着:

  1. 序列化到 TypeScript 侧时,时间字段的格式与带时区的 ISO 8601 字符串(如2026-09-30T02:27:52Z或带+08:00偏移的形式)并不一致;
  2. 如果插件或前端代码发送 ISO 字符串,后端在反序列化/存储时可能因格式不匹配产生解析偏差或失败;
  3. 仓库内所有时间均由upsert_date统一以Utc::now().naive_utc()形式生成(见 3.2),天然是无时区的 UTC 时刻,前端如需展示本地时间再做转换即可。

实践结论:TypeScript/插件侧应当把时间字段视为"后端私有、只读"的字段——既不发送,也不假设其带时区语义;需要展示时,将其当作 UTC 时刻在 UI 层转换。

四、MCP Server 的上下文模型:没有"活动窗口"概念

规则原文:MCP server has no active window context - cannot callwindow.workspaceId()- Get workspace ID fromworkspaceCtx.yaak.workspace.list()instead。

4.1 为什么 MCP Server 没有活动窗口上下文

MCP Server 插件(plugins-external/mcp-server)是一个独立运行的后台服务。从其入口 plugins-external/mcp-server/src/index.ts 可以看到,插件在init后延迟 5 秒启动服务器,监听环境变量YAAK_PLUGIN_MCP_SERVER_PORT(默认端口64343):

const serverPort = parseInt(process.env.YAAK_PLUGIN_MCP_SERVER_PORT ?? "64343", 10);

它通过 MCP(Model Context Protocol)标准协议与外部 AI 客户端通信,运行时并不依附于某个具体的桌面窗口。而window.workspaceId()这类 API 依赖"当前活动窗口"的 UI 状态——在无窗口的后台进程中,该调用无法可靠返回(可能为空或 undefined)。这就是规则禁止直接调用它的根本原因。

4.2 源码中的正确替代模式:显式解析 workspace 上下文

规则要求的替代路径在 MCP 工具实现中有完整的源码佐证:

  • plugins-external/mcp-server/src/tools/workspace.ts 的list_workspaces工具直接调用ctx.yaak.workspace.list(),返回当前打开的全部 workspace 及其 ID;
  • plugins-external/mcp-server/src/tools/helpers.ts 中的getWorkspaceContext是核心辅助函数:先ctx.yaak.workspace.list()拿到 workspace 列表,若用户未指定且存在多个 workspace 则抛出带编号清单的错误提示,最后通过ctx.yaak.workspace.withContext(workspace)构造出带 workspace 上下文的引用:
    const workspaces = await ctx.yaak.workspace.list(); ... return { yaak: ctx.yaak.workspace.withContext(workspace) };
  • 基于该上下文,plugins-external/mcp-server/src/tools/window.ts 的get_workspace_id/get_environment_id工具才得以工作——它们在getWorkspaceContext返回的workspaceCtx上调用workspaceCtx.yaak.window.workspaceId()/workspaceCtx.yaak.window.environmentId()。

由此可以看到这条规则的精髓:MCP 场景下不要依赖隐式的"活动窗口",而要用 workspace 列表显式确定目标上下文(必要时让用户从列表中选择)。AI 助手在编写或审查 MCP 工具时,应优先复用getWorkspaceContext模式,而不是凭空假设存在活动窗口。

五、Rust 类型生成:cargo test驱动 TypeScript bindings 再生成

规则原文:Runcargo test --package yaak-plugins(and for other crates) to regenerate TypeScript bindings after modifying Rust event types。

5.1 机制:ts-rs 的测试期导出

Yaak 使用ts-rs库在 Rust 侧生成 TypeScript 类型声明。在 crates/yaak-plugins/src/events.rs 与 crates/yaak-plugins/src/api.rs 中,所有需要暴露给前端的 Rust 类型都标注了#[ts(export, export_to = "gen_events.ts")]、#[ts(export, export_to = "gen_api.ts")]等属性:

#[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export, export_to = "gen_events.ts")] pub struct InternalEvent { ... }

ts_rs的export属性会在cargo test 执行期间触发文件写出,生成到 crates/yaak-plugins/bindings 目录下(gen_events.ts、gen_api.ts、gen_models.ts、gen_search.ts等)。随后 crates/yaak-plugins/index.ts 通过export * from "./bindings/gen_events"等语句把这些类型重新导出,供@yaakapp/api与前端消费。

5.2 为什么要用cargo test触发

这正是.claude/rules.md强调"修改 Rust 事件类型后必须运行cargo test --package yaak-plugins"的原因:只有运行测试,bindings 文件才会被重新生成。如果 AI 助手改动了crates/yaak-plugins中的事件结构(比如给某个InternalEventPayload变体增加字段),却忘记运行对应 crate 的测试,那么 crates/yaak-plugins/bindings 下的gen_*.ts仍是旧类型,TypeScript 侧会出现类型不匹配或缺失字段的编译错误,而且这类错误极难定位(错误发生在跨语言边界的另一端)。

同理,规则括号中的and for other crates说明:凡是用#[ts(export, ...)]声明了 bindings 的 crate(如 crates/yaak-models、crates/yaak-plugins 等,可在各 crate 的bindings/目录确认),改动其导出类型后都要跑对应 crate 的cargo test来刷新 bindings。作为快速自查手段,修改后应检查git status中bindings/gen_*.ts是否出现了预期的 diff。

六、实践落地:AI 协作修改的检查清单

综合.claude/rules.md全部条目,一份可直接执行的自检清单如下:

修改类型必做动作对应规则/源码
任意代码修改不 commit、不 push,等待显式确认.claude/rules.md、AGENTS.md
修改 TS/JS 文件npm run lint(含lint:vp+ 各 workspace lint)package.json
修改插件运行时 / MCP Server 代码npm run bootstrap(wasm-pack 安装 → 构建 → vendor)package.json、scripts/install-wasm-pack.cjs
编写插件事件中的数据库写入一律传UpdateSource::Plugin,绝不使用其他来源crates/yaak/src/plugin_events.rs
编写插件/前端数据写入不发送createdAt/updatedAt,不发送 ISO 时间字符串crates/common/yaak-database/src/traits.rs
编写 MCP 工具不用window.workspaceId(),改用workspace.list()显式解析上下文plugins-external/mcp-server/src/tools/helpers.ts、plugins-external/mcp-server/src/tools/workspace.ts
修改 Rust 事件/模型类型运行cargo test --package <对应 crate>重新生成 bindingscrates/yaak-plugins/src/events.rs、crates/yaak-plugins/index.ts

七、结语

.claude/rules.md虽然只有二十余行,却精准覆盖了 Yaak 这类"Rust 内核 + TypeScript 前端 + 插件生态"混合工程中 AI 协作的六大高风险区。它不是空泛的行为准则,而是可以从源码中逐条验证的工程约束:UpdateSource::Plugin在 crates/yaak/src/plugin_events.rs 中有完整的调用链与测试佐证;时间戳策略在 crates/common/yaak-database/src/traits.rs 的upsert_date中有明确的来源分支;MCP 上下文约束在 plugins-external/mcp-server/src/tools/helpers.ts 中有标准的替代实现;bindings 再生成则由 ts-rs 的测试期导出机制驱动。对于任何参与 Yaak 开发(尤其是通过 AI 助手提交代码)的工程师,把这七条规则内化为肌肉记忆,是避免跨语言、跨进程边界的隐性 bug 最有效的途径。

  • 开发工具
  • 接口测试
  • 桌面应用

【免费下载链接】yaak

The most intuitive desktop API client. Organize and execute REST, GraphQL, WebSockets, Server Sent Events, and gRPC 🦬

项目地址:https://gitcode.com/GitHub_Trending/ya/yaak
点击查看免费下载

相关推荐

上一篇:推荐文章:轻松管理预约,Open Source Doctor Appointment Booking System —— 您的线上医疗服务助手
下一篇:深入理解 Elasticsearch 读写原理:从协调节点路由到 Lucene 倒排索引

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 1:53:04

C++控制台小游戏实战:贪吃蛇、扫雷、2048从环境配置到完整实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:52:01

nginx部署vue包dist,页面刷新后,提示“404 Not Found”

nginx-1.9.10错误截图如下&#xff1a;处理方法&#xff1a;在指定的位置添加下面代码&#xff0c;即可&#xff1b;location / {root E:/workspace/dists/dist;index index.html index.htm;# 解决页面刷新后&#xff0c;报404的问题try_files $uri $uri/ /index.html;}重启…

作者头像 李华
网站建设 2026/10/1 1:51:14

【Excel】零碎技能积累

文章目录1.INDIRECT跨表引用2.常用功能快速使用&#xff08;快捷键&#xff09;3.excel数据透视表&#xff0c;非重复计数4.透视表计算字段、计算项5.条件格式&#xff0c;A列是条件&#xff0c;B列因A列条件显示格式6.快速全表替换某一范围的数7.批量合并单元格8.快速拆分单元…

作者头像 李华
网站建设 2026/10/1 1:51:12

USB蓝牙适配器Linux不识别?CM591/ATS2851内核与BlueZ排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:50:52

AAA级武士角色纹理制作全流程:PBR工作流与Substance Painter实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华