
1. 项目概述一个面向工程化落地的 TypeScript Agent 能力库设计实践“agent-skills”这个名称乍看像某个开源库的包名但结合热搜词中高频出现的TypeScript、Node、Nx、semantic-release它实际指向一个正在被越来越多团队重视的工程实践方向将 AI Agent 的能力模块化、可复用、可测试、可发布。这不是一个玩具 demo而是一套为生产级 Agent 应用服务的技能Skill抽象层——它把“调用天气 API”“查询数据库”“执行 Shell 命令”“生成 Markdown 报告”这些具体动作统一建模为具备类型约束、输入校验、错误分类、可观测埋点、版本语义化管理的独立单元。我过去三年在多个智能体平台项目中反复重构这类代码从最初手写一堆if-else分发函数到后来用装饰器硬编码再到如今用 Nx TypeScript semantic-release 构建出真正可交付的myorg/agent-skills包体系。它解决的核心问题很朴素当你的 Agent 不再是单个脚本而是要支撑客服对话、自动化运维、数据分析师助手等多场景时“技能”必须像前端组件或后端微服务一样能独立开发、独立测试、独立部署、独立升级。你不需要懂 LLM 推理细节但必须清楚一个SearchWebSkill的输入参数该用zod还是io-ts校验它的 timeout 是设 5s 还是 30s失败时该抛AgentSkillTimeoutError还是重试三次这些决策直接决定整个 Agent 系统的稳定性与可维护性。适合正在用 TypeScript 构建智能体应用的工程师、技术负责人以及想把 AI 能力真正嵌入现有业务系统的架构师——尤其当你开始被问“这个技能能不能单独上线”“上个月改的那个搜索逻辑现在哪个 Agent 在用”时这套设计就不再是可选项而是必选项。2. 整体架构设计与核心思路拆解2.1 为什么不是简单封装函数——从“能跑”到“可管”的范式跃迁很多团队起步时会直接写一个weatherService.tsexport async function getWeather(city: string): PromiseWeatherData { const res await fetch(https://api.example.com/weather?q${city}); return res.json(); }然后在 Agent 的run()方法里if (intent weather) await getWeather(input)。这在 PoC 阶段完全可行但一旦进入真实业务立刻暴露三大硬伤无契约约束city: string看似有类型但没校验是否为空、是否含非法字符、是否超长。线上日志里满屏TypeError: Cannot read property temperature of undefined。无生命周期管理技能没有初始化、销毁钩子。比如数据库连接池需要在 Agent 启动时创建在关闭时释放而函数无法承载这种状态。无统一可观测性每个技能自己打日志、自己捕获异常、自己上报指标。运维时查一个问题要翻 8 个文件的日志格式。“agent-skills” 的设计起点就是把技能从“函数”升格为“实体”。它借鉴了微服务中 Service 的概念但更轻量、更专注。我们定义了一个核心接口AgentSkillTInput, TOutputinterface AgentSkillTInput, TOutput { readonly id: string; // 唯一标识用于注册、路由、监控 readonly version: string; // 语义化版本如 1.2.0 readonly description: string; // 供 LLM 理解用途的自然语言描述 readonly inputSchema: ZodSchemaTInput; // 输入校验 schema readonly outputSchema: ZodSchemaTOutput; // 输出校验 schema readonly metadata: SkillMetadata; // 扩展元数据耗时预估、依赖服务、权限要求等 init?(config: Recordstring, unknown): Promisevoid; // 可选初始化 execute(input: TInput, context: SkillContext): PromiseTOutput; // 核心执行 destroy?(): Promisevoid; // 可选销毁 }这个接口本身不复杂但它的存在强制所有技能开发者回答四个关键问题你的技能叫什么谁在用输入长什么样输出保证是什么这看似增加了几行代码实则把模糊的“功能”变成了清晰的“契约”。2.2 为什么选 Nx 而非单一仓库或 Turborepo——规模化协作的刚性需求看到热搜词里反复出现nx和nx二次开发就知道这不是偶然。我们对比过三种主流单体仓库方案方案优势痛点适用规模单一src/目录简单零配置技能间无隔离npm run test跑全量CI 时间随技能数线性增长无法按需发布单个技能包 5 个技能Turborepo构建快缓存好工作区workspace粒度粗一个package.json对应一个目录难以对“技能”做细粒度依赖管理缺乏内置的插件生态中型技能间耦合低Nx工作区拓扑清晰支持 project-level dependencies、task pipelines、affected graph内置插件如 nx/node开箱即用CLI 提供nx graph可视化依赖nx affected精准触发 CI学习曲线稍陡配置略多中大型技能数 10需跨团队协作我们最终选择 Nx核心在于它解决了“技能爆炸”后的治理难题。举个真实例子某次安全审计要求所有调用外部 API 的技能必须增加X-Request-ID头。在 Nx 下我们只需创建一个myorg/agent-skill-core库封装通用 HTTP 客户端修改http-client.ts注入X-Request-ID运行nx affected --targetbuildNx 自动识别出所有依赖此库的技能项目并只构建它们nx affected --targetlint检查这些技能的代码风格是否一致。整个过程无需人工梳理依赖关系也不用担心漏掉某个技能。而如果用 Turborepo你得手动维护turbo.json中的pipeline依赖声明用单一目录则只能全局搜索替换风险极高。Nx 的project.json文件本质上就是每个技能的“身份证”它明确定义了这个技能的源码在哪、测试在哪、如何构建、依赖哪些其他技能或工具库。这种显式化、结构化的管理是工程化落地的基石。2.3 为什么坚持 TypeScript semantic-release——类型即文档版本即契约热搜词里typescript面试typescript教程高频出现说明社区对 TS 的接受度已从“加分项”变成“标配”。但在 agent-skills 场景下TS 的价值远不止于“避免运行时类型错误”LLM 友好我们给 LLM 提供的技能描述description字段会自动提取inputSchema的 zod 定义生成类似 “city: string, required, max length 50” 的结构化提示。LLM 生成的参数 JSON能被 TS 类型系统在编译期就验证合法性大幅降低解析失败率。IDE 智能提示当业务方在集成技能时VS Code 能直接显示SearchWebSkill的execute方法签名、参数说明、返回值示例无需翻文档。重构安全修改WeatherData接口时Nx 的nx dep-graph会高亮所有受影响的技能TS 编译器会报错指出哪一行调用需要更新。而semantic-release则是把“版本号”从一个随意的字符串变成一个可追溯、可预测、可自动化的契约。我们约定feat:提交触发minor版本如1.2.0 → 1.3.0代表新增技能或技能新增非破坏性参数fix:提交触发patch版本如1.2.0 → 1.2.1代表修复 bug 或优化性能BREAKING CHANGE:在提交信息 body 中出现触发major版本如1.2.0 → 2.0.0代表inputSchema或outputSchema发生不兼容变更。这套规则由 CI 流水线严格执行。每次 PR 合并到mainsemantic-release 就自动解析 commit 历史计算新版本号更新package.json中的version生成 CHANGELOG.mdnpm publish到私有 registry创建 GitHub Release。结果是业务方只要npm install myorg/agent-skills^1.2.0就能确保获得所有1.x的向后兼容更新若需用新技能必须显式升级到2.0.0并处理 breaking change。版本号不再是一个数字而是团队间关于兼容性的无声承诺。3. 核心细节解析与实操要点3.1 技能接口的深度实现不只是类型更是行为契约AgentSkill接口的execute方法签名看似简单但其背后隐藏着大量工程细节。我们以一个真实的DatabaseQuerySkill为例展示如何将接口定义转化为健壮的实现import { z } from zod; import { AgentSkill, SkillContext, SkillError } from myorg/agent-skill-core; // 1. 严格定义输入 Schema —— 这是技能的“门禁” const DatabaseQueryInputSchema z.object({ query: z.string().min(1).max(2000), // 防止 SQL 注入和超长查询 params: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(), // 参数化查询 timeoutMs: z.number().int().min(100).max(30000).default(5000), // 显式控制超时 }); // 2. 定义输出 Schema —— 这是技能的“承诺” const DatabaseQueryOutputSchema z.object({ rows: z.array(z.record(z.unknown())), // 查询结果行 rowCount: z.number().int().nonnegative(), // 影响行数 tookMs: z.number().int().nonnegative(), // 实际耗时 }); // 3. 实现技能类 —— 继承基类聚焦业务逻辑 export class DatabaseQuerySkill implements AgentSkillz.infertypeof DatabaseQueryInputSchema, z.infertypeof DatabaseQueryOutputSchema { readonly id database-query; readonly version 1.1.0; readonly description Execute a parameterized SQL query against the primary database.; readonly inputSchema DatabaseQueryInputSchema; readonly outputSchema DatabaseQueryOutputSchema; readonly metadata { estimatedDurationMs: 2000, requiresPermission: [db:read], tags: [database, sql] }; private pool?: Pool; // 数据库连接池由 init 初始化 // 4. 初始化建立连接池设置健康检查 async init(config: { connectionString: string }) { this.pool new Pool({ connectionString: config.connectionString }); // 添加连接池健康检查 this.pool.on(error, (err) { console.error([DatabaseQuerySkill] Pool error:, err); // 触发告警但不中断 Agent后续请求会自动重连 }); } // 5. 核心执行封装所有防御性编程 async execute( input: z.infertypeof DatabaseQueryInputSchema, context: SkillContext ): Promisez.infertypeof DatabaseQueryOutputSchema { // 步骤1输入校验自动由基类调用 inputSchema.safeParse const parseResult this.inputSchema.safeParse(input); if (!parseResult.success) { throw new SkillError(INVALID_INPUT, parseResult.error.toString()); } // 步骤2上下文增强 —— 注入 traceId、userId 等 const startTime Date.now(); const queryContext { ...context, traceId: context.traceId || generateTraceId(), userId: context.userId || anonymous }; try { // 步骤3执行查询带超时控制 const result await Promise.race([ this.pool!.query(input.query, input.params || []), new Promisenever((_, reject) setTimeout(() reject(new SkillError(TIMEOUT, Query timed out after ${input.timeoutMs}ms)), input.timeoutMs) ) ]); const tookMs Date.now() - startTime; // 步骤4输出校验自动由基类调用 outputSchema.safeParse const output { rows: result.rows, rowCount: result.rowCount, tookMs }; const outputParseResult this.outputSchema.safeParse(output); if (!outputParseResult.success) { throw new SkillError(INVALID_OUTPUT, outputParseResult.error.toString()); } return outputParseResult.data; } catch (error) { // 步骤5标准化错误 —— 统一抛出 SkillError便于上层统一处理 if (error instanceof SkillError) { throw error; } if (error instanceof QueryCanceledError) { throw new SkillError(QUERY_CANCELED, error.message); } if (error instanceof ConnectionError) { throw new SkillError(DB_CONNECTION_ERROR, error.message); } throw new SkillError(UNKNOWN_ERROR, error instanceof Error ? error.message : String(error)); } } // 6. 销毁优雅关闭连接池 async destroy() { if (this.pool) { await this.pool.end(); this.pool undefined; } } }这个实现的关键细节在于Schema 即文档zod定义不仅用于运行时校验还自动生成 OpenAPI spec 和 LLM 提示一份定义三处使用。错误分类明确SkillError是一个基类派生出INVALID_INPUT、TIMEOUT、DB_CONNECTION_ERROR等子类。业务方可以if (err.name TIMEOUT) retry()而不是if (err.message.includes(timeout))。上下文透传SkillContext是一个标准对象包含traceId、userId、sessionId等确保所有技能日志可关联、可追踪。资源生命周期闭环init和destroy成对出现避免连接泄漏。提示不要在execute中做耗时的初始化操作如创建新连接。所有初始化必须在init中完成并在destroy中清理。这是技能可复用的前提。3.2 Nx 工作区的精细化配置让每个技能成为独立可交付单元Nx 的强大在于其配置的灵活性。一个典型的agent-skills工作区目录结构如下agent-skills/ ├── apps/ # Agent 主应用可选 ├── libs/ │ ├── agent-skill-core/ # 核心接口、基类、错误类型 │ ├── skills/ # 所有具体技能的集合monorepo 中的“技能库” │ │ ├── weather/ # weather-skill 项目 │ │ ├── database/ # database-skill 项目 │ │ └── ... # 其他技能 │ └── utils/ # 通用工具函数如 trace 工具、日志封装 ├── tools/ # 自定义 Nx 插件如 skill-validator └── nx.json # Nx 全局配置每个技能如libs/skills/weather都是一个独立的 Nx project其project.json文件是关键{ name: weather-skill, root: libs/skills/weather, sourceRoot: libs/skills/weather/src, projectType: library, targets: { build: { executor: nx/node:build, outputs: [{options.outputPath}], options: { outputPath: dist/libs/skills/weather, main: libs/skills/weather/src/index.ts, tsConfig: libs/skills/weather/tsconfig.lib.json, assets: [libs/skills/weather/*.md] }, configurations: { production: { optimization: true, extractLicenses: true, inspect: false, fileReplacements: [ { replace: libs/skills/weather/src/environments/environment.ts, with: libs/skills/weather/src/environments/environment.prod.ts } ] } } }, test: { executor: nx/jest:jest, options: { jestConfig: libs/skills/weather/jest.config.ts, passWithNoTests: true } }, lint: { executor: nx/eslint:eslint, options: { lintFilePatterns: [libs/skills/weather/**/*.ts] } }, publish: { executor: nx:run-commands, options: { command: npx semantic-release --branchesmain --ci } } }, tags: [type:skill, domain:weather], dependencies: [ { source: weather-skill, target: agent-skill-core, type: static } ] }这个配置实现了三个关键目标构建隔离weather-skill的构建产物只包含自身代码和agent-skill-core的依赖不会打包进database-skill的代码。dist/libs/skills/weather目录就是一个标准的 npm 包。依赖显式化dependencies数组强制声明了weather-skill依赖agent-skill-coreNx 的nx dep-graph可以可视化这个关系nx affected可以精准计算影响范围。任务可组合publishtarget 直接调用semantic-release与build和test形成完整流水线。CI 中只需nx run weather-skill:publish即可完成构建、测试、发布全流程。注意tags字段不是装饰而是 Nx 的强大过滤器。你可以nx run-many --targettest --projectsweather-skill,database-skill也可以nx run-many --targettest --tagsdomain:weather甚至nx run-many --targetbuild --all --excludetype:util。标签让大规模项目管理变得可伸缩。3.3 semantic-release 的定制化适配内部发布流程默认的semantic-release面向 GitHub public repo而企业环境往往需要适配私有 GitLab、Nexus 私库、内部审批流。我们做了三项关键定制第一适配私有 Git 仓库在.releaserc中指定gitlab插件并配置 token{ plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, [ semantic-release/gitlab, { gitlabUrl: https://gitlab.internal.company.com, assets: [dist/**/*] } ], [ semantic-release/npm, { npmPublish: true, registryUrl: https://nexus.internal.company.com/repository/npm/ } ] ] }第二增加预发布检查创建tools/scripts/pre-release-check.ts在semantic-release执行前运行import { execSync } from child_process; import * as fs from fs; // 检查 CHANGELOG 是否已更新防止忘记 if (!fs.existsSync(CHANGELOG.md)) { throw new Error(CHANGELOG.md is missing. Please run nx changelog first.); } // 检查所有技能的 package.json version 是否与 workspace 一致 const workspaceVersion require(../package.json).version; const skillPackages fs.readdirSync(dist/libs/skills); skillPackages.forEach(skill { const pkg require(../dist/libs/skills/${skill}/package.json); if (pkg.version ! workspaceVersion) { throw new Error(Skill ${skill} version ${pkg.version} does not match workspace version ${workspaceVersion}); } }); console.log(✅ Pre-release checks passed.);然后在project.json的publishtarget 中前置调用publish: { executor: nx:run-commands, options: { commands: [ ts-node tools/scripts/pre-release-check.ts, npx semantic-release --branchesmain --ci ] } }第三生成技能级 CHANGELOG默认semantic-release生成的是 workspace 级 CHANGELOG。我们用conventional-changelog的--lerna-package参数为每个技能生成专属 CHANGELOG# 在 weather-skill 目录下运行 npx conventional-changelog -p angular -i CHANGELOG.md -s --lerna-package weather-skill这样每个dist/libs/skills/weather/CHANGELOG.md都只记录该技能的变更业务方查阅时一目了然。4. 实操过程与核心环节实现4.1 从零搭建 Nx 工作区避开新手最常踩的五个坑我带过的 7 个团队90% 的人在初始化 Nx 时都卡在这几个点。以下是我整理的、经过 12 次实战验证的步骤清单第一步安装 Nx CLI全局# 推荐使用 npm避免 nvm/pnpm 的路径问题 npm install -g nx # 验证 nx --version # 应输出 18.0.0坑1npx create-nx-workspacelatest会创建一个空 workspace但缺少nx/node插件。正确做法是直接nx命令初始化。第二步创建 workspace# 创建名为 agent-skills 的 workspace选择 empty 模板因为我们自己定义结构 nx create agent-skills --presetempty --nx-cloudfalse --no-prompt cd agent-skills第三步添加 Node 插件关键# 这一步必须做否则后续无法创建 library nx add nx/node # 验证nx list nx/node 应显示可用的 executors坑2很多人跳过这步直接nx g nx/node:library结果报错Cannot find executor nx/node:build。add命令会自动安装依赖并更新nx.json。第四步创建核心库agent-skill-corenx g nx/node:library agent-skill-core --directorylibs --importPathmyorg/agent-skill-core --no-interactive # 这会生成 libs/agent-skill-core/ 目录并在 libs/agent-skill-core/src/index.ts 中导出默认内容第五步创建第一个技能weather-skill# 关键参数--importPath 指定包名--publishable 表示这是一个可发布的库 nx g nx/node:library weather-skill --directorylibs/skills --importPathmyorg/weather-skill --publishable --no-interactive # 修改 libs/skills/weather-skill/project.json添加 dependencies # 将 agent-skill-core 加入 dependencies 数组Nx 会自动处理 tsconfig paths第六步配置 TypeScript 路径别名省去 ../../../在tsconfig.base.json的compilerOptions.paths中添加{ compilerOptions: { paths: { myorg/agent-skill-core: [libs/agent-skill-core/src/index.ts], myorg/weather-skill: [libs/skills/weather-skill/src/index.ts], myorg/*: [libs/*] } } }坑3不配置 paths你在weather-skill中import { AgentSkill } from myorg/agent-skill-core会报错Cannot find module。Nx 的nx/js:tscexecutor 依赖此配置。第七步编写第一个技能最小可行在libs/skills/weather-skill/src/lib/weather-skill.ts中import { AgentSkill } from myorg/agent-skill-core; export class WeatherSkill implements AgentSkillstring, { temperature: number } { readonly id weather; readonly version 0.0.1; readonly description Get current temperature for a city.; readonly inputSchema z.string(); // 简化版 readonly outputSchema z.object({ temperature: z.number() }); async execute(input: string): Promise{ temperature: number } { // 模拟 API 调用 return { temperature: Math.floor(Math.random() * 30) 10 }; } }第八步构建并验证# 构建 weather-skill nx build weather-skill # 检查 dist/libs/skills/weather-skill/ 是否生成了 index.js 和 index.d.ts ls dist/libs/skills/weather-skill/ # 验证类型文件是否正确 cat dist/libs/skills/weather-skill/index.d.ts # 应看到 export declare class WeatherSkill implements ...坑4nx build默认不生成.d.ts声明文件。需在libs/skills/weather-skill/tsconfig.lib.json的compilerOptions中添加declaration: true, declarationMap: true。第九步本地链接测试# 在 dist 目录下执行 npm link cd dist/libs/skills/weather-skill npm link # 在另一个测试项目如 apps/test-app中 npm link myorg/weather-skill cd ../../apps/test-app npm link myorg/weather-skill # 编写测试代码 import { WeatherSkill } from myorg/weather-skill; const skill new WeatherSkill(); skill.execute(Beijing).then(console.log); // 应输出 { temperature: xx }坑5npm link在 Windows 上常因权限问题失败。解决方案以管理员身份运行 PowerShell或改用pnpm link推荐。4.2 技能注册与运行时调度让 Agent 知道“该找谁干活”有了技能还需一个中央调度器Orchestrator它负责接收 Agent 的意图intent匹配对应的技能并传递上下文。我们设计了一个极简的SkillRegistry// libs/agent-skill-core/src/registry.ts import { AgentSkill } from ./skill; export class SkillRegistry { private skills: Mapstring, AgentSkillany, any new Map(); // 注册技能实例 registerSkill extends AgentSkillany, any(skill: Skill): void { if (this.skills.has(skill.id)) { throw new Error(Skill with id ${skill.id} already registered); } this.skills.set(skill.id, skill); } // 根据 ID 获取技能类型安全 getTInput, TOutput(id: string): AgentSkillTInput, TOutput | undefined { return this.skills.get(id) as AgentSkillTInput, TOutput; } // 获取所有技能 ID供 LLM 选择 getAllIds(): string[] { return Array.from(this.skills.keys()); } // 获取所有技能描述供 LLM 生成 prompt getAllDescriptions(): Recordstring, string { const descriptions: Recordstring, string {}; this.skills.forEach((skill, id) { descriptions[id] ${skill.description} (v${skill.version}); }); return descriptions; } } // 使用示例 const registry new SkillRegistry(); // 初始化时注册所有技能 const weatherSkill new WeatherSkill(); await weatherSkill.init({ apiKey: xxx }); registry.register(weatherSkill); const dbSkill new DatabaseQuerySkill(); await dbSkill.init({ connectionString: ... }); registry.register(dbSkill); // Agent 运行时调用 async function runSkill(intent: string, input: unknown, context: SkillContext) { const skill registry.get(intent); if (!skill) { throw new Error(No skill registered for intent: ${intent}); } return skill.execute(input, context); }这个注册器的设计哲学是中心化注册去中心化执行。所有技能在启动时一次性注册之后的execute调用完全不经过 registry避免性能瓶颈。registry.get()返回的是泛型类型AgentSkillTInput, TOutputTypeScript 能根据intent字符串字面量推断出具体的TInput和TOutput实现完美的类型安全。4.3 CI/CD 流水线配置从提交到发布的全自动闭环我们使用 GitHub Actions适配 GitLab CI 同理一个典型的agent-skills/.github/workflows/ci.yml如下name: CI on: push: branches: [main] paths-ignore: - **/*.md - **/*.txt jobs: build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: fetch-depth: 0 # 必须semantic-release 需要完整 commit history - name: Setup Node.js uses: actions/setup-nodev4 with: node-version: 18.x cache: npm - name: Install dependencies run: npm ci - name: Build all projects run: npx nx build --all --configurationproduction - name: Run tests for affected projects run: npx nx affected --targettest --parallel3 - name: Run lint for affected projects run: npx nx affected --targetlint --parallel3 - name: Run e2e tests (if any) if: always() run: npx nx affected --targete2e --parallel2 || echo No e2e tests to run release: needs: build-and-test if: github.event_name push github.event.branch main runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-nodev4 with: node-version: 18.x cache: npm - name: Install dependencies run: npm ci - name: Publish packages env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} run: npx nx run-many --targetpublish --all --parallel1这个流水线的关键点fetch-depth: 0这是 semantic-release 的硬性要求否则无法解析 commit history。npx nx affected只运行受本次提交影响的项目的测试和 lint极大缩短 CI 时间。Nx 通过分析 git diff 和 project dependencies 自动生成影响图。--paralleltest和lint任务并行执行但publish必须串行--parallel1避免多个技能同时发布导致 registry 冲突。Secrets 管理NPM_TOKEN和GITLAB_TOKEN在 GitHub Settings 中配置不在代码中硬编码。实操心得第一次发布时semantic-release 会失败因为它找不到上一个 tag。解决方案手动创建一个v0.0.0tag 并 push 到 main 分支然后重跑 workflow。后续所有发布都将自动进行。5. 常见问题与排查技巧实录5.1 技能构建产物体积过大——Tree-shaking 与依赖分析现象dist/libs/skills/weather-skill/index.js体积达 2MB远超预期。原因分析zod、axios等库被完整打包而技能本身代码只有几百行。解决方案确认external配置在libs/skills/weather-skill/project.json的buildtarget 中添加externalDependenciesoptions: { externalDependencies: [zod, axios, node-fetch], outputPath: dist/libs/skills/weather-skill, ... }这告诉nx/node:build将这些包标记为peerDependencies不打包进产物。检查package.json确保zod、axios在peerDependencies中而非dependencies{ name: myorg/weather-skill, version: 1.0.0, peerDependencies: { zod: ^3.22.0, axios: ^1.6.0 } }验证产物构建后用npx source-map-explorer dist/libs/skills/weather-skill/index.js分析包体积确认zod已消失。注意peerDependencies意味着使用者必须自行安装这些包。这是合理的因为zod是类型校验基础设施所有技能都应共享同一版本避免冲突。5.2 Nxaffected命令不生效——依赖图失效的四大诱因现象修改了libs/agent-skill-core但nx affected --targetbuild没有触发任何技能重建。排查步骤检查project.json中的dependencies打开libs/skills/weather-skill/project.json确认dependencies数组中确实有agent-skill-core条目。手动添加后运行nx graph查看是否连线。检查 TypeScriptpaths配置如果技能通过import { X } from myorg/agent-skill-core导入但tsconfig.base.json的paths未正确配置Nx 无法解析依赖。