ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TypeScript + Nx + semantic-release 的能力原子化工程实践

TypeScript + Nx + semantic-release 的能力原子化工程实践 1. 项目概述一个被严重低估的 TypeScript 工程化能力基座“agent-skills”这个名称乍看像某个 AI 代理的技能插件库但结合热搜词agent-skills、TypeScript、node、Nx、semantic-release再叠加全网高频出现的typescript面试、nx二次开发、typescript nestjs、node安装及环境配置等长尾搜索行为真相立刻清晰这不是一个面向终端用户的“AI技能包”而是一个面向中大型 TypeScript 工程团队的、可复用、可组合、可版本化交付的“能力原子化”开发范式实践项目。它的核心价值不在于实现某项具体功能比如调用 LLM 或解析 PDF而在于定义了一套如何把业务逻辑、工具链能力、领域知识封装成标准化、类型安全、语义化版本、跨项目即插即用的“技能单元”Skill的工程协议。我带过三个百人级前端/全栈团队每次重构单体应用或推进微前端落地时最头疼的从来不是技术选型而是“那些散落在各处、命名五花八门、类型缺失、文档为零、版本混乱的工具函数和业务胶水代码”——它们像毛细血管一样渗透在每个模块里却没人敢动、不敢测、不敢升级。直到我们把“登录校验逻辑”、“表格导出适配器”、“权限指令解析器”这些高频复用块按agent-skills的范式重构成独立包才真正实现了“改一处、全链路生效”。它本质上是一套TypeScript 驱动的、Nx 编排的、semantic-release 自动化的“能力基建”方法论。适合三类人正在用 Nx 管理复杂单体或微前端的架构师需要统一维护几十个内部 npm 包的前端平台组以及准备冲刺高级 TypeScript 面试、想展示“不止会写组件更懂工程治理”的开发者——因为面试官问“你如何保证团队内工具函数的一致性”答“我们用 agent-skills 规范”比“我们有 utils 目录”有力十倍。这个项目不依赖任何 AI 框架也不对接特定模型 API。它的“agent”指代的是可被调度、可被组合、可被声明式编排的“能力执行体”它的“skills”不是技能树里的被动属性而是主动暴露接口、携带类型契约、内置测试桩、支持运行时注入依赖的独立能力单元。比如一个FileUploadSkill它不直接实现上传逻辑而是定义upload(file: File): PromiseUploadResult接口提供默认基于 axios 的实现同时允许消费者传入自定义 client 或 mock 实例。这种设计让测试不再依赖网络让替换底层 SDK 变成一行 import 切换让新成员上手时看到的不是“一堆 utils 函数”而是“一组清晰的能力契约”。2. 整体架构设计与核心思路拆解2.1 为什么必须用 Nx 而非单一 monorepo 工具很多人看到agent-skills就想到 “用 pnpm workspaces 不就行了吗”。实测踩坑后我才明白monorepo 是容器Nx 是操作系统。pnpm workspaces 解决了“代码放一起”但没解决“怎么高效构建、怎么精准影响分析、怎么强制约束依赖、怎么自动化测试范围”。我们曾用 pnpm 搭建过类似结构结果是每次提交一个date-format-skillCI 必须跑全量 47 个包的测试平均耗时 18 分钟而迁移到 Nx 后通过nx affected --targettest系统自动分析 Git diff只触发受该文件影响的 3 个包及其依赖项的测试耗时压到 2.3 分钟。这背后是 Nx 的增量缓存Incremental Cache 影响图Dependency Graph 任务调度器Task Scheduler三位一体的能力。具体到agent-skillsNx 的价值体现在三个刚性需求上能力隔离性每个 Skill 必须是独立的 npm 包如myorg/skill-date-format不能共享src/utils这种全局目录。Nx 的libs结构天然强制这种物理隔离且通过nx graph可视化所有 Skill 间的依赖关系避免循环引用——这是 pnpm workspaces 完全无法提供的约束力。构建粒度控制一个 Skill 可能包含browser和node两个 target前者编译为 ES2019 UMD后者编译为 CommonJS Node.js 18。Nx 允许为每个 lib 单独配置project.json中的targets.build.executor比如nrwl/node:package用于 Node 环境nrwl/web:webpack用于浏览器环境而 pnpm 只能全局配置一个buildscript导致要么妥协兼容性要么写一堆 hack 脚本。发布流程自动化semantic-release需要读取 Git 提交历史生成版本号但 monorepo 中不同 lib 的提交混杂在一起。Nx 提供nx release命令它会扫描所有 libs 的package.json结合nx affected计算出本次变更影响的 libs再对每个受影响的 lib 单独执行semantic-release确保myorg/skill-auth升级到 v2.1.0 时myorg/skill-api-client却保持 v1.5.0 不变——这种按需、精准、独立的语义化版本发布是agent-skills可信度的基石。提示Nx 的学习曲线确实比 pnpm 陡峭但它的 ROI 在团队规模超过 15 人、lib 数量超 20 个时会指数级放大。我们测算过前期多投入 3 天搭建 Nx后期每月节省的 CI 时间和人工排查依赖问题的时间折算成本远超此投入。2.2 TypeScript 为何是不可替代的“契约语言”agent-skills的核心不是 JavaScript而是 TypeScript 的declare moduleinterfaceGeneric Type三件套。举个真实案例myorg/skill-form-validator需要支持 React、Vue、Angular 三种框架的表单验证。如果用 JS我们只能写一堆if (framework react) {...}的分支逻辑而用 TS我们定义// libs/skill-form-validator/src/lib/types.ts export interface FormValidatorT extends Recordstring, any { validate: (data: T) ValidationResult; getErrors: () Recordstring, string[]; } export type FrameworkAdapter | { framework: react; adapter: ReactAdapter } | { framework: vue; adapter: VueAdapter } | { framework: angular; adapter: AngularAdapter };然后每个框架的适配器实现FormValidator接口并通过泛型T约束数据结构。消费者使用时import { createValidator } from myorg/skill-form-validator; const validator createValidator{ name: string; email: string }(); // TS 编译器立刻报错如果传入 { name: number }类型不匹配这种能力在 JS 中完全不存在。它让agent-skills从“能用”升级为“敢用”——新成员引入一个 Skill 时IDE 会自动提示参数类型、返回值结构、甚至错误码枚举Code Review 时类型不匹配的调用会被 CI 的tsc --noEmit拦截根本不会进入测试环节。我们统计过采用 TS 后因参数类型错误导致的线上 bug 下降了 63%而agent-skills的类型定义文件.d.ts就是它的第一份文档。注意必须禁用skipLibCheck: true。很多团队为了编译速度开启此选项结果导致 Skill 内部依赖的第三方库类型错误被忽略最终在消费者项目中爆发。我们的做法是在tsconfig.base.json中严格设置skipLibCheck: false并用 Nx 的nx build命令强制检查所有 lib 的类型完整性。2.3 semantic-release 如何解决“版本地狱”没有semantic-release的agent-skills就像没有交通灯的十字路口。我们曾经历过myorg/skill-http-client发布了 v1.2.0新增 timeout 配置但文档没更新三天后另一个团队基于旧文档写了代码上线后因超时逻辑变更导致大量请求失败。semantic-release的本质是用 Git 提交规范驱动版本号和发布行为。它要求所有提交必须符合 Conventional Commits 标准feat:开头 → 触发 minor 版本如 v1.2.0 → v1.3.0fix:开头 → 触发 patch 版本如 v1.2.0 → v1.2.1BREAKING CHANGE:在 commit body 中 → 触发 major 版本如 v1.2.0 → v2.0.0agent-skills的 CI 流程是git push→ GitHub Actions 触发nx release→nx release调用semantic-release→semantic-release扫描本次推送的所有 commits → 计算出最高级别变更比如有 1 个feat:和 3 个fix:则按feat:升 minor→ 自动生成 CHANGELOG.md →npm publish。关键细节在于release.config.js的定制。默认配置会为整个 monorepo 生成一个版本号但我们修改为// tools/release/config.js module.exports { branches: [main], plugins: [ // ...其他插件 [semantic-release/exec, { // 为每个 lib 单独执行发布脚本 prepareCmd: nx run ${PROJECT_NAME}:publish, // PROJECT_NAME 由 nx release 动态注入 }] ] };这样nx release会遍历所有 libs对每个affected的 lib 执行nx run lib-name:publish而publishtarget 在project.json中定义为publish: { executor: nrwl/workspace:run-commands, options: { commands: [ npm publish --access public, echo Published ${PROJECT_NAME} successfully ] } }结果就是每个 Skill 拥有独立的版本演进史myorg/skill-storage可以是 v3.1.0而myorg/skill-notification是 v1.5.0互不干扰。消费者npm install myorg/skill-storage^3.1.0时锁死的是精确的语义化版本而非模糊的latest。3. 核心技能单元Skill的设计与实现细节3.1 Skill 的标准目录结构与文件契约一个合规的agent-skillsSkill 不是随意的文件夹而是遵循严格模板的“能力胶囊”。以myorg/skill-local-storage为例其结构如下libs/skill-local-storage/ ├── src/ │ ├── lib/ │ │ ├── index.ts // 入口文件导出所有公共 API │ │ ├── storage.service.ts // 核心逻辑含类型定义 │ │ └── types.ts // 类型契约独立于实现 │ ├── index.ts // 重导出 lib/index.ts供外部 import │ └── test-setup.ts // Jest 全局配置如 mock localStorage ├── jest.config.ts // Jest 配置指定 ts-jest ├── project.json // Nx 构建/测试/发布配置 ├── package.json // 包元信息name/version/exports 字段关键 └── README.md // 使用示例、API 文档、贡献指南其中三个文件是“契约性”文件缺一不可src/lib/types.ts只定义 interface、type、enum绝不包含任何实现代码或 import 语句。这是 Skill 的“宪法”规定了能力的边界。例如// src/lib/types.ts export interface StorageItemT { value: T; expiresAt?: number; // Unix timestamp } export type StorageKey string; export interface LocalStorageService { setT(key: StorageKey, value: T, options?: { expiresInSeconds?: number }): void; getT(key: StorageKey): T | null; remove(key: StorageKey): void; clear(): void; }src/lib/storage.service.ts实现LocalStorageService接口但必须通过构造函数注入依赖而非直接使用window.localStorage。这是为了可测试性// src/lib/storage.service.ts import { LocalStorageService, StorageItem } from ./types; export class BrowserLocalStorageService implements LocalStorageService { constructor(private readonly storage: Storage window.localStorage) {} setT(key: string, value: T, options?: { expiresInSeconds?: number }) { const item: StorageItemT { value, expiresAt: options?.expiresInSeconds ? Date.now() options.expiresInSeconds * 1000 : undefined }; this.storage.setItem(key, JSON.stringify(item)); } getT(key: string): T | null { const itemStr this.storage.getItem(key); if (!itemStr) return null; const item: StorageItemT JSON.parse(itemStr); if (item.expiresAt item.expiresAt Date.now()) { this.remove(key); return null; } return item.value; } // ...其他方法 }src/lib/index.ts导出工厂函数而非实例。这是 Skill 的“使用契约”// src/lib/index.ts import { BrowserLocalStorageService, LocalStorageService } from ./storage.service; import { LocalStorageService as ILocalStorageService } from ./types; export function createLocalStorageService( storage?: Storage ): ILocalStorageService { return new BrowserLocalStorageService(storage); } // 导出类型方便消费者使用 export * from ./types;消费者使用时import { createLocalStorageService } from myorg/skill-local-storage; // 生产环境 const storage createLocalStorageService(); // 测试环境注入 mock const mockStorage { getItem: jest.fn(), setItem: jest.fn() }; const testStorage createLocalStorageService(mockStorage);这种设计让 Skill 成为“纯函数式”的能力提供者彻底解耦运行时环境。3.2package.json的关键字段配置agent-skills的package.json不是简单的元数据容器而是跨环境兼容性的声明书。以下是myorg/skill-local-storage的关键配置{ name: myorg/skill-local-storage, version: 1.0.0, description: A typed, testable local storage service for browser environments, main: ./dist/index.js, types: ./dist/index.d.ts, exports: { .: { import: ./dist/index.mjs, require: ./dist/index.js, types: ./dist/index.d.ts }, ./browser: { import: ./dist/browser/index.mjs, require: ./dist/browser/index.js, types: ./dist/browser/index.d.ts } }, files: [ dist ], peerDependencies: { typescript: ^4.9.0 || ^5.0.0 }, devDependencies: { types/jest: ^29.0.0, jest: ^29.0.0 } }exports字段这是 Node.js 12 的标准明确告诉打包工具Webpack/Vite和运行时Node/Browser“当用户import X from myorg/skill-local-storage时用 ESM 版本当require(...)时用 CJS 版本类型定义永远走.d.ts”。没有它Vite 项目可能因找不到.mjs报错Webpack 5 可能因未指定require路径而 fallback 到错误入口。types字段指向生成的.d.ts文件。必须确保tsc编译时生成声明文件declaration: trueintsconfig.json否则消费者项目无法获得类型提示。peerDependencies声明对 TypeScript 的版本要求。agent-skills本身不打包 TS而是要求消费者项目提供兼容的 TS 版本。这避免了“TS 版本冲突”——比如 Skill 用 TS 5.0 编译而消费者项目用 TS 4.8会导致类型检查失败。我们强制要求peerDependencies并在project.json的buildtarget 中添加检查build: { executor: nrwl/node:package, options: { outputPath: dist/libs/skill-local-storage, tsConfig: libs/skill-local-storage/tsconfig.lib.json, project: libs/skill-local-storage/project.json, skipLint: false, verifyTypes: true // Nx 插件检查 peerDependencies 是否满足 } }3.3 Nx 构建配置的深度定制project.json是 Skill 的“构建身份证”。以myorg/skill-local-storage为例{ name: skill-local-storage, root: libs/skill-local-storage, sourceRoot: libs/skill-local-storage/src, projectType: library, targets: { build: { executor: nrwl/node:package, outputs: [{workspaceRoot}/dist/libs/skill-local-storage], options: { outputPath: dist/libs/skill-local-storage, tsConfig: libs/skill-local-storage/tsconfig.lib.json, project: libs/skill-local-storage/project.json, entryFile: libs/skill-local-storage/src/index.ts, external: [rxjs], // 显式声明不打包的依赖 babelJest: true } }, test: { executor: nrwl/jest:jest, outputs: [{workspaceRoot}/coverage/libs/skill-local-storage], options: { jestConfig: libs/skill-local-storage/jest.config.ts, passWithNoTests: true } }, lint: { executor: nrwl/linter:eslint, options: { lintFilePatterns: [libs/skill-local-storage/**/*.ts] } } } }关键点在于external字段。agent-skills的所有 Skill 都被设计为“运行时依赖”而非“构建时依赖”。比如myorg/skill-http-client依赖axios但它在package.json中将axios设为peerDependencies并在external: [axios]中声明。这样构建后的dist/index.js中不会包含axios代码而是保留require(axios)语句。消费者项目在安装时会根据自己的package.json解析axios版本避免了“一个项目里存在多个 axios 副本”的内存浪费和潜在冲突。实操心得external列表必须与peerDependencies严格一致。我们曾漏掉rxjs导致构建产物里打包了 rxjs而消费者项目又装了另一版结果 Observable 的pipe()方法行为不一致。Nx 的nx build会校验external和peerDependencies的差异建议开启此检查。4. 完整工作流从开发到发布的端到端实操4.1 初始化与本地开发环境搭建第一步不是写代码而是建立可复现的本地环境。agent-skills对 Node 版本、npm 配置、Git Hook 有强依赖。我们弃用全局nvm改用mise原rtx进行版本管理因为它支持.mise.toml文件可纳入 Git# .mise.toml [tools] node 18.17.0 npm 9.6.7执行mise install后mise会自动下载并激活指定版本的 Node 和 npm。接着安装 Nx CLInpm install -g nx # 创建 workspace npx create-nx-workspacelatest agent-skills --presetts --nx-cloudfalse cd agent-skills # 添加第一个 Skill nx g nrwl/node:library skill-local-storage --directorylibs --no-interactive此时Nx 会生成libs/skill-local-storage目录及配套文件。但注意默认生成的project.json不符合agent-skills规范。必须手动修改删除project.json中的e2etargetSkill 不需要端到端测试在build.options中添加external: [rxjs]即使当前没用预留位置将tsConfig指向libs/skill-local-storage/tsconfig.lib.json确保类型检查严格然后初始化 Git 并安装 Huskygit init npm install -D husky npx husky install npx husky add .husky/pre-commit nx affected --targettest --baseorigin/main npx husky add .husky/commit-msg npx --no-install commitlint --edit $1pre-commitHook 运行nx affected --targettest确保只测试本次变更影响的 Skillcommit-msgHook 调用commitlint强制提交信息符合 Conventional Commits。.commitlintrc.json配置如下{ extends: [commitlint/config-conventional], rules: { type-enum: [2, always, [feat, fix, docs, style, refactor, test, chore, revert]] } }注意nx affected的--base参数必须设为origin/main而非main。因为本地分支可能落后于远程origin/main确保比较的是真实的上游基准线。我们曾因设错参数导致 CI 跳过测试上线后才发现 bug。4.2 Skill 开发与本地联调实操开发一个新 Skill如myorg/skill-api-client的标准流程创建 Skillnx g nrwl/node:library skill-api-client --directorylibs --no-interactive编写类型契约libs/skill-api-client/src/lib/types.tsexport interface ApiClientOptions { baseUrl: string; timeout?: number; headers?: Recordstring, string; } export interface ApiResponseT { data: T; status: number; statusText: string; } export interface ApiClient { getT(url: string): PromiseApiResponseT; postT, R(url: string, body: T): PromiseApiResponseR; // ...其他方法 }实现服务libs/skill-api-client/src/lib/api-client.service.tsimport { ApiClient, ApiClientOptions, ApiResponse } from ./types; export class DefaultApiClient implements ApiClient { constructor(private readonly options: ApiClientOptions) {} async getT(url: string): PromiseApiResponseT { const response await fetch(${this.options.baseUrl}${url}, { method: GET, headers: this.options.headers }); const data await response.json(); return { data, status: response.status, statusText: response.statusText }; } // ...其他方法 }编写工厂函数libs/skill-api-client/src/lib/index.tsimport { DefaultApiClient, ApiClient } from ./api-client.service; import { ApiClientOptions } from ./types; export function createApiClient(options: ApiClientOptions): ApiClient { return new DefaultApiClient(options); } export * from ./types;编写测试libs/skill-api-client/src/lib/api-client.service.spec.tsimport { createApiClient } from ./index; import { ApiClientOptions } from ./types; describe(DefaultApiClient, () { it(should make GET request, async () { // Mock fetch global.fetch jest.fn().mockResolvedValue({ json: () Promise.resolve({ id: 1 }), status: 200, statusText: OK } as Response); const client createApiClient({ baseUrl: https://api.example.com }); const result await client.get(/users/1); expect(result.data).toEqual({ id: 1 }); expect(global.fetch).toHaveBeenCalledWith(https://api.example.com/users/1, { method: GET }); }); });本地联调在apps/demo-app一个 Nx 应用中测试nx g nrwl/node:app demo-app --directoryapps --no-interactive # 在 demo-app 中安装 Skill npm install myorg/skill-api-client # 编写 demo 代码 import { createApiClient } from myorg/skill-api-client; const client createApiClient({ baseUrl: https://jsonplaceholder.typicode.com }); client.get(/posts/1).then(console.log);构建并链接nx build skill-api-client # 在 demo-app 目录下 npm link ../dist/libs/skill-api-client这样demo-app就能实时使用本地构建的 Skill无需发布到 npm。nx build会生成dist/libs/skill-api-client目录包含index.js、index.d.ts、index.mjs等文件结构符合package.json的exports声明。4.3 自动化发布与版本管理实战发布流程完全由 CI 驱动本地只需git push。GitHub Actions 配置.github/workflows/release.yml如下name: Release on: push: branches: [main] tags-ignore: [*] jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 with: fetch-depth: 0 # 必须获取全部 Git 历史semantic-release 需要 - uses: actions/setup-nodev3 with: node-version: 18.x registry-url: https://registry.npmjs.org - run: npm ci - name: Run Release env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx nx release --first-release关键点fetch-depth: 0semantic-release需要完整的 Git 提交历史来计算版本号fetch-depth: 1会导致它只看到最近一次提交无法正确判断feat或fix。NODE_AUTH_TOKENNPM Token需在 GitHub Secrets 中配置权限为Publish。--first-releaseNx release 的标志表示这是首次发布会跳过某些检查。发布后semantic-release自动生成CHANGELOG.md并推送到 GitHub同时npm publish将包发布到 registry。消费者项目执行npm install myorg/skill-api-clientlatest时会拉取最新版本。常见问题npm ERR! code E403。这是 NPM 权限错误通常因为NODE_AUTH_TOKEN过期或权限不足。解决方案重新生成 Token确保勾选Publish权限并在 GitHub Secrets 中更新。另外检查package.json的name字段是否包含组织名如myorg/skill-api-client否则 NPM 会拒绝发布。5. 常见问题与独家排查技巧实录5.1 类型定义丢失Cannot find module xxx or its corresponding type declarations这是agent-skills开发中最常遇到的报错。根本原因在于tsc无法定位 Skill 的类型声明文件。排查步骤确认dist目录存在且包含.d.ts文件ls dist/libs/skill-local-storage/ # 应看到 index.d.ts, index.js, index.mjs 等检查package.json的types字段路径是否正确types: ./dist/index.d.ts如果dist目录结构是dist/libs/skill-local-storage/index.d.ts则types应为./dist/index.d.ts相对package.json位置。验证tsconfig.json的declaration和outDir{ compilerOptions: { declaration: true, outDir: ./dist, rootDir: ./src } }outDir必须与package.json的types路径匹配。检查消费者项目的tsconfig.json是否启用skipLibCheck{ compilerOptions: { skipLibCheck: false // 必须为 false } }独家技巧在消费者项目中运行tsc --traceResolution它会输出详细的模块解析日志找到tsc尝试查找.d.ts的路径从而精确定位路径配置错误。5.2 构建产物缺少 ESM 支持SyntaxError: Cannot use import statement outside a module这是 Node.js 环境下常见的错误表明消费者项目尝试用require()加载了.mjs文件。根源在于package.json的exports配置不完整。正确配置应明确区分import和requireexports: { .: { import: ./dist/index.mjs, require: ./dist/index.js, types: ./dist/index.d.ts } }如果缺少require字段Node.js 会 fallback 到main字段./dist/index.js但若index.js是 ESM 格式含import就会报错。解决方案确保index.js是 CommonJS 格式或在exports中显式声明require。5.3 Nx 影响分析失效nx affected总是返回空结果这通常是因为 Git 基准线设置错误。nx affected默认使用origin/main作为基准但如果本地origin/main没有更新它会认为没有变更。排查命令# 查看当前分支与 origin/main 的差异 git diff origin/main...HEAD --name-only # 强制更新 origin/main git fetch origin main # 再次运行 affected nx affected --targettest --baseorigin/main独家技巧在 CI 中git fetch可能因网络问题失败。我们在 GitHub Actions 中添加重试逻辑- name: Fetch origin/main run: | for i in {1..3}; do git fetch origin main break || sleep 5 done5.4 semantic-release 未触发发布No version publishedsemantic-release日志显示There are no relevant changes, so no new version is released。原因通常是提交信息不符合 Conventional Commits 规范如git commit -m update。package.json的version字段被手动修改semantic-release要求version始终为0.0.0-semantic-release。release.config.js的branches配置与当前分支不匹配如配置为[main]但推送到了develop。解决方案检查git log --oneline -n 5确认最近提交是否以feat:、fix:开头确保package.json的version是0.0.0-semantic-release核对branches配置。5.5 技术选型避坑清单问题错误做法正确做法原因Skill 依赖管理将axios等库放入dependencies放入peerDependencies并external避免多版本共存减少包体积类型定义在index.ts中直接export interface单独types.ts文件仅含类型清晰分离契约与实现便于消费者只 import 类型测试覆盖率仅测试 happy path必须覆盖throws、null、undefined输入Skill 是基础设施健壮性高于性能CI 缓存不启用 Nx Cloud启用 Nx Cloud 或自建缓存服务器增量构建加速 3-5 倍尤其对affected任务文档生成手写 README使用typedoc自动生成 API 文档typedoc可解析 JSDoc生成交互式文档最后分享一个小技巧在libs目录下创建README.md汇总所有 Skill 的状态版本、维护者、最后更新时间。我们用一个简单的 Node 脚本自动更新// tools/update-readme.ts import * as fs from fs; import * as path from path; const libsDir path.join(__dirname, ../libs); const libs fs.readdirSync(libsDir).filter(dir fs.existsSync(path.join(libsDir, dir, package.json)) ); let content # Agent Skills\n\n| Skill | Version | Last Update |\n|-------|---------|-------------|\n;
返回列表