项目团队从 5 人扩到 15 人,我写了个 CLI 让 IDE 共享 AI 规则

AI IDE横行的今天,好像每个团队都在谈怎么写好 AI 规则。

笔者所在的小团队开发着一个创新项目,组内同学混用着 AI IDE(IDE 矩阵:Cursor + CodeBuddy + Windsurf + Trae + Qoder,每个新同学按自己喜好挑一个),沉淀了不少项目级 AI 上下文(TypeScript规范、React组件约定、MCP Server清单),随着项目成员的增加,问题就来了------

团队新人: 哥,我们项目用什么 AI 规则啊?

我: 写在 .cursorrules 里......

新人: 可是我用的是 CodeBuddy 啊。

我: ......那你 copy 一份到 .codebuddy/rules/

新人: 以后每次更新规则呢?

我: ......群里通知一下?

好家伙,这活儿干得比prettier还没排面。于是就有了 xingwu-ai-kit------一个让"一份配置同步到 5 个 IDE"的 CLI。

搞事1.0:先跑起来

笔者写项目有个臭毛病,喜欢先把最丑的版本跑起来再说,反正写完肯定要重写一版(笔者不喜欢重复以往的方案,喜欢用点新玩意儿)。

最简版本核心逻辑就三行------读配置、读模板、writeFileSync 写到 IDE 目录:

typescript 复制代码
// 第一版核心逻辑:写文件
for (const ide of config.ide) {
  for (const preset of config.rules.preset) {
    const content = readFileSync(getRuleTemplatePath(preset), 'utf-8')
    writeFileSync(resolve(cwd, adapters[ide].rulesDir, `${preset}.mdc`), content)
  }
}

init 出来一份配置,sync 一下,CodeBuddy / Cursor 目录里就有了规则文件。

嗯,能用,先交差。

团队第一次升级规范的时候,麻烦来了------

同事 A: 我看 common.mdc 里没写"禁止无脑可选链",能加上吗?

我: 改了。

同事 B: 我这边还是旧版啊?

我: 你跑一下 npx xingwu-ai-kit sync

同事 C: 我跑完之后 IDE 里的规则文件被覆盖了,我加的注释没了!

第一个问题是模板升级要全员重跑 sync ,第二个问题是writeFileSync会冲掉用户本地改动

于是乎,把 writeFileSync 改成 symlinkSync

typescript 复制代码
// 第二版:symlink,让 IDE 目录指向模板
symlinkSync(getRuleTemplatePath(preset), resolve(rulesDir, `${preset}.mdc'))

跑通之后立刻香了:

  • 改模板 → 升级 xingwu-ai-kit → 所有 IDE 立刻生效,不用重跑 sync
  • 用户本地改 .cursorrules 不再被冲,它就是项目专属文件

但好景不长,又冒出来两个新问题:

  1. symlink 嵌套 ------之前残留的旧 symlink 没清,会出现 common.mdc -> template.mdc -> 真实文件 这种链中链
  2. 断链 ------pnpm install 重新拉 node_modules 之后所有 symlink 全指向空气

safeLink 处理第一个:

typescript 复制代码
// packages/cli/src/core/linker.ts
if (stat.isSymbolicLink()) {
  // 已有 symlink → 先删再创建,避免嵌套
  unlinkSync(target)
} else {
  // 真实文件 → 尊重用户,按 overrideStrategy 决定
  if (overrideStrategy === 'skip') {
    return { status: 'skipped', reason: '项目专属文件,跳过' }
  } else if (overrideStrategy === 'override') {
    unlinkSync(target)
  }
}

doctor --fix 处理第二个:

bash 复制代码
npx xingwu-ai-kit doctor --fix

doctor 会扫所有 IDE 目录里的断链,按需清掉,再报告修了几条:

text 复制代码
━━━ Cursor ━━━
  💔 [broken] symlink 指向不存在的路径
     路径: .cursor/rules/common.mdc
  🔧 修复中...
  🗑️  移除断链: common.mdc
  ✅ 已修复 1 个问题

嗯,能拖动、能折叠、能兜底,这回够用了吧。

搞事2.0:JSDoc 配置拯救 npx / 全局安装

某天同事抛了个问题:

同事: 我在自己笔记本上不想每个项目都装 xingwu-ai-kit,能不能全局装一个?

我: 可以啊,pnpm add -g xingwu-ai-kit 就行。

同事: 那我跑 init 生成的 xingwu-ai-kit.config.ts 报错了,说找不到 xingwu-ai-kit/config 这个包。

我: ......

看了一眼生成的配置,长这样:

typescript 复制代码
// ❌ 第一版:依赖外部包,全局安装就 GG
import { defineConfig } from 'xingwu-ai-kit/config'
export default defineConfig({ ... })

init 把类型定义写成了 import,导致配置文件的运行时依赖xingwu-ai-kit 这个包的存在。

项目里没装 → 报错。全局装 → 项目里还是没装 → 同样报错。npx 临时调用 → 更没装。

解决办法:放弃运行时类型,让 import 退化成"纯类型注释":

typescript 复制代码
// ✅ 改版:JSDoc 类型注解,运行时零依赖
/** @type {import('xingwu-ai-kit/config').AIKitConfig} */
export default { ... }

注释里的 import('xingwu-ai-kit/config') 只在 TypeScript 类型检查 / 编辑器里生效 ,不会真的去 require 这个包。

所以无论项目内安装、全局安装、npx 临时使用,生成的配置文件都能直接跑。

defineConfig 仍然保留,给那些项目内安装了 xingwu-ai-kit 的同学留个选择:

typescript 复制代码
import { defineConfig } from 'xingwu-ai-kit/config'  // 需要项目内也装
export default defineConfig({ ... })

好吧好吧,这个改动不大,但解决了"零依赖使用 CLI"的关键一环。

搞事2.1:c12 + defu 的"数组拼接"暗坑

这一节是笔者踩过最"阴"的一个坑,写出来给大家提个醒。

xingwu-ai-kit 用了 c12 加载 xingwu-ai-kit.config.ts,又用 defu 合并默认值。第一版配置加载长这样:

typescript 复制代码
import { loadConfig } from 'c12'

const DEFAULT_CONFIG = {
  ide: ['codebuddy', 'cursor'],
  rules: { preset: [], overrideStrategy: 'skip' },
  // ...
}

const { config } = await loadConfig({
  name: 'xingwu-ai-kit',
  cwd,
  defaults: DEFAULT_CONFIG,  // ← c12 内部用 defu 合并
})

sync --dry-run,输出把我看懵了------

text 复制代码
━━━ CodeBuddy ━━━
  ✅ common.mdc
  ✅ typescript.mdc
  ✅ security.mdc
━━━ Cursor ━━━
  ✅ common.mdc
  ✅ typescript.mdc
  ✅ security.mdc
━━━ CodeBuddy ━━━   ← 怎么又来一遍?
  ✅ common.mdc
  ✅ typescript.mdc
  ✅ security.mdc
━━━ Cursor ━━━       ← ???
  ✅ common.mdc
  ✅ typescript.mdc
  ✅ security.mdc

✨ 同步完成!共 12 条规则、0 个技能

ide 数组被遍历了 4 次,common.mdc 被处理了 4 次------说好 2 个 IDE,怎么冒出 4 个?

查了一会儿 defu 文档,原因藏在它的默认合并策略里------defu 对数组字段的默认行为是"拼接"而不是"覆盖"

typescript 复制代码
// 用户配置
{ ide: ['codebuddy', 'cursor'] }

// 默认配置(也是 codebuddy, cursor)
{ ide: ['codebuddy', 'cursor'] }

// defu 默认合并
{ ide: ['codebuddy', 'cursor', 'codebuddy', 'cursor'] }  // 长度变 4!

解决思路:放弃 c12 的 defaults 选项,自己控制合并逻辑 。用 createDefu 写一个自定义 merger,对数组字段采用"覆盖":

typescript 复制代码
// packages/cli/src/core/config-loader.ts
import { createDefu } from 'defu'

// 自定义 defu merger:数组字段用更高优先级的 source 覆盖,不拼接
const defuOverrideArrays = createDefu((obj, key, value) => {
  if (Array.isArray(value)) {
    obj[key] = value
    return true
  }
  return undefined
})

export async function loadAIKitConfig(cwd?: string) {
  const { config } = await loadConfig<AIKitConfig>({
    name: 'xingwu-ai-kit',
    cwd: cwd || process.cwd(),
    // 注意:这里故意不传 defaults
  })
  const userConfig = (config || {}) as AIKitConfig
  return defuOverrideArrays(userConfig, DEFAULT_CONFIG) as AIKitConfig
}

改完跑 sync --dry-run

text 复制代码
━━━ CodeBuddy ━━━
  ✅ common.mdc
  ✅ typescript.mdc
  ✅ security.mdc
━━━ Cursor ━━━
  ✅ common.mdc
  ✅ typescript.mdc
  ✅ security.mdc

✨ 同步完成!共 6 条规则、0 个技能

嗯,6 条规则 2 个 IDE,正常了。

PS: 第一次看 defu 文档的时候没注意到这点,差点以为是自己 sync 逻辑写错了,浪费了一个晚上。给同样用 c12 / defu 的同学提个醒------数组字段的合并语义要自己把控。

搞事3.0:模板跟随 npm 包一起发布

最后一个坑,比想象中朴素。

templates/ 目录里有 5 条规则、4 个技能,最早放在 packages/cli/templates/ 下,vite build 默认会一起打包。

后来重构 monorepo,把 templates/ 移到了仓库根目录(CLI / 脚手架 / 文档脚本都要用),vite build不认它了 ------只打 packages/cli/src/ 下的东西。

跑出来的 dist/ 里没有 templates/,发布的 npm 包里也没有,用户装上之后 sync 一跑就报:

text 复制代码
⚠️  未找到内置规则: common
⚠️  未找到内置规则: typescript
⚠️  未找到内置规则: security

解决:写一个 30 行的 copy-templates.mjsvite build 完事之后手动拷:

javascript 复制代码
// packages/cli/scripts/copy-templates.mjs
const PKG_ROOT = resolve(__dirname, '..')
const MONOREPO_ROOT = resolve(PKG_ROOT, '../..')
const SRC = resolve(MONOREPO_ROOT, 'templates')   // 仓库根 templates/
const DEST = resolve(PKG_ROOT, 'dist/templates')   // 跟 dist 走

function copyDir(src, dest) {
  mkdirSync(dest, { recursive: true })
  for (const entry of readdirSync(src)) {
    const srcPath = join(src, entry)
    const destPath = join(dest, entry)
    if (statSync(srcPath).isDirectory()) {
      copyDir(srcPath, destPath)
    } else {
      copyFileSync(srcPath, destPath)
    }
  }
}

copyDir(SRC, DEST)

package.json 的 build 串起来:

json 复制代码
{
  "scripts": {
    "build": "vite build && node scripts/copy-templates.mjs",
    "dev": "vite build --watch & node scripts/copy-templates.mjs --watch"
  }
}

dev 模式还支持 --watch,模板改了自动同步到 dist/

为了让 getTemplatesDir()所有运行方式下都能找到模板,我列了 4 个候选路径,按优先级找第一个存在的:

typescript 复制代码
// packages/cli/src/core/templates.ts
const candidates = [
  resolve(hereDir, 'templates'),                  // ① dist 内(构建产物)
  resolve(hereDir, '../../templates'),            // ② node_modules/xingwu-ai-kit/templates
  resolve(hereDir, '../../../templates'),         // ③ 源码直跑
  resolve(hereDir, '../../../../templates'),      // ④ monorepo 根
]

实测 npm pack --dry-run 验证:13 个模板文件全部以 dist/templates/... 路径打进 tarball,发布到 npm 后 node_modules/xingwu-ai-kit/dist/templates/ 就是模板真实位置。

架构总览

上面三节搞事走完,整个链路就清晰了,整理成一张图:

flowchart TB UC[&#34;📝 xingwu-ai-kit.config.ts<br/><i>ide / rules / skills / mcp</i>&#34;] TPL[&#34;📚 templates/<br/><i>5 rules + 4 skills</i>&#34;] subgraph CLI[&#34;⚙️ CLI: xingwu-ai-kit sync&#34;] L[&#34;config-loader<br/>(c12 + defu)&#34;] A[&#34;ide adapters<br/>5 个 IDE 适配&#34;] RL[&#34;rules / skills linker<br/>(safeLink)&#34;] MW[&#34;mcp writer<br/>(JSON merge)&#34;] end subgraph IDE[&#34;🎯 5 个 IDE 工作目录&#34;] CB[&#34;.codebuddy/&#34;] CU[&#34;.cursor/&#34;] WS[&#34;.windsurf/&#34;] TR[&#34;.trae/&#34;] QD[&#34;.qoder/&#34;] end UC --> L --> A TPL --> RL A --> RL A --> MW RL -->|symlink| CB RL -->|symlink| CU RL -->|symlink| WS RL -->|symlink| TR RL -->|symlink| QD MW -->|写 mcp.json| CB MW -->|写 mcp.json| CU MW -->|写 mcp.json| WS MW -->|写 mcp.json| TR MW -->|写 mcp.json| QD

数据流只有一条单向路径:用户配置 → 加载合并 → IDE 适配 → 规则/技能走 symlink、MCP 走 JSON 写入 → 5 个 IDE 同时落库 。改一处配置,跑一次 sync,5 个 IDE 自动对齐。

最终效果

经过上面这一轮搞事,最终的 xingwu-ai-kit.config.ts 长这样:

typescript 复制代码
/** @type {import('xingwu-ai-kit/config').AIKitConfig} */
export default {
  ide: ['codebuddy', 'cursor', 'windsurf', 'trae', 'qoder'],

  rules: {
    preset: ['common', 'typescript', 'security'],
    overrideStrategy: 'skip',
  },

  skills: {
    preset: ['code-review', 'figma2code', 'git-commit', 'refactor'],
  },

  mcp: {
    servers: {
      FramelinkFigmaMCP: {
        type: 'stdio',
        command: 'npx',
        args: ['-y', 'figma-developer-mcp', '--stdio'],
        env: { FIGMA_API_KEY: process.env.FIGMA_API_KEY || '' },
      },
      context7: {
        type: 'http',
        url: 'https://mcp.context7.com/mcp',
      },
    },
    target: 'user',
    onConflict: 'skip',
    preserveUserServers: true,
  },
}

npx xingwu-ai-kit sync,5 个 IDE 同时落库,规则 / 技能 / MCP 一次到位:

text 复制代码
开始同步...

━━━ CodeBuddy ━━━
📏 Rules:
  ✅ common.mdc
  ✅ typescript.mdc
  ✅ security.mdc
🧠 Skills:
  ✅ code-review
  ✅ figma2code
  ✅ git-commit
  ✅ refactor

━━━ Cursor / Windsurf / Trae / Qoder ... ━━━   (同上)

🔌 MCP Servers:
  [CodeBuddy] 新增: FramelinkFigmaMCP, context7
  [Cursor] 新增: FramelinkFigmaMCP, context7
  ...

✨ 同步完成!共 15 条规则、20 个技能

效果图:

同事: 这次升级规范怎么搞?

我:xingwu-ai-kit.config.ts,提交到 Git,大家 pull 一下再跑一次 sync

同事: 完事?

我: 完事。

同事: 好家伙,跟 prettier 一样方便。

我: 那可不。

小结

  1. AI 上下文基建这事,社区里没有"事实标准",有空白就有机会 ------xingwu-ai-kit 核心思路是"声明式配置 + 适配器 + symlink 同步";
  2. safeLink 的 symlink + 冲突策略、JSDoc 配置的零依赖、createDefu 自定义合并,是三个"用对了很香"的小技巧,希望给大家带来一点帮助;
  3. 目前 5 个 IDE(CodeBuddy / Cursor / Windsurf / Trae / Qoder)开箱即用,Claude Code / VSCode Copilot 还在路上 ^_^。

xingwu-ai-kit 传送门,欢迎试用、提 issue、贡献规则模板。

相关推荐
掘金者阿豪1 小时前
因为一条LEFT JOIN,差点把客户的数据搞丢了
后端
查不理1 小时前
给 WorkBuddy 接入AI 音乐 MCP,让 Agent 真正生成音乐 demo
后端
2601_956865771 小时前
# 2026年AI API中转平台选型指南:高并发能力、协议兼容与白盒计费体系深度解析
人工智能·api
格子软件1 小时前
GEO系统深度实战:多引擎自适应算法与去中心化流控
人工智能·算法·去中心化·区块链
触底反弹1 小时前
面试被问 RAG 只能说出六个字?这篇用 100 行 Node.js 代码帮你彻底搞懂
javascript·人工智能·面试
小白跃升坊1 小时前
1Panel企业版+AI门户|从零构建企业专属AI门户
人工智能·ai·1panel·skills·ai运维·ai门户
Highcharts1 小时前
@highcharts/react完整技术指南|新版Highcharts React使用、迁移、SSR、TS 优化全解
前端·javascript
山水洛行1 小时前
Loop Engineering:4 种 AI Agent 循环,写好一个能顶你熬夜盯屏一整晚
后端
写代码像蔡徐抻1 小时前
三年了,为什么 AI 应用还没爆发?
人工智能