@wdyy/skills 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,9 +11,14 @@
11
11
  "files": ["SKILL.md", "agents/openai.yaml", "reference/linux-deployment-rules.md", "scripts/validate-deployment-package.mjs", "scripts/validate-deployment-package.test.mjs", "templates/Dockerfile.template", "templates/deploy.sh.template", "templates/docker-compose.blue-green.yml", "templates/nginx-upstream.template.conf"]
12
12
  },
13
13
  {
14
- "name": "wdyy-internal-api-standard",
15
- "description": "规范企业内部 REST API 客户端的认证头、traceId、超时、受控重试、熔断、错误映射、日志和响应校验。Use when DEFINE 阶段识别接口边界、BUILD 阶段调用内部服务或 REVIEW 阶段发现散落 fetch、axios、httpClient 调用时。",
16
- "files": ["SKILL.md", "agents/openai.yaml", "reference/internal-api-rules.md", "scripts/check-raw-http-calls.mjs", "templates/api-client.template.ts", "templates/api-error.template.ts", "templates/api-mock.template.ts"]
14
+ "name": "wdyy-api-standard",
15
+ "description": "根据“获取XX数据”“使用XX接口”等具体需求,定位已登记的接口 reference,并按已确认的接口契约将调用方式写入程序。Use when 需要查询或实现一个已登记接口的数据调用时。",
16
+ "files": ["SKILL.md", "agents/openai.yaml", "reference/api-根据就诊号获取检验数据.md"]
17
+ },
18
+ {
19
+ "name": "wdyy-bug-record",
20
+ "description": "手动汇总当前任务中已解决的 BUG、漏洞和其他非需求问题,并按固定模板追加写入 docs/bug_record.md。",
21
+ "files": ["SKILL.md"]
17
22
  },
18
23
  {
19
24
  "name": "wdyy-logging-standard",
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: wdyy-api-standard
3
+ description: 根据“获取XX数据”“使用XX接口”等具体需求,定位已登记的接口 reference,并按已确认的接口契约将调用方式写入程序。Use when 需要查询或实现一个已登记接口的数据调用时。
4
+ ---
5
+
6
+ # 接口路由
7
+
8
+ ## 用途
9
+
10
+ 本 Skill 只负责根据具体需求定位接口 reference,并将已确认的接口调用方式写入程序。接口明细一律维护在 `reference/` 中,每个接口一个文档。
11
+
12
+ ## 何时使用
13
+
14
+ - 用户描述“获取XX数据”“使用XX接口”或等价的数据查询、接口调用需求时。
15
+ - 需要将已登记接口的 URL、入参和返回值处理方式写入程序时。
16
+
17
+ ## 路由步骤
18
+
19
+ 1. 根据需求中的数据名称、业务对象或接口名称,在 `reference/api-*.md` 中查找候选接口。
20
+ 2. 仅在唯一匹配时读取该 reference,使用其中的接口 URL、JSON 入参和 JSON 返回值编写调用方式。
21
+ 3. 按 reference 的“备注”实现特殊调用要求。
22
+ 4. 若没有匹配接口、存在多个候选接口,或 URL、入参、返回值不完整,先要求补充或确认接口信息;不得编造调用细节。
23
+
24
+ ## 接口文档约定
25
+
26
+ - 一个接口对应一个 `reference/api-接口名称.md` 文件。
27
+ - 文件名去除 `.md` 后必须与“接口名称”字段一致。
28
+ - 每个接口文档必须按以下顺序包含六个一级标题:
29
+
30
+ ```md
31
+ # 接口名称:api-xxx
32
+
33
+ # 接口用途:中文描述
34
+
35
+ # 接口URL:
36
+
37
+ # 入参格式:JSON
38
+
39
+ # 返回值:JSON
40
+
41
+ # 备注:特殊使用方法说明
42
+ ```
43
+
44
+ ## 禁止事项
45
+
46
+ - 不得依据接口名称或业务描述猜测 URL、请求字段、返回字段或特殊调用方式。
47
+ - 不得将多个接口明细重新堆放回本 `SKILL.md`。
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Enterprise API Standard"
3
+ short_description: "Route data requests to documented API references"
4
+ default_prompt: "Use $wdyy-api-standard to find the documented API for this requirement and write its call into the program."
@@ -0,0 +1,25 @@
1
+ # 接口名称:api-根据就诊号获取检验数据
2
+
3
+ # 接口用途:根据就诊号获取检验数据
4
+
5
+ # 接口URL:http://172.26.1.64:8996/api/execute/getLabInfoByVisitId
6
+
7
+ # 入参格式:JSON
8
+
9
+ ```json
10
+ {
11
+ "visitId": "6275033"
12
+ }
13
+ ```
14
+
15
+ # 返回值:JSON
16
+
17
+ ```json
18
+ {
19
+ "code": 200,
20
+ "data": [{}, {}],
21
+ "msg": "success"
22
+ }
23
+ ```
24
+
25
+ # 备注:使用就诊号作为 `visitId` 参数调用;调用前应由业务上下文提供实际就诊号。
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: wdyy-bug-record
3
+ description: 手动汇总当前任务中已解决的 BUG、漏洞和其他非需求问题,并按固定模板追加写入 docs/bug_record.md。
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # BUG 记录
8
+
9
+ 仅在需要沉淀当前任务已解决问题时手动调用本 Skill。
10
+
11
+ ## 记录范围
12
+
13
+ - 仅汇总当前任务中已经解决的 BUG、漏洞、环境、构建、部署或运行异常。
14
+ - 不扫描 Git、Issue、日志或其他项目文件来推断问题。
15
+ - 当前任务没有已解决问题时,明确说明无可记录内容,不写入文件。
16
+ - 不记录密码、令牌、验证码或其他秘密。
17
+
18
+ ## 写入步骤
19
+
20
+ 1. 从当前任务上下文整理每个已解决问题的“问题描述”和“解决方法”。
21
+ 2. 在项目根目录的 `docs/bug_record.md` 末尾追加每条记录;保留既有内容,不覆盖历史。
22
+ 3. 每条记录使用调用当天的日期,并严格采用以下格式:
23
+
24
+ ```md
25
+ - 日期:yyyy-mm-dd
26
+ - 问题描述:
27
+ - 解决方法:
28
+ ```
@@ -2,7 +2,7 @@
2
2
 
3
3
  - 日志为一行 JSON,最小字段见 `logger.template.ts`。
4
4
  - `timestamp` 使用服务器本地时间 `YYYY-MM-DD HH:mm:ss`,精确到秒;不得包含毫秒、`T` 或 `Z`。
5
- - 文件名使用服务器本地时间 `yyyy-mm-dd_hh24-mm-ss.log`;当前文件大于 2MB 时先创建新文件再写入。既有日志文件不得重命名。
5
+ - 文件名使用服务器本地时间 `yyyy-mm-dd_hh24-mm-ss.log`;最新日志文件大于 2MB 时先创建新文件再写入。既有日志文件不得重命名。
6
6
  - 日志仅记录关键业务事件、请求摘要和异常;`params`、`query` 和 `body` 必须裁剪。授权请求可记录实际用户、模式、表和权限等入参。
7
7
  - HTTP 日志必须保留 `statusCode`,且 `result` 在 100–399 时为 `success`、在 400–599 时为 `failure`。
8
8
  - password、token、secret、authorization、databaseUrl、idCard、bankCard 必须从输出中完全移除,不得以占位符输出。数组保留前 500 项;超出时追加 `[TRUNCATED N ITEMS]`。
package/README.md CHANGED
@@ -10,12 +10,13 @@ OpenSpec 单项变更的需求、设计、任务、实
10
10
  .well-known/skills/wdyy-* 数据库、内部接口、日志、部署的专业规则
11
11
  ```
12
12
 
13
- 当前 `.well-known/skills/index.json` 登记 4 个企业 Skill。
13
+ 当前 `.well-known/skills/index.json` 登记 5 个企业 Skill。
14
14
 
15
15
  | Skill | 职责 |
16
16
  |---|---|
17
17
  | `wdyy-database-standard` | PostgreSQL 18 数据库设计、版本化迁移、数据兼容性与 DDL 校验。 |
18
- | `wdyy-internal-api-standard` | 内部 REST 调用的认证、traceId、超时、受控重试、错误映射与响应校验。 |
18
+ | `wdyy-api-standard` | 根据“获取XX数据”“使用XX接口”等需求定位接口 reference,并按已确认契约写入调用方式。 |
19
+ | `wdyy-bug-record` | 手动汇总当前任务已解决的问题,并按模板追加至 `docs/bug_record.md`。 |
19
20
  | `wdyy-logging-standard` | 结构化日志、敏感字段移除、traceId、前端异常上报与日志轮转。 |
20
21
  | `wdyy-deployment-standard` | 人工 SCP 交付、版本化前端资源、Docker 蓝绿发布与回滚。 |
21
22
 
@@ -28,7 +29,19 @@ npm install -g @wdyy/skills
28
29
  wdyy init
29
30
  ```
30
31
 
31
- `wdyy init` 自动执行 `openspec init`,将 4 个受管理 Skill 安装到 `.agents/skills/`。若目标项目已有 `AGENTS.md`,原文件保持不变,命令将生成 `AGENTS_new.md`;每次运行都会覆盖更新 `.agents/skills/wdyy-*`,但不修改同级自定义 Skill。
32
+ `wdyy init` 自动执行 `openspec init`,将 5 个受管理 Skill 安装到 `.agents/skills/`,并在 `docs/bug_record.md` 创建或追加 BUG 记录模板。若目标项目已有 `AGENTS.md`,原文件保持不变,命令将生成 `AGENTS_new.md`;每次运行都会覆盖更新 `.agents/skills/wdyy-*`,但不修改同级自定义 Skill。
33
+
34
+ ### 仅更新已安装的 Skill
35
+
36
+ 发布包含 Skill 更新的新版本后,先升级全局 CLI 包,再在目标项目根目录按需更新一个或全部受管理 Skill:
37
+
38
+ ```bash
39
+ npm install -g @wdyy/skills@latest
40
+ wdyy update-skill wdyy-logging-standard
41
+ wdyy update-skill --all
42
+ ```
43
+
44
+ `wdyy update-skill <skill-name>` 只接受 5 个受管理的 `wdyy-*` Skill,且仅替换指定目录;`--all` 替换全部 5 个受管理 Skill。两种方式都不会写入 `AGENTS.md` 或 `AGENTS_new.md`、追加 `docs/bug_record.md`,也不会执行 `openspec init`。首次初始化或需要重新生成项目规则时,使用 `wdyy init`。
32
45
 
33
46
  中大型变更先使用 `/opsx:explore` 或 `/opsx:propose <change-name>`,在方案确认后使用 `/opsx:apply`;完成验证后使用 `/opsx:archive`。涉及数据库、内部 API、日志、部署时,按 `AGENTS.md` 的路由加载相应企业 Skill。配置仅从环境变量读取,不得提交秘密或生产环境 `.env`。
34
47
 
@@ -37,7 +50,6 @@ wdyy init
37
50
  - Node.js 24、pnpm workspace、TypeScript
38
51
  - Vue 3、Vite、Pinia、Vue Router、Element Plus
39
52
  - NestJS、PostgreSQL 18
40
- - Redis、RabbitMQ 仅在工程师明确确认后启用
41
53
 
42
54
  ## 仓库维护
43
55
 
@@ -46,16 +58,44 @@ wdyy init
46
58
  - 自定义企业 Skill 的目录名和 frontmatter `name` 必须以 `wdyy-` 开头且保持一致。
47
59
  - 只修改 `wdyy-*` Skill;详细且易变的规则应放在对应的 `reference/`,可重复校验应放在 `scripts/`。
48
60
 
49
- ## 名称迁移
61
+ ## 发布到 npm
50
62
 
51
- | 原名称 | 新名称 |
52
- |---|---|
53
- | `enterprise-database-standard` | `wdyy-database-standard` |
54
- | `enterprise-internal-api-standard` | `wdyy-internal-api-standard` |
55
- | `enterprise-logging-standard` | `wdyy-logging-standard` |
56
- | `enterprise-deployment-standard` | `wdyy-deployment-standard` |
63
+ 发布包名为 `@wdyy/skills`,命令行入口为 `wdyy`。发布前不得跳过验证:
64
+
65
+ ```bash
66
+ npm test
67
+ npm run lint
68
+ node --test $(find .well-known/skills -path '*/scripts/*.test.mjs' -print)
69
+ npm pack --dry-run
70
+ ```
71
+
72
+ 首次发布前,登录拥有 `wdyy` 组织 owner 或发布权限的 npm 账号,并在 npm 账户设置中配置安全密钥 2FA(WebAuthn,例如 macOS Touch ID 或硬件安全密钥)。npm 已不支持通过 CLI 新增 TOTP 认证器;不得在仓库、终端历史或文档中记录 access token、验证码或恢复码。
73
+
74
+ 确认身份与组织权限后,发布公开包:
57
75
 
58
- 旧名称不再作为本仓库提供的 Skill 身份。请将项目规则与手动调用更新为相应的 `wdyy-*` 名称。
76
+ ```bash
77
+ npm login
78
+ npm whoami
79
+ npm org ls wdyy
80
+ npm publish --access public
81
+ ```
82
+
83
+ `npm whoami` 返回 `E401` 表示当前 CLI 没有有效登录态,应重新执行 `npm login` 后再发布。已成功登录但 `npm publish --access public` 仍返回 `E404` 时,通常表示当前账号没有 `@wdyy` 组织的包创建或发布权限;请由该组织的 Owner 授予权限后重试。不要通过在命令行中传入 access token、验证码或恢复码来规避认证。
84
+
85
+ 同一包名和版本只能发布一次。后续发布必须先升级版本,再重新执行全部发布前验证:
86
+
87
+ ```bash
88
+ npm version patch
89
+ npm publish --access public
90
+ ```
91
+
92
+ 发布后核验 registry 中的版本,并在干净的目标项目中验证全局安装与初始化:
93
+
94
+ ```bash
95
+ npm view @wdyy/skills version
96
+ npm install -g @wdyy/skills
97
+ wdyy init
98
+ ```
59
99
 
60
100
  ## 验证
61
101
 
package/lib/wdyy-cli.js CHANGED
@@ -1,19 +1,20 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { access, cp, mkdir, mkdtemp, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { access, appendFile, cp, mkdir, mkdtemp, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
 
6
6
  const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
7
7
  const managedSkillNames = [
8
8
  'wdyy-database-standard',
9
- 'wdyy-internal-api-standard',
9
+ 'wdyy-api-standard',
10
+ 'wdyy-bug-record',
10
11
  'wdyy-logging-standard',
11
12
  'wdyy-deployment-standard'
12
13
  ];
13
14
 
14
15
  const generatedAgentsContent = `# 企业开发规则
15
16
 
16
- 本项目使用 wdyy 企业 Skill。新增、更新、删除受管理 Skill 后,必须同步核对项目内 Skill 清单;Skill 目录名必须与 \`SKILL.md\` frontmatter 的 \`name\` 一致。
17
+ 本项目同时使用OpenSpec及企业 Skill统一开发规则及流程。
17
18
 
18
19
  ## 固定技术栈
19
20
 
@@ -21,49 +22,51 @@ const generatedAgentsContent = `# 企业开发规则
21
22
  - 前端:Vue 3、Vite、Pinia、Vue Router、Element Plus。
22
23
  - 后端:NestJS。
23
24
  - 数据库:PostgreSQL 18。
24
- - Redis、RabbitMQ 仅在工程师明确确认后引入。
25
25
 
26
- 默认项目目录为 \`apps/frontend\`、\`apps/backend\`、\`packages\`、\`database/migrations\`、\`deploy/docker\`、\`deploy/nginx\`、\`docs\`、\`scripts\`、\`tests\`。配置仅从环境变量读取;不得提交秘密或生产环境 \`.env\`。
26
+ 默认项目目录为 \`src/frontend\`、\`src/backend\`、\`database\`、\`deploy\`、\`docs\`、\`scripts\`、\`tests\`、\`.env.example\`。
27
27
 
28
- ## OpenSpec 工作流
29
-
30
- 功能新增、跨模块变更、数据模型变更、重构和生产发布必须使用 OpenSpec:不明确时先执行 \`explore\`;明确需求时执行 \`propose <change-name>\`;涉及业务、接口、数据库、部署或不可逆决定时,必须等待工程师确认方案后执行 \`apply\`;完成实现与验证后执行 \`/opsx:archive\`。
28
+ 配置仅从环境变量读取;不得提交秘密或生产环境 \`.env\`。
31
29
 
32
30
  ## Skill 路由
33
31
 
34
32
  | 场景 | 必须使用的 Skill |
35
33
  |---|---|
36
34
  | PostgreSQL 表、DDL、迁移、数据兼容性 | \`wdyy-database-standard\` |
37
- | 服务间 REST 调用、认证、超时、重试、错误映射 | \`wdyy-internal-api-standard\` |
35
+ | 获取已登记接口数据、使用已登记接口 | \`wdyy-api-standard\` |
36
+ | 汇总当前任务已解决的 BUG、漏洞或运行异常 | 手动调用 \`wdyy-bug-record\`,追加写入 \`docs/bug_record.md\` |
38
37
  | 结构化日志、traceId、脱敏、前端异常上报 | \`wdyy-logging-standard\` |
39
38
  | Linux 发布、Docker 蓝绿、Nginx 切流、回滚 | \`wdyy-deployment-standard\` |
40
39
 
41
40
  ## 通用边界
42
41
 
43
- - 项目根目录应提供真实可执行的 \`pnpm dev\`、\`pnpm build\`、\`pnpm test\`、\`pnpm lint\` 命令;\`pnpm dev\` 不得启动或管理数据库、缓存或消息队列。
42
+ - 项目根目录应提供真实可执行的 \`pnpm dev\`、\`pnpm build\`、\`pnpm test\`、\`pnpm lint\` 命令;\`pnpm dev\` 只负责前后端的启动,不得启动或管理数据库。
44
43
  - 代码、配置、迁移、测试与部署文件必须以实际实现为准;不得用模板虚构已实现能力。
45
- - 任何破坏性数据库操作、生产发布、引入基础设施或新外部依赖,必须在 OpenSpec 方案中说明影响并获得工程师确认。
44
+ - 所有需求(如数据库变更、流程变更、功能变更、从零开发项目等),必须先使用\`openspec-explore\`技能确认需求,再根据实际情况,使用\`openspec\`的其它技能。
46
45
  - 不得静默吞错、跳过验证、以兜底逻辑掩盖未决问题。
47
46
  `;
48
47
 
48
+ const bugRecordTemplate = '- 日期:yyyy-mm-dd\n- 问题描述:\n- 解决方法:\n';
49
+
49
50
  function printHelp() {
50
51
  console.log('用法: wdyy init');
51
52
  console.log('在当前项目生成 AGENTS 规则、安装 wdyy Skill,并执行 openspec init。');
53
+ console.log('用法: wdyy update-skill <skill-name> | --all');
54
+ console.log('仅更新当前项目的一个或全部受管理 wdyy Skill,不执行项目初始化。');
52
55
  }
53
56
 
54
- async function installManagedSkills(targetRoot, writtenPaths) {
57
+ async function installManagedSkills(targetRoot, writtenPaths, skillNames = managedSkillNames) {
55
58
  const skillsRoot = join(targetRoot, '.agents', 'skills');
56
59
  await mkdir(skillsRoot, { recursive: true });
57
60
  const temporaryRoot = await mkdtemp(join(skillsRoot, '.wdyy-skills-'));
58
61
 
59
62
  try {
60
- for (const skillName of managedSkillNames) {
63
+ for (const skillName of skillNames) {
61
64
  const source = join(packageRoot, '.well-known', 'skills', skillName);
62
65
  const prepared = join(temporaryRoot, skillName);
63
66
  await cp(source, prepared, { recursive: true, force: true, errorOnExist: false });
64
67
  }
65
68
 
66
- for (const skillName of managedSkillNames) {
69
+ for (const skillName of skillNames) {
67
70
  const destination = join(skillsRoot, skillName);
68
71
  await rm(destination, { recursive: true, force: true });
69
72
  await rename(join(temporaryRoot, skillName), destination);
@@ -89,6 +92,24 @@ async function writeAgentsFile(targetRoot, writtenPaths) {
89
92
  console.log(`已写入项目规则: ${destination}`);
90
93
  }
91
94
 
95
+ async function appendBugRecordTemplate(targetRoot, writtenPaths) {
96
+ const docsRoot = join(targetRoot, 'docs');
97
+ const bugRecordPath = join(docsRoot, 'bug_record.md');
98
+ await mkdir(docsRoot, { recursive: true });
99
+
100
+ let prefix = '';
101
+ try {
102
+ const existing = await readFile(bugRecordPath, 'utf8');
103
+ if (existing.length > 0) prefix = existing.endsWith('\n') ? '\n' : '\n\n';
104
+ } catch (error) {
105
+ if (error.code !== 'ENOENT') throw error;
106
+ }
107
+
108
+ await appendFile(bugRecordPath, `${prefix}${bugRecordTemplate}`, 'utf8');
109
+ writtenPaths.push(bugRecordPath);
110
+ console.log(`已更新 BUG 记录模板: ${bugRecordPath}`);
111
+ }
112
+
92
113
  function initializeOpenSpec(targetRoot, writtenPaths) {
93
114
  const result = spawnSync('openspec', ['init'], { cwd: targetRoot, stdio: 'inherit' });
94
115
  if (result.error || result.status !== 0) {
@@ -108,6 +129,7 @@ export async function run(argumentsList) {
108
129
  try {
109
130
  await writeAgentsFile(process.cwd(), writtenPaths);
110
131
  await installManagedSkills(process.cwd(), writtenPaths);
132
+ await appendBugRecordTemplate(process.cwd(), writtenPaths);
111
133
  const openSpecExitCode = initializeOpenSpec(process.cwd(), writtenPaths);
112
134
  if (openSpecExitCode !== 0) return openSpecExitCode;
113
135
  console.log('wdyy 初始化完成。');
@@ -119,6 +141,30 @@ export async function run(argumentsList) {
119
141
  }
120
142
  }
121
143
 
144
+ if (argumentsList[0] === 'update-skill') {
145
+ const skillArguments = argumentsList.slice(1);
146
+ const skillNames = skillArguments.length === 1 && skillArguments[0] === '--all'
147
+ ? managedSkillNames
148
+ : skillArguments.length === 1 && managedSkillNames.includes(skillArguments[0])
149
+ ? [skillArguments[0]]
150
+ : null;
151
+
152
+ if (!skillNames) {
153
+ printHelp();
154
+ return 1;
155
+ }
156
+
157
+ const writtenPaths = [];
158
+ try {
159
+ await installManagedSkills(process.cwd(), writtenPaths, skillNames);
160
+ return 0;
161
+ } catch (error) {
162
+ console.error(`Skill 更新失败: ${error.message}`);
163
+ if (writtenPaths.length > 0) console.error(`已写入路径:\n${writtenPaths.map((path) => `- ${path}`).join('\n')}`);
164
+ return 1;
165
+ }
166
+ }
167
+
122
168
  printHelp();
123
169
  return argumentsList.length === 1 && ['--help', '-h'].includes(argumentsList[0]) ? 0 : 1;
124
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdyy/skills",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "企业内部开发 Skill 与项目初始化命令",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,60 +0,0 @@
1
- ---
2
- name: wdyy-internal-api-standard
3
- description: 规范企业内部 REST API 客户端的认证头、traceId、超时、受控重试、熔断、错误映射、日志和响应校验。Use when DEFINE 阶段识别接口边界、BUILD 阶段调用内部服务或 REVIEW 阶段发现散落 fetch、axios、httpClient 调用时。
4
- ---
5
-
6
- # 企业内部接口规范
7
-
8
- ## Overview
9
-
10
- 将内部服务调用收敛到可追踪、可验证且有明确失败语义的 REST 客户端边界。
11
-
12
- ## When to Use
13
-
14
- - 识别接口边界、实现内部服务调用或审查业务层 HTTP 调用时。
15
-
16
- ## 协作边界
17
-
18
- 先确认接口契约、错误语义、实现计划和测试要求。本 skill 独立规定企业客户端实现约束,不能替代实现和验证工作。
19
-
20
- ## 输入与输出
21
-
22
- - 输入:接口契约、认证方案、超时预算、错误码、重试安全性和 Mock 场景。
23
- - 输出:统一客户端、接口定义、错误处理、Mock 与测试证据。
24
- - 使用 [客户端模板](templates/api-client.template.ts)、[错误模板](templates/api-error.template.ts) 和 [规则](reference/internal-api-rules.md)。
25
-
26
- ## 执行步骤
27
-
28
- 1. 确认服务边界、认证头、请求/响应 schema、超时预算及幂等条件。
29
- 2. 通过唯一封装客户端发起所有内部调用,注入 traceId 和请求日志。
30
- 3. 仅对明确幂等且可恢复的错误执行有限重试;配置熔断与可观测错误码。
31
- 4. 校验响应结构,映射外部错误为业务可处理的稳定错误。
32
- 5. 用 Mock 覆盖超时、认证失败、无效响应、重试和熔断路径。
33
-
34
- ## 禁止事项
35
-
36
- - 不得在业务代码中散落 `fetch`、`axios` 或原始 `httpClient` 调用。
37
- - 不得对非幂等写操作无条件重试。
38
- - 不得吞掉响应校验、超时、错误码或调用日志。
39
-
40
- ## Red Flags
41
-
42
- - 业务模块直接调用 fetch、axios 或原始 httpClient。
43
- - 未定义幂等性却配置重试,或未校验响应结构。
44
-
45
- ## Verification
46
-
47
- - [ ] 调用均通过封装客户端,traceId 与认证头可验证。
48
- - [ ] 超时、重试上限、熔断与错误码映射有测试。
49
- - [ ] 无效响应不能进入业务逻辑。
50
- - [ ] Mock 覆盖成功及主要异常分支。
51
-
52
- 认证、幂等性或错误语义未定义时,停止调用实现并回到接口契约澄清;不得以无限重试或吞错替代决策。
53
-
54
- ## Common Rationalizations
55
-
56
- | 合理化说法 | 事实 |
57
- |---|---|
58
- | “一个 fetch 不值得封装” | 分散调用无法统一认证、追踪、超时和错误行为。 |
59
- | “重试总会提高成功率” | 非幂等重试会造成重复写入和状态污染。 |
60
- | “内部服务可以信任响应” | 服务边界仍可能发生版本漂移和故障。 |
@@ -1,4 +0,0 @@
1
- interface:
2
- display_name: "Enterprise Internal API Standard"
3
- short_description: "Standardize internal REST client integrations"
4
- default_prompt: "Use $wdyy-internal-api-standard to implement an internal REST API client."
@@ -1,5 +0,0 @@
1
- # 内部 API 规则
2
-
3
- - 认证头、`x-trace-id`、超时、响应 schema 与错误码映射均由统一客户端处理。
4
- - 仅明确幂等请求可有限重试;熔断状态必须可观测。
5
- - 禁止业务层直接调用 fetch、axios 或原始 httpClient。
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFile } from 'node:fs/promises';
3
-
4
- const file = process.argv[2];
5
- if (!file) throw new Error('Pass a business source file');
6
- const content = await readFile(file, 'utf8');
7
- if (/\b(fetch|axios\.|httpClient\.)\s*\(/.test(content)) {
8
- throw new Error(`Raw HTTP call found in ${file}; use the internal API client`);
9
- }
10
- process.stdout.write('no raw HTTP calls\n');
@@ -1,20 +0,0 @@
1
- type RequestOptions = { method: string; path: string; body?: unknown; traceId: string; idempotent?: boolean };
2
-
3
- export class InternalApiClient {
4
- constructor(private readonly baseUrl: string, private readonly token: () => Promise<string>) {}
5
-
6
- async request<T>(options: RequestOptions): Promise<T> {
7
- const response = await fetch(`${this.baseUrl}${options.path}`, {
8
- method: options.method,
9
- headers: {
10
- authorization: `Bearer ${await this.token()}`,
11
- 'content-type': 'application/json',
12
- 'x-trace-id': options.traceId,
13
- },
14
- body: options.body === undefined ? undefined : JSON.stringify(options.body),
15
- signal: AbortSignal.timeout(5_000),
16
- });
17
- if (!response.ok) throw new Error(`INTERNAL_API_${response.status}`);
18
- return await response.json() as T;
19
- }
20
- }
@@ -1,7 +0,0 @@
1
- export class InternalApiError extends Error {
2
- constructor(
3
- readonly code: string,
4
- readonly retryable: boolean,
5
- message: string,
6
- ) { super(message); }
7
- }
@@ -1,8 +0,0 @@
1
- import { http, HttpResponse } from 'msw';
2
-
3
- export const handlers = [
4
- http.get('*/internal/<resource>', ({ request }) => {
5
- if (!request.headers.get('x-trace-id')) return HttpResponse.json({ code: 'TRACE_ID_REQUIRED' }, { status: 400 });
6
- return HttpResponse.json({ data: [] });
7
- }),
8
- ];