Airi 单体仓库 Agent 协作指南:架构地图、工程规范与跨端开发工作流

发布时间:2026/9/12 16:23:48
Airi 单体仓库 Agent 协作指南:架构地图、工程规范与跨端开发工作流
Airi 单体仓库 Agent 协作指南架构地图、工程规范与跨端开发工作流【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airiAiri 是一个同时覆盖桌面端Electron、Web 与移动端Capacitor的跨端 monorepo本文以仓库根目录的 AGENTS.md 为主线面向在该仓库中工作的贡献者与 AI Agent系统梳理其技术栈分层、目录职责、pnpm 工作流命令、强制 Skill 体系以及贯穿全部 TypeScript 代码的类型安全、IPC、模块设计与可读性规范。读完本文你将掌握如何在 Airi monorepo 中快速定位代码、正确执行类型检查/测试/构建并写出符合仓库约定、可被持续维护的代码。技术栈总览按端侧分层的工程选型AGENTS.md 首先按端侧surface划分了技术栈这是理解整个仓库的第一个关键视角不同端共享同一套前端技术底座但各自的运行时与打包方式不同。端侧目录核心技术栈桌面端apps/stage-tamagotchiElectron、Vue、Vite、TypeScript、Pinia、VueUse、EventaIPC/RPC、UnoCSS、Vitest、ESLintWeb 端apps/stage-webVue 3 Vue Router、Vite、TypeScript、Pinia、VueUse、UnoCSS、Vitest、ESLint后端为 WIP移动端apps/stage-pocketVue 3 Vue Router、Vite、TypeScript、Pinia、VueUse、UnoCSS、Vitest、ESLint、Kotlin、Swift、Capacitor值得注意的选型要点IPC/RPC 统一走 Eventamoeru/eventa这是一个类型安全、框架/运行时无关的 IPC/RPC 方案契约定义集中在apps/stage-tamagotchi/src/shared主进程/渲染进程的接入模式参考apps/stage-tamagotchi/src/main/services/electron。依赖注入统一用 injeca在apps/stage-tamagotchi/src/main/index.ts中可以看到完整的组合模式injeca.setLogger(...)配置日志、injeca.provide(configs:app, () createGlobalAppConfig())等注册方式逐层提供 app 配置、artistry 配置与 Electron 宿主实例。样式优先 UnoCSS 而非 Tailwind全局配置位于根目录uno.config.ts动画参考apps/stage-web/src/styles。历史上桌面端曾基于 Tauricrates/为旧目录当前桌面端是 Electron这一点在阅读遗留代码时尤其重要。UI/共享包的分层谁拥有业务逻辑共享逻辑集中在packages/下AGENTS.md 明确给出了每个包的分工边界packages/stage-ui核心业务组件、composables、stores被 stage-web 与 stage-tamagotchi 共享是舞台工作的心脏。packages/stage-ui-threeThree.js 绑定 Vue 组件。packages/stage-ui-pixi规划中的 Pixi 绑定尚未实现。packages/stage-shared跨 stage-ui、stage-ui-three、stage-web、stage-tamagotchi 的共享逻辑。packages/ui基于 reka-ui 构建的标准原语输入框、textarea、按钮、布局业务逻辑最少化。packages/i18n集中式翻译避免 i18n 散落在各个 app/package。服务端通道packages/server-runtime、packages/server-sdk、packages/server-shared支撑services/与plugins/。从源码结构看packages/stage-ui/src/stores内部进一步分层为providers/标准化 provider 定义、modules/AIRI 编排构建块、chat/、character/、settings/、ai/等子目录modules/下可以看到hearing.ts、speech.ts、consciousness.ts、artistry.ts、gaming-minecraft.ts、gaming-factorio.ts、web-search.ts、discord.ts、twitter.ts等按能力划分的编排模块且每个模块几乎都带有配套测试文件如speech.test.ts、hearing.test.ts、consciousness.test.ts印证了模块即领域能力、测试即行为契约的组织思路。仓库结构与职责托管后端、Apps 与共享页面托管后端server/server/apps/apiHono 资源 API 与业务领域。server/apps/auth独立的 Better Auth 与 OIDC 服务。server/packages后端私有的 schema 与 Node 基础设施包。server/dev/caddy仅本地使用的 Auth/API 边缘路由。server/docker-compose.yaml完整的本地后端栈。Apps 的目录约定apps/stage-webWeb 应用。composables/stores 在src/composables、src/stores页面在src/pagesdevtools 在src/pages/devtools路由配置在vite.config.ts。apps/stage-tamagotchiElectron 应用。渲染进程页面在src/renderer/pagesdevtools 在src/renderer/pages/devtools设置布局在src/renderer/layouts/settings.vue路由配置在electron.vite.config.ts。设置/devtools 路由依赖route langyaml meta: layout: settings /route声明因此新增路由/图标时必须同步注册到 桌面端设置布局 与 Web 设置布局。共享页面基座位于packages/stage-pages各端自有页面放在各自的src/pages含 devtools 目录。Stage UI 内部packages/stage-ui/srcProvidersstores/providers.ts与stores/providers/提供标准化的 provider 定义。Modulesstores/modules/提供 AIRI 编排构建块。Composablescomposables/提供面向业务的 Vue 辅助函数如use-airi-runtime-prompt.ts、use-analytics.ts、use-duck-db.ts等且大多带.test.ts。Componentscomponents/存放业务组件components/scenarios/存放页面/用例专属片段。Storiespackages/stage-ui/stories与packages/stage-ui/histoire.config.ts例如components/misc/Button.story.vue。关键路径索引快速定位什么东西在哪AGENTS.md 给出了一张路径速查表直接抄录并补充现状核对如下packages/stage-ui核心舞台业务组件/composables/stores。packages/stage-ui-threeThree.js 绑定 Vue 组件。packages/stage-shared跨包共享逻辑。packages/ui基于 reka-ui 的标准原语。packages/i18n全部翻译。托管后端server/apps/api、server/apps/auth、server/packages本地工具在server/dev。服务端通道packages/server-runtime、packages/server-sdk、packages/server-shared支撑services/与plugins/。遗留桌面端crates/旧 TauriElectron 为现行方案。页面packages/stage-pages共享基座各 app 的src/pagesdevtools 位于各 app 的.../pages/devtools。路由配置apps/stage-web/vite.config.ts、apps/stage-tamagotchi/electron.vite.config.ts。IPC/Eventa 契约与示例apps/stage-tamagotchi/src/shared、apps/stage-tamagotchi/src/main/services/electron。DI 示例apps/stage-tamagotchi/src/main/index.tsinjeca。样式uno.config.tsUnoCSS、apps/stage-web/src/styles动画参考。构建流水线.github/workflowslint 规则在eslint.config.ts。已记录的解决方案docs/solutions/按类别组织并带 YAML frontmattermodule、tags、problem_type在相关区域实现、调试或验证时应当查阅。常用命令pnpm workspace filter 的正确用法核心原则使用 pnpm workspace filter 限定任务范围。下面示例中的 filter 需替换为目标 workspace 名称例如proj-airi/stage-tamagotchi、proj-airi/stage-web、proj-airi/stage-ui等已在各package.json的name字段确认。Typecheckpnpm -F package.json name typecheck示例pnpm -F proj-airi/stage-tamagotchi typecheck内部依次运行tscvue-tsc。单元测试Vitest定向运行单个文件pnpm exec vitest run path/to/file例如pnpm exec vitest run apps/stage-tamagotchi/src/renderer/stores/tools/builtin/widgets.test.ts限定 workspace 运行pnpm -F package.json name exec vitest run例如pnpm -F proj-airi/stage-tamagotchi exec vitest run根目录pnpm test:run运行所有已注册项目的全部测试若没有发现测试请检查根 vitest.config.ts 的 include 模式。根配置包含apps/stage-tamagotchi等项目且每个 app/package 可以有自己的vitest.config。Lintpnpm lint pnpm lint:fix格式化由 ESLint 负责pnpm lint:fix会同时应用格式化。Buildpnpm -F package.json name build示例pnpm -F proj-airi/stage-tamagotchi buildtypecheck electron-vite build。强制 Skill 体系Agent 工作流的入口约定AGENTS.md 明确要求在执行特定类型任务时必须调用对应的仓库内 Skill均已确认存在于.agents/skills/下测试相关Vitest、回归复现、mocks、测试导入边界一律使用 enforce-rules-for-vitest。UnoCSS、Vue 样式、UI 组件、动画、图标或 color-mode 工作一律使用 enforce-rules-for-unocss。Web/Electron 中通过 HTML input、动态创建的 input 或文件选择器上传本地文件调用 use-agent-browser-with-input-file并配套调用$agent-browser目标是 Electron 时再调用$agent-browser-electron。AIRI Live2D、VRM、MMD 的导入与渲染测试跨 stage-web、stage-tamagotchi、stage-pocket调用 use-agent-browser-for-airi它内部会复用上传机制并补充 AIRI 专属路由、状态准备、格式行为与渲染器验证。编辑、编写、重构代码提交 issue、Pull Request 与文档注释调用 simple-english。开发实践模块边界、校验、IPC 与错误处理偏好清晰的模块边界共享逻辑进packages/。保持运行时入口精简重逻辑移入 services/modules——这与apps/stage-tamagotchi/src/main/index.ts中入口只做 wiring、具体能力由 services 承担的写法一致。使用Valibot做 schema 校验schema 尽量贴近消费方。需要结构化 IPC/RPC 契约时使用Eventamoeru/eventa。错误消息统一用errorMessageFrom(error)来自moeru/std提取而不是手写error instanceof Error ? error.message : String(error)需要默认值时配合?? fallback。该 API 在仓库中已有实际使用如packages/server-runtime/src/index.ts中的import { errorMessageFrom } from moeru/std。不添加向后兼容守卫如需扩展支持应写重构文档并另起 Codex/Claude Code 实例按明确指令完成实现。小规模重构则渐进式、分步进行。涉及node:*内置模块、DOM 操作、Vue composables、React hooks、Vite 插件或 GitHub Actions 工作流的新需求必须先做深度调研选择合适库选择库之前必须让用户确认禁止擅自选用通用工具库如es-toolkit、unjs下的工具等。若用户采用 spec 驱动用简洁的 Markdown 对比表列出候选。TypeScript / IPC / 工具规范JSON Schema 必须符合 provider 约定显式type: object、必填字段避免无界 record。Electron 与后端相关包使用 injeca 管理依赖除非是扩展浏览器 API否则避免新的类层级类更难 mock 与测试。Eventa 契约集中化所有事件一律走moeru/eventa。类型从拥有契约的模块/包导入不要在本地重声明外部/公开契约也不要绕道本地运行时装配模块转发类型导入。相对导入省略 TS/JS 扩展名写./module而非./module.ts仅当运行时或资源格式要求时才保留扩展名。不要为消除类型错误而直接改tsconfig.json应先调查编译行为、package.json的exports声明、类型声明以及依赖暴露的 browser/node 入口。Node-only 与 browser-only 类型混在一条导入链时把类型声明拆分到中性类型文件保持运行时模块按环境隔离不要为了取类型而导入带副作用的模块。导入/导出缺失报错时先追溯完整导入链与副作用链优先修复包/模块的导出与所属边界而不是在叶子处加本地 workaround。把循环导入视为设计问题出现环时先重新考虑所有权、模块边界以及共享类型/纯辅助函数是否该移动无法自信解决时先询问用户。用户要求使用某工具/依赖时先查 Context7 文档再检查该依赖在仓库中的实际用法Context7 返回多个难以区分的结果时请用户选择确认文档与 typecheck 冲突时检查node_modules下依赖源码定位根因。i18n集中翻译与术语表翻译统一加/改在packages/i18n避免散落。默认只修改英文源语言与当前开发者使用的语言其他语言文件由 Crowdin 集成管理直接本地修改可能在下次 Crowdin 上传/同步后被未翻译源内容覆盖。术语表packages/i18n/glossary/terms.yaml为每个产品概念提供已批准的英文术语pnpm -F proj-airi/i18n glossary:build生成 Crowdin 导入的 TBX 文件schema.ts记录每个字段。写用户可见字符串前先读terms.yaml不要编辑packages/i18n/glossary/translations/会被 Crowdin 下载覆盖。术语条的添加判定遵循两条规则仅当只读源字符串的译者会选错词或两位译者可能选两个都正确的不同词时才新增代码控制 token、JSON key、文件名、包名、普通英语词如 Speed/Volume、各部分已各有词条的组合如 VRM model、以及低歧义的一次性功能如仅出现两次的Tachie例外都不应添加。定义必须一句话写完描述事物而非单词负面规则放在note字段每条术语的取舍理由写在 PR 说明里因为terms.yaml的每个字段都要映射到 TBX 元素不能承载 rationale。可读性、命名与注释命名所有文件名使用 kebab-case。让模块边界提供上下文避免在符号里重复包名/产品名/协议名/传输名除非跨边界后上下文会丢失。函数按领域操作命名而非实现层已解析的领域概念用名词变换或副作用用动词。如果一个符号需要多个所有权限定词才能看懂重新考虑模块边界或引入更清晰的领域概念。注释注释只解释代码本身无法清晰表达的信息意图、约束、所有权、不变量、优先级、生命周期、顺序、副作用、协议形状、非显然的 fallback。不要写复述名称/类型/可见操作的注释。要点包括契约注释解释生产者与消费者之间的关系先解释值为什么存在再解释代码如何表示它。值跨越模块/组件边界时指出消费者及其应用方式表示细节单位、坐标系、阈值、clamp、来源 API 字段放在行为之后背景证据浏览器行为、issue 链接、调查历史、移除条件放在契约之后。计算密集的代码在相关中间值/分支旁解释坐标系、单位、转换、clamp、舍入、聚合与优先级。调查型注释写成短段落上下文、观察到的失败、为什么明显修法不够、选择的修复、移除条件。使用标记// TODO:后续工作、// REVIEW:需要他人意见、// NOTICE:workaround、魔法值、外部约束等非显然上下文。Fallback 与优先级超过两个来源的 fallback 链必须显式表达优先级不同 schema 版本、兼容性行为、特异性级别、用户/系统覆盖的每个非主分支都要解释存在原因与优先级。避免嵌套三元做非显然 fallback改用命名中间变量或if/else if。不要随手用新对象/数组做 fallbackvalue ?? {}、value ?? []等每次都会创建新引用严禁在响应式 getter、computed、watcher source 或 Pinia state 投影中使用内联对象/数组 fallback——新引用会引发假变更、watcher 循环和状态广播。若不可变空 fallback 有效复用稳定模块级值消费者不可变更时用freeze。??仅在null/undefined表示缺失时使用||仅在false、0、空串也要触发 fallback 时使用。临时 fallback 用// NOTICE:标记并注明移除条件永久 fallback 作为受支持策略写进文档而不是称之为 legacy。有状态与协议代码实现协议、状态机、生命周期、缓存、请求/响应流、事件路由、watcher、session、cookie 或清理序列的代码要在实现附近记录状态模型并区分持久化配置、发现的文件系统状态、运行时加载状态、缓存状态、session/cookie 状态、watcher 状态与外部副作用。形如setEnabled、load、unload、dispose、start、stop、refresh的状态转换方法若从所属类型/模块看不显然要说明改变的是哪个状态。匹配事件/响应时显式记录关联键与隔离规则如requestId、sessionId、ownerExtensionId、bindingId、路由命名空间、源窗口。事件处理器必须让被忽略的事件可理解因路由不匹配、属主不匹配、request id 过期、生命周期已释放或来源错误而忽略时原因要在代码中可见或用命名谓词捕获。请求/响应流在生产者与消费者附近定义/命名信封形状说明超时、关闭、unload、dispose 与发布失败时 pending 请求的处置。返回快照/fallback/陈旧值/缓存值时在返回处说明新鲜度语义watcher、事件监听器与异步后台工作要显式说明所有权与关闭行为。Pinia 跨窗口同步AGENTS.md 对多窗口状态同步给出了非常具体的约束这是 Electron 应用特有的难点将pinia-plugin-synced视为快照复制 leader 路由的 RPC它并不在渲染器之间共享 Vue ref。只给需要跨窗口所有权的 store 添加synced同步最小可序列化的 source-of-truth 状态state: true会在每次本地变更后发送完整 store 提案因此瞬态/高频状态要放在未同步的 store 中。state、action 参数与 action 返回值必须支持structuredClonecomputed、查询状态、运行时客户端、控制器、pending promise 与组件状态不能放进同步状态。远端快照会触发本地 Vue watcherwatcher 不能直接写同步状态。watcher 可以通过调用同步 action 来强制 leader 拥有的不变量但必须await该 action且 action 必须是幂等的每个渲染器可能观察到同一快照。跨字段不变量在显式 action 内、状态提交前强制不要用 watcher 修复被复制的状态。setup store 中每个返回函数都是 Pinia action只读投影用 computed 或纯辅助函数。只有 leader 拥有的带副作用 action 列在synced.actions下这些 action 必须是异步的调用方必须await。未列出的 action 在调用方渲染器执行其变更在state: true时成为全量提案。同步与持久化保持独立边界持久化的同步状态只能有一个显式持久化属主不要给同步状态加双向持久化 composable 或 storage-event 监听器改用显式持久化命令。每个 Electron 渲染器都要显式设置 leadership 模式工具窗口与最小窗口必须使用follower-only。同步变更必须加多窗口回归测试远端快照不得产生本地同步状态提案若 watcher 调用同步 action验证重复调用收敛且无重复副作用。模块设计深模块优于浅模块深模块隐藏有意义决策策略、持久化边界、协议/schema 契约、调度语义、模型 prompt 契约、领域不变量或生命周期关注点。不要只按执行顺序切分代码模块边界应代表可独立理解的稳定职责。200–400 行的内聚模块优于多个互相传递同一份 context/options 的浅模块。运行时/浏览器 API 与拥有状态、生命周期或稳定领域边界的大型业务模块偏向类纯变换与本地辅助函数偏向函数。依赖注入只在外部边界使用数据库、模型运行时、队列、缓存、文件系统、网络、时钟、环境、feature gate。内部函数调用兄弟 helper 或转发参数时不要引入依赖对象。新建createXService/XDependencies前先验证 X 是否真的增加了策略、校验、状态、重试/错误处理、IO 边界或可复用抽象否则保留为私有 helper 或内联。避免createXService({ yService })这种不增加任何价值的透传服务。特殊 case 就近放在其影响的分支旁测试走稳定的公开行为不要仅为可 mock 而新增导出、依赖袋或包装服务。PR 与工作流技巧创建/打开/发布/准备 PR 时一律使用仓库内create-prskill.agents/skills/create-pr/SKILL.md对用户可见的变更它会编排use-vishot及对应运行时变体并把 before/after 截图作为 GitHub user assets 上传到 PR body。创建 PR 后要跟进 review threads、评论与检查状态确认的错误要修复、跑定向检查、推送更新、附证据回复并 resolve 线程。Rebase 式拉取分支命名username/feat/short-name提交信息清晰禁止 gitmoji。总结变更、如何测试命令与后续事项改善你碰到的遗留代码避免一次性模式保持变更范围小使用 workspace filterpnpm -F package script。每个packages/与apps/条目维护结构化README.md说明做什么、怎么用、何时用、何时不用。完成任务后务必运行pnpm typecheck与pnpm lint。提交信息使用 Conventional Commits如feat(package name): add runner reconnect backoff。规划/编写新工具函数前先搜索仓库内已有实现可成为共享工具的逻辑应主动向用户/开发者提议共享方案。TypeScript 编码法规全仓库通用底线实施期间不创建 commit。已实现模块尽量用 Vitest 验证行为与测试通过。每个 workaround 必须使用// NOTICE:格式包含为什么需要该 workaround、根因摘要、来源/上下文文件、issue、URL 或 node_modules 引用、移除条件何时可安全删除。优先使用泛型不使用any仅在几乎不可避免且类型无法安全修复时使用as unknown as 目标类型。公开 API、包级导出、共享架构边界、非平凡的导出函数/类/类型要写 JSDoc只记录签名无法表达的内容假设、副作用、生命周期、返回保证普通 helper、局部投影、透传函数不加 JSDoc避免用固定小节模板复述名称与签名。不要仅为满足测试或文档规则而导出 helper生产代码未复用的实现 helper 保持私有。导出的测试 helper 或非显然的可复用测试夹具在能说明用途时加example不要给普通describe、it、expect*挂 JSDoc。导出的 interface/type alias顶层 JSDoc 聚焦类型代表什么详细语义放相关字段泛型参数用param说明每个有默认值的选项都要加default。runner/CLI 入口的 JSDoc 必须包含清晰的 ASCII 调用栈图用{link ...}引用server 编排器仅在能澄清稳定架构边界时添加不用于浅层胶水代码。调用栈段落格式/** * ... * * Call stack: * * collectEvalEntries (../runner) * - {link createRunnerSchedule} * - {link createMatrixCombinations} * - {link VievalScheduledTask}[] */非显然的 OS、exec、process、参数、网络、文件/目录处理要在相关代码附近解释约束或目的。工具函数优先es-toolkit错误处理优先moeru/std模式。导出的 normalizer、共享 normalizer 或非显然的本地 normalizer对输出、格式、文件名或值做标准化不含 config 默认值标准化要加 JSDoc 与example格式为normalizeTarget(ExampleInput) // example-output。不要把所有东西都挪进常量只用一两次的常量就近放通常在 import 之后顶部配/** ... */说明原因。可配置默认值优先用moeru/std的 merge 函数并把默认值写成文档化对象重试、退避、限额值不要用单个常量包打天下。避免硬编码 Unix/macOS/Windows 路径字面量优先路径安全的数组参数与跨平台处理。测试不要只依赖 smoke 测试先复现 bug/失败再打补丁注释保留根因与修复理由。回归测试用 ROOT CAUSE 块格式什么情况下发生、为什么定位到行、补丁前行为、如何修复、补丁后行为。不要用之类的分隔符切分模块用内聚的私有 helper 分组或仅在模块拥有独立职责时拆分。不要仅为了减少嵌套、行数或制造测试接缝而拆文件。不要过度使用 table-driven多数情况下内联 table 数组直接.map(...)。偏好提前 return、保持函数简单不要为了减缩进引入透传 helper 或浅模块。可读性 Review 检查清单Review 复杂 TypeScript 模块时检查四点所有状态/副作用/生命周期转换/清理/新鲜度语义是否无需追溯多个邻近文件即可识别协议信封、关联键、隔离规则与 fallback 优先级是否在决策点显式呈现模块/helper 边界是否隐藏了有意义策略而非转发上下文或掩盖特殊 case注释是否在相关代码旁解释非显然决策而不是复述名称、类型或可见操作。结语把 AGENTS.md 当作协作契约Airi monorepo 的 AGENTS.md 不只是给人类贡献者的指南更是给 AI Agent 的协作契约从技术栈分层、目录职责、路径索引到 pnpm 命令、强制 Skill、TypeScript 法规、i18n 术语表、Pinia 跨窗口同步与 PR 工作流它把在这个仓库里如何正确地干活沉淀成可执行规则。对刚接触该仓库的开发者按本文梳理的路径先看技术栈表 → 对照 Key Path Index → 用pnpm -F跑定向检查 → 遵循 Skill 与编码法规即可快速进入状态对已有经验的贡献者AGENTS.md 中关于深模块、fallback 优先级与同步 store 的约束则是最值得反复对照的长期约定。所有规范最终都指向同一目标让跨 Web、桌面与移动端的代码保持可读、可测、可长期演进。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考