ethan-skill 1.11.0 → 1.12.0

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.
Files changed (66) hide show
  1. package/dist/cli/index.js +1019 -1
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/mcp/server.d.ts.map +1 -1
  4. package/dist/mcp/server.js +206 -1
  5. package/dist/mcp/server.js.map +1 -1
  6. package/dist/skills/27-tech-debt.d.ts +3 -0
  7. package/dist/skills/27-tech-debt.d.ts.map +1 -0
  8. package/dist/skills/27-tech-debt.js +149 -0
  9. package/dist/skills/27-tech-debt.js.map +1 -0
  10. package/dist/skills/28-api-mock.d.ts +3 -0
  11. package/dist/skills/28-api-mock.d.ts.map +1 -0
  12. package/dist/skills/28-api-mock.js +272 -0
  13. package/dist/skills/28-api-mock.js.map +1 -0
  14. package/dist/skills/29-data-migration.d.ts +3 -0
  15. package/dist/skills/29-data-migration.d.ts.map +1 -0
  16. package/dist/skills/29-data-migration.js +331 -0
  17. package/dist/skills/29-data-migration.js.map +1 -0
  18. package/dist/skills/30-llm-feature.d.ts +3 -0
  19. package/dist/skills/30-llm-feature.d.ts.map +1 -0
  20. package/dist/skills/30-llm-feature.js +328 -0
  21. package/dist/skills/30-llm-feature.js.map +1 -0
  22. package/dist/skills/31-threat-model.d.ts +3 -0
  23. package/dist/skills/31-threat-model.d.ts.map +1 -0
  24. package/dist/skills/31-threat-model.js +240 -0
  25. package/dist/skills/31-threat-model.js.map +1 -0
  26. package/dist/skills/32-green-code.d.ts +3 -0
  27. package/dist/skills/32-green-code.d.ts.map +1 -0
  28. package/dist/skills/32-green-code.js +346 -0
  29. package/dist/skills/32-green-code.js.map +1 -0
  30. package/dist/skills/33-service-catalog.d.ts +3 -0
  31. package/dist/skills/33-service-catalog.d.ts.map +1 -0
  32. package/dist/skills/33-service-catalog.js +334 -0
  33. package/dist/skills/33-service-catalog.js.map +1 -0
  34. package/dist/skills/34-mobile-review.d.ts +3 -0
  35. package/dist/skills/34-mobile-review.d.ts.map +1 -0
  36. package/dist/skills/34-mobile-review.js +390 -0
  37. package/dist/skills/34-mobile-review.js.map +1 -0
  38. package/dist/skills/35-data-pipeline.d.ts +3 -0
  39. package/dist/skills/35-data-pipeline.d.ts.map +1 -0
  40. package/dist/skills/35-data-pipeline.js +392 -0
  41. package/dist/skills/35-data-pipeline.js.map +1 -0
  42. package/dist/skills/36-ml-experiment.d.ts +3 -0
  43. package/dist/skills/36-ml-experiment.d.ts.map +1 -0
  44. package/dist/skills/36-ml-experiment.js +415 -0
  45. package/dist/skills/36-ml-experiment.js.map +1 -0
  46. package/dist/skills/index.d.ts +10 -0
  47. package/dist/skills/index.d.ts.map +1 -1
  48. package/dist/skills/index.js +41 -1
  49. package/dist/skills/index.js.map +1 -1
  50. package/dist/skills/pipeline.d.ts.map +1 -1
  51. package/dist/skills/pipeline.js +35 -0
  52. package/dist/skills/pipeline.js.map +1 -1
  53. package/dist/skills/skills.test.js +3 -3
  54. package/dist/skills/skills.test.js.map +1 -1
  55. package/package.json +1 -1
  56. package/rules/claude-code/CLAUDE.md +2963 -3
  57. package/rules/cline/.clinerules +2805 -2
  58. package/rules/codebuddy/CODEBUDDY.md +2913 -2
  59. package/rules/continue/.continuerules +2805 -2
  60. package/rules/copilot/copilot-instructions.md +2883 -2
  61. package/rules/cursor/.cursorrules +2952 -2
  62. package/rules/cursor/smart-flow.mdc +2952 -2
  63. package/rules/jetbrains/smart-flow.md +2883 -2
  64. package/rules/lingma/smart-flow.md +2904 -3
  65. package/rules/windsurf/.windsurf/rules/smart-flow.md +2884 -3
  66. package/rules/zed/smart-flow.rules +2794 -1
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiMockSkill = void 0;
4
+ exports.apiMockSkill = {
5
+ id: 'api-mock',
6
+ name: 'API Mock 服务',
7
+ nameEn: 'api_mock',
8
+ order: 28,
9
+ description: '根据接口定义生成 MSW/JSON Server Mock 配置,支持动态数据与边界场景模拟',
10
+ descriptionEn: 'Generate MSW/JSON Server mock configs from API specs, supporting dynamic data and edge case simulation',
11
+ detailDescription: `前后端并行开发的核心痛点是接口未就绪时前端无法推进。
12
+ 本 Skill 帮助团队快速搭建 Mock 服务:从 OpenAPI/Swagger 或接口描述生成 MSW handlers、
13
+ JSON Server 配置,集成 faker.js 生成真实感测试数据,并模拟网络错误、超时、边界场景,
14
+ 让前端开发完全不依赖后端进度。`,
15
+ triggers: [
16
+ 'api mock',
17
+ 'mock service',
18
+ 'mock 服务',
19
+ '接口 mock',
20
+ 'msw',
21
+ 'json server',
22
+ 'mock 数据',
23
+ '接口模拟',
24
+ '@ethan api-mock',
25
+ '/api-mock',
26
+ ],
27
+ steps: [
28
+ {
29
+ title: '1. 接口分析与 Mock 范围确定',
30
+ content: `分析需要 Mock 的接口,建立清单:
31
+
32
+ **接口信息收集**
33
+ - OpenAPI/Swagger 文档路径(如 \`api/swagger.json\`)
34
+ - 手工接口描述(方法、路径、请求/响应结构)
35
+ - 需要模拟的特殊场景(401/403/500/超时/慢响应)
36
+
37
+ **Mock 范围分类**
38
+ \`\`\`
39
+ 📋 待 Mock 接口清单
40
+ ├── 认证接口
41
+ │ ├── POST /api/auth/login ← 成功 / 密码错误 / 账号锁定
42
+ │ └── POST /api/auth/refresh ← 成功 / token 过期
43
+ ├── 用户接口
44
+ │ ├── GET /api/users ← 列表 / 空列表 / 分页
45
+ │ └── GET /api/users/:id ← 成功 / 404
46
+ └── 业务接口
47
+ ├── POST /api/orders ← 成功 / 库存不足 / 支付失败
48
+ └── GET /api/orders?status=... ← 各状态过滤
49
+ \`\`\`
50
+
51
+ **输出**:接口 Mock 清单(含边界场景枚举)`,
52
+ },
53
+ {
54
+ title: '2. Mock 方案选型',
55
+ content: `根据项目特点选择最合适的 Mock 方案:
56
+
57
+ **选型矩阵**
58
+
59
+ | 方案 | 适用场景 | 优点 | 缺点 |
60
+ |------|---------|------|------|
61
+ | **MSW(推荐)** | React/Vue 前端项目 | 拦截真实网络请求,零侵入,支持 Jest/Vitest | 需要 Service Worker |
62
+ | **JSON Server** | REST API 快速原型 | 零代码,自动 CRUD | 功能有限,不支持复杂逻辑 |
63
+ | **Mirage.js** | Ember/复杂 SPA | 内置数据库,关系模型支持 | 包较大,配置复杂 |
64
+ | **Nock** | Node.js 单元测试 | 精确控制 HTTP 请求 | 仅限 Node 环境 |
65
+
66
+ **推荐组合**:
67
+ - 开发阶段:**MSW**(浏览器端拦截)
68
+ - 单元测试:**MSW + @mswjs/data**(内存数据库)
69
+ - 快速原型:**JSON Server**(5分钟启动)
70
+
71
+ **输出**:选型决策 + 安装命令`,
72
+ },
73
+ {
74
+ title: '3. MSW handlers 生成',
75
+ content: `生成 MSW(Mock Service Worker)拦截处理器:
76
+
77
+ **安装**
78
+ \`\`\`bash
79
+ npm install msw --save-dev
80
+ npx msw init public/ --save
81
+ \`\`\`
82
+
83
+ **handlers.ts 模板**
84
+ \`\`\`typescript
85
+ // src/mocks/handlers.ts
86
+ import { http, HttpResponse, delay } from 'msw';
87
+ import { faker } from '@faker-js/faker';
88
+
89
+ export const handlers = [
90
+ // ─── 认证接口 ──────────────────────────────────────
91
+ http.post('/api/auth/login', async ({ request }) => {
92
+ const { email, password } = await request.json() as any;
93
+
94
+ // 模拟特殊场景
95
+ if (password === 'wrong') {
96
+ return HttpResponse.json(
97
+ { code: 401, message: '密码错误' },
98
+ { status: 401 }
99
+ );
100
+ }
101
+ if (email === 'locked@test.com') {
102
+ return HttpResponse.json(
103
+ { code: 423, message: '账号已锁定,请联系管理员' },
104
+ { status: 423 }
105
+ );
106
+ }
107
+
108
+ // 正常响应
109
+ return HttpResponse.json({
110
+ token: faker.string.uuid(),
111
+ user: { id: faker.string.uuid(), email, name: faker.person.fullName() },
112
+ });
113
+ }),
114
+
115
+ // ─── 用户列表(分页)──────────────────────────────
116
+ http.get('/api/users', ({ request }) => {
117
+ const url = new URL(request.url);
118
+ const page = Number(url.searchParams.get('page') ?? 1);
119
+ const pageSize = Number(url.searchParams.get('pageSize') ?? 10);
120
+
121
+ const total = 87;
122
+ const items = Array.from({ length: Math.min(pageSize, total - (page - 1) * pageSize) }, () => ({
123
+ id: faker.string.uuid(),
124
+ name: faker.person.fullName(),
125
+ email: faker.internet.email(),
126
+ createdAt: faker.date.past().toISOString(),
127
+ }));
128
+
129
+ return HttpResponse.json({ items, total, page, pageSize });
130
+ }),
131
+
132
+ // ─── 慢响应模拟 ────────────────────────────────────
133
+ http.get('/api/slow-endpoint', async () => {
134
+ await delay(3000); // 模拟 3 秒延迟
135
+ return HttpResponse.json({ data: 'slow response' });
136
+ }),
137
+
138
+ // ─── 网络错误模拟 ──────────────────────────────────
139
+ http.get('/api/network-error', () => {
140
+ return HttpResponse.error(); // 模拟网络断开
141
+ }),
142
+ ];
143
+ \`\`\`
144
+
145
+ **browser.ts(浏览器初始化)**
146
+ \`\`\`typescript
147
+ // src/mocks/browser.ts
148
+ import { setupWorker } from 'msw/browser';
149
+ import { handlers } from './handlers';
150
+ export const worker = setupWorker(...handlers);
151
+ \`\`\`
152
+
153
+ **server.ts(Node.js/测试环境)**
154
+ \`\`\`typescript
155
+ // src/mocks/server.ts
156
+ import { setupServer } from 'msw/node';
157
+ import { handlers } from './handlers';
158
+ export const server = setupServer(...handlers);
159
+ \`\`\`
160
+
161
+ **输出**:完整 handlers.ts + browser.ts + server.ts`,
162
+ },
163
+ {
164
+ title: '4. JSON Server 配置',
165
+ content: `生成 JSON Server 快速 REST Mock 配置:
166
+
167
+ **安装与启动**
168
+ \`\`\`bash
169
+ npm install json-server --save-dev
170
+ # 启动:json-server --watch db.json --port 3001 --routes routes.json
171
+ \`\`\`
172
+
173
+ **db.json(数据库)**
174
+ \`\`\`json
175
+ {
176
+ "users": [
177
+ { "id": "1", "name": "张三", "email": "zhang@test.com", "role": "admin" },
178
+ { "id": "2", "name": "李四", "email": "li@test.com", "role": "user" }
179
+ ],
180
+ "orders": [
181
+ { "id": "101", "userId": "1", "status": "pending", "amount": 299.00, "createdAt": "2024-01-15" },
182
+ { "id": "102", "userId": "2", "status": "completed", "amount": 599.00, "createdAt": "2024-01-16" }
183
+ ],
184
+ "products": [
185
+ { "id": "P001", "name": "商品A", "price": 99.00, "stock": 100 }
186
+ ]
187
+ }
188
+ \`\`\`
189
+
190
+ **routes.json(路由重写)**
191
+ \`\`\`json
192
+ {
193
+ "/api/*": "/$1",
194
+ "/api/v1/*": "/$1",
195
+ "/api/users/:id/orders": "/orders?userId=:id"
196
+ }
197
+ \`\`\`
198
+
199
+ **package.json 脚本**
200
+ \`\`\`json
201
+ {
202
+ "scripts": {
203
+ "mock": "json-server --watch src/mocks/db.json --port 3001 --routes src/mocks/routes.json",
204
+ "dev:mock": "concurrently \\"npm run mock\\" \\"npm run dev\\""
205
+ }
206
+ }
207
+ \`\`\`
208
+
209
+ **输出**:db.json + routes.json + npm 脚本`,
210
+ },
211
+ {
212
+ title: '5. 动态数据与边界场景策略',
213
+ content: `用 faker.js 生成真实感数据,覆盖各类边界场景:
214
+
215
+ **faker.js 常用生成器速查**
216
+ \`\`\`typescript
217
+ import { faker } from '@faker-js/faker/locale/zh_CN'; // 中文数据
218
+
219
+ // 个人信息
220
+ faker.person.fullName() // 王小明
221
+ faker.internet.email() // user@example.com
222
+ faker.phone.number() // 138-1234-5678
223
+ faker.date.birthdate() // 生日
224
+
225
+ // 地址
226
+ faker.location.city() // 上海
227
+ faker.location.streetAddress() // 延安路 123 号
228
+
229
+ // 业务数据
230
+ faker.string.uuid() // UUID
231
+ faker.number.int({ min:1, max:100 }) // 随机整数
232
+ faker.helpers.arrayElement(['pending', 'active', 'closed']) // 随机枚举
233
+ faker.helpers.multiple(() => faker.person.fullName(), { count: 10 }) // 批量生成
234
+ \`\`\`
235
+
236
+ **边界场景 Checklist**
237
+ - [ ] 空列表(返回 \`{ items: [], total: 0 }\`)
238
+ - [ ] 单条数据(边界分页)
239
+ - [ ] 超长字符串(名称 > 100 字符)
240
+ - [ ] 特殊字符(\`<script>\`、SQL 注入字符串)
241
+ - [ ] 极大数字(金额 = 999999999.99)
242
+ - [ ] 时区边界(UTC+8 vs UTC)
243
+ - [ ] 并发响应竞态(两个请求同时返回)
244
+ - [ ] 401 token 过期 → 自动刷新
245
+ - [ ] 503 服务不可用 → 降级 UI
246
+
247
+ **测试集成(Vitest)**
248
+ \`\`\`typescript
249
+ // setupTests.ts
250
+ import { beforeAll, afterEach, afterAll } from 'vitest';
251
+ import { server } from './mocks/server';
252
+
253
+ beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }));
254
+ afterEach(() => server.resetHandlers()); // 每个测试后重置
255
+ afterAll(() => server.close());
256
+ \`\`\`
257
+
258
+ **输出**:faker 数据工厂函数 + 边界场景 handlers + 测试集成配置`,
259
+ },
260
+ ],
261
+ outputFormat: 'Mock 方案选型报告 + MSW handlers.ts + JSON Server db.json/routes.json + faker 数据工厂 + 边界场景 Checklist + 测试集成配置',
262
+ examples: [],
263
+ notes: [
264
+ 'MSW 是目前最推荐的前端 Mock 方案,可在浏览器和 Node.js 两种环境无缝切换',
265
+ 'Mock 数据应尽量真实(使用 faker.js),避免 "test"/"demo" 等无意义数据干扰开发体验',
266
+ '边界场景的 Mock 与正常流程同等重要,建议用命名 handler(override)覆写特定测试场景',
267
+ '生产环境应通过环境变量完全关闭 Mock,避免意外拦截真实请求',
268
+ ],
269
+ category: '执行侧',
270
+ nextSkill: 'unit-testing',
271
+ };
272
+ //# sourceMappingURL=28-api-mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"28-api-mock.js","sourceRoot":"","sources":["../../src/skills/28-api-mock.ts"],"names":[],"mappings":";;;AAEa,QAAA,YAAY,GAAoB;IAC3C,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,aAAa;IACnB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,EAAE;IACT,WAAW,EAAE,gDAAgD;IAC7D,aAAa,EAAE,wGAAwG;IACvH,iBAAiB,EAAE;;;gBAGL;IACd,QAAQ,EAAE;QACR,UAAU;QACV,cAAc;QACd,SAAS;QACT,SAAS;QACT,KAAK;QACL,aAAa;QACb,SAAS;QACT,MAAM;QACN,iBAAiB;QACjB,WAAW;KACZ;IACD,KAAK,EAAE;QACL;YACE,KAAK,EAAE,oBAAoB;YAC3B,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;2BAqBY;SACtB;QACD;YACE,KAAK,EAAE,cAAc;YACrB,OAAO,EAAE;;;;;;;;;;;;;;;;mBAgBI;SACd;QACD;YACE,KAAK,EAAE,oBAAoB;YAC3B,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CAsFgC;SAC1C;QACD;YACE,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sCA4CuB;SACjC;QACD;YACE,KAAK,EAAE,gBAAgB;YACvB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6CA6C8B;SACxC;KACF;IACD,YAAY,EAAE,wGAAwG;IACtH,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE;QACL,+CAA+C;QAC/C,yDAAyD;QACzD,sDAAsD;QACtD,iCAAiC;KAClC;IACD,QAAQ,EAAE,KAAK;IACf,SAAS,EAAE,cAAc;CAC1B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SkillDefinition } from './types';
2
+ export declare const dataMigrationSkill: SkillDefinition;
3
+ //# sourceMappingURL=29-data-migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"29-data-migration.d.ts","sourceRoot":"","sources":["../../src/skills/29-data-migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,eAAO,MAAM,kBAAkB,EAAE,eAsUhC,CAAC"}
@@ -0,0 +1,331 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dataMigrationSkill = void 0;
4
+ exports.dataMigrationSkill = {
5
+ id: 'data-migration',
6
+ name: '数据迁移助手',
7
+ nameEn: 'data_migration',
8
+ order: 29,
9
+ description: '评估迁移风险,生成 UP/DOWN 双向脚本,制定零停机迁移与回滚方案',
10
+ descriptionEn: 'Assess migration risks, generate bidirectional UP/DOWN scripts, design zero-downtime migration and rollback plans',
11
+ detailDescription: `数据迁移是研发中风险最高的操作之一:一旦出错可能导致数据丢失或服务中断。
12
+ 本 Skill 提供系统化的迁移方法论:从评估阶段的风险识别,到生成双向迁移脚本,
13
+ 再到零停机迁移的"扩列→双写→切流→清旧"四步法,每个环节都有完整的验证与回滚手段,
14
+ 让数据迁移从"胆战心惊"变为"有序可控"。`,
15
+ triggers: [
16
+ '数据迁移',
17
+ 'data migration',
18
+ 'schema migration',
19
+ 'db migration',
20
+ '数据库迁移',
21
+ 'migrate database',
22
+ 'migration script',
23
+ '迁移脚本',
24
+ '@ethan data-migration',
25
+ '/data-migration',
26
+ ],
27
+ steps: [
28
+ {
29
+ title: '1. 迁移评估',
30
+ content: `在编写任何脚本之前,先全面评估迁移的范围和风险:
31
+
32
+ **评估清单**
33
+
34
+ | 维度 | 问题 | 影响 |
35
+ |------|------|------|
36
+ | **数据量** | 涉及多少行/多少 GB? | 决定迁移时长和窗口 |
37
+ | **Schema 变更** | 新增/修改/删除了哪些列? | 影响向后兼容性 |
38
+ | **外键约束** | 是否有级联影响? | 需要临时禁用约束 |
39
+ | **业务流量** | 高峰期 QPS 是多少? | 影响迁移策略选择 |
40
+ | **停机容忍** | 是否接受停机?接受多长? | 决定是否需要零停机方案 |
41
+ | **数据一致性** | 允许最终一致性还是强一致? | 影响双写策略 |
42
+
43
+ **Schema 变更对比**
44
+ \`\`\`sql
45
+ -- 变更前
46
+ CREATE TABLE users (
47
+ id BIGINT PRIMARY KEY,
48
+ username VARCHAR(50),
49
+ created_at TIMESTAMP
50
+ );
51
+
52
+ -- 变更后(新增 email 列,重命名 username → name)
53
+ CREATE TABLE users (
54
+ id BIGINT PRIMARY KEY,
55
+ name VARCHAR(100), -- 原 username,扩大长度
56
+ email VARCHAR(255), -- 新增,NOT NULL 需要默认值
57
+ created_at TIMESTAMP,
58
+ updated_at TIMESTAMP -- 新增审计列
59
+ );
60
+ \`\`\`
61
+
62
+ **停机时间估算**
63
+ \`\`\`
64
+ 数据行数: 5,000,000
65
+ 迁移速度: ~10,000 行/秒(受磁盘 IO 限制)
66
+ 估算时长: 5,000,000 ÷ 10,000 = 500 秒 ≈ 8.3 分钟
67
+ 建议窗口: 低峰期(凌晨 2:00-4:00)
68
+ \`\`\`
69
+
70
+ **输出**:迁移评估报告(范围 + 风险 + 时长估算 + 策略选择)`,
71
+ },
72
+ {
73
+ title: '2. 迁移脚本生成(UP/DOWN)',
74
+ content: `生成可逆的双向迁移脚本:
75
+
76
+ **Knex.js 迁移模板(Node.js)**
77
+ \`\`\`typescript
78
+ // migrations/20240115_add_email_rename_username.ts
79
+ import { Knex } from 'knex';
80
+
81
+ export async function up(knex: Knex): Promise<void> {
82
+ await knex.schema.alterTable('users', (table) => {
83
+ // 1. 新增列(先加,允许 NULL,稍后回填)
84
+ table.string('name', 100).nullable();
85
+ table.string('email', 255).nullable();
86
+ table.timestamp('updated_at').nullable();
87
+ });
88
+
89
+ // 2. 数据回填(分批处理,避免锁表)
90
+ const BATCH_SIZE = 1000;
91
+ let offset = 0;
92
+ while (true) {
93
+ const rows = await knex('users')
94
+ .select('id', 'username')
95
+ .limit(BATCH_SIZE)
96
+ .offset(offset);
97
+ if (rows.length === 0) break;
98
+
99
+ await knex('users')
100
+ .whereIn('id', rows.map((r) => r.id))
101
+ .update((row: any) => ({ name: row.username }));
102
+
103
+ offset += BATCH_SIZE;
104
+ }
105
+
106
+ // 3. 添加约束
107
+ await knex.schema.alterTable('users', (table) => {
108
+ table.string('name', 100).notNullable().alter();
109
+ table.dropColumn('username'); // 危险操作!确认数据回填完成后执行
110
+ });
111
+ }
112
+
113
+ export async function down(knex: Knex): Promise<void> {
114
+ // 回滚:恢复 username 列
115
+ await knex.schema.alterTable('users', (table) => {
116
+ table.string('username', 50).nullable();
117
+ table.dropColumn('name');
118
+ table.dropColumn('email');
119
+ table.dropColumn('updated_at');
120
+ });
121
+
122
+ // 回填 username(从备份表恢复)
123
+ await knex.raw('UPDATE users u JOIN users_backup b ON u.id = b.id SET u.username = b.username');
124
+ }
125
+ \`\`\`
126
+
127
+ **Flyway SQL 迁移模板**
128
+ \`\`\`sql
129
+ -- V20240115__add_email_rename_username.sql
130
+ BEGIN;
131
+
132
+ -- 阶段1:新增列
133
+ ALTER TABLE users ADD COLUMN name VARCHAR(100);
134
+ ALTER TABLE users ADD COLUMN email VARCHAR(255);
135
+ ALTER TABLE users ADD COLUMN updated_at TIMESTAMP DEFAULT NOW();
136
+
137
+ -- 阶段2:数据回填
138
+ UPDATE users SET name = username WHERE name IS NULL;
139
+
140
+ -- 阶段3:添加约束(回填完成后)
141
+ ALTER TABLE users ALTER COLUMN name SET NOT NULL;
142
+ CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
143
+
144
+ COMMIT;
145
+ \`\`\`
146
+
147
+ **输出**:可执行的 UP/DOWN 迁移脚本`,
148
+ },
149
+ {
150
+ title: '3. 数据验证策略',
151
+ content: `迁移前后必须验证数据完整性:
152
+
153
+ **三阶段验证**
154
+
155
+ **① 迁移前基线(Baseline)**
156
+ \`\`\`sql
157
+ -- 记录迁移前状态
158
+ CREATE TABLE migration_baseline AS
159
+ SELECT
160
+ COUNT(*) as total_rows,
161
+ COUNT(DISTINCT id) as unique_ids,
162
+ SUM(CASE WHEN email IS NOT NULL THEN 1 ELSE 0 END) as non_null_email,
163
+ MD5(STRING_AGG(id::text, ',' ORDER BY id)) as row_checksum
164
+ FROM users;
165
+ \`\`\`
166
+
167
+ **② 迁移后验证(Post-Migration)**
168
+ \`\`\`sql
169
+ -- 行数对比
170
+ SELECT
171
+ (SELECT COUNT(*) FROM users) as after_count,
172
+ (SELECT total_rows FROM migration_baseline) as before_count,
173
+ (SELECT COUNT(*) FROM users) - (SELECT total_rows FROM migration_baseline) as diff;
174
+
175
+ -- 关键字段空值检查
176
+ SELECT COUNT(*) as null_names FROM users WHERE name IS NULL;
177
+ SELECT COUNT(*) as null_emails FROM users WHERE email IS NULL;
178
+
179
+ -- 数据一致性抽样(对比10%数据)
180
+ SELECT u.id, u.name, b.username
181
+ FROM users u
182
+ JOIN users_backup b ON u.id = b.id
183
+ WHERE u.name != b.username
184
+ LIMIT 100;
185
+ \`\`\`
186
+
187
+ **③ 业务验证(Smoke Test)**
188
+ \`\`\`bash
189
+ # 验证核心业务接口
190
+ curl -X POST /api/auth/login -d '{"email":"test@example.com","password":"xxx"}'
191
+ curl -X GET /api/users/1
192
+ curl -X POST /api/orders -d '{"userId":1,"items":[...]}'
193
+ \`\`\`
194
+
195
+ **验证通过标准**
196
+ - [ ] 行数差异为 0(或符合预期的新增/删除)
197
+ - [ ] 关键字段 NULL 率为 0
198
+ - [ ] 数据抽样一致性 100%
199
+ - [ ] Smoke Test 全部通过
200
+ - [ ] 数据库慢查询无异常增加
201
+
202
+ **输出**:验证脚本套件 + 通过标准 Checklist`,
203
+ },
204
+ {
205
+ title: '4. 回滚方案',
206
+ content: `制定多层次回滚机制,确保任何阶段都可以安全撤退:
207
+
208
+ **三级回滚策略**
209
+
210
+ **Level 1:脚本级回滚(最快,< 5 分钟)**
211
+ \`\`\`bash
212
+ # 直接执行 DOWN 脚本
213
+ npx knex migrate:down
214
+ # 或
215
+ flyway undo
216
+ \`\`\`
217
+ 适用条件:迁移后立即发现问题,数据量小
218
+
219
+ **Level 2:备份恢复(中速,5-30 分钟)**
220
+ \`\`\`bash
221
+ # 迁移前创建备份(必须!)
222
+ pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME \
223
+ --table=users \
224
+ -f backup_users_$(date +%Y%m%d_%H%M%S).sql
225
+
226
+ # 恢复
227
+ psql -h $DB_HOST -U $DB_USER -d $DB_NAME < backup_users_20240115_020000.sql
228
+ \`\`\`
229
+
230
+ **Level 3:蓝绿数据库(最可靠,但成本高)**
231
+ \`\`\`
232
+ Green DB(新)→ 迁移成功 → 流量切到 Green
233
+ Blue DB(旧)→ 保留 72 小时 → 确认无问题后关闭
234
+ \`\`\`
235
+
236
+ **回滚决策树**
237
+ \`\`\`
238
+ 迁移执行中...
239
+ ├── 数据验证失败?
240
+ │ └── YES → 立即执行 Level 1 回滚(DOWN 脚本)
241
+ ├── 业务 Smoke Test 失败?
242
+ │ └── YES → Level 1 回滚 → 分析根因
243
+ └── 迁移后 24 小时内发现数据异常?
244
+ └── YES → Level 2 回滚(备份恢复)→ 通知用户
245
+ \`\`\`
246
+
247
+ **输出**:多级回滚方案 + 回滚决策树 + 备份命令`,
248
+ },
249
+ {
250
+ title: '5. 零停机迁移四步法',
251
+ content: `对于无法停机的生产系统,使用"扩列→双写→切流→清旧"四步法:
252
+
253
+ **背景**:将 \`username\` 列重命名为 \`name\`,同时新增 \`email\` 列
254
+
255
+ **Step 1:扩列(向后兼容)**
256
+ \`\`\`sql
257
+ -- 新增 name 和 email 列,保留 username(双列共存)
258
+ ALTER TABLE users ADD COLUMN name VARCHAR(100);
259
+ ALTER TABLE users ADD COLUMN email VARCHAR(255);
260
+ -- 应用代码:只读 username,不写 name(暂不变更代码)
261
+ \`\`\`
262
+
263
+ **Step 2:双写 + 数据回填**
264
+ \`\`\`typescript
265
+ // 应用代码更新:同时写入 username 和 name
266
+ await db.users.update({
267
+ where: { id },
268
+ data: {
269
+ username: newName, // 旧列
270
+ name: newName, // 新列(同步写入)
271
+ email: email,
272
+ },
273
+ });
274
+
275
+ // 异步回填历史数据(后台任务,不影响主流程)
276
+ async function backfillNames() {
277
+ let cursor = 0;
278
+ while (true) {
279
+ const rows = await db.users.findMany({
280
+ where: { name: null },
281
+ take: 1000,
282
+ });
283
+ if (rows.length === 0) break;
284
+ await db.users.updateMany({
285
+ where: { id: { in: rows.map(r => r.id) } },
286
+ data: rows.map(r => ({ name: r.username })),
287
+ });
288
+ }
289
+ }
290
+ \`\`\`
291
+
292
+ **Step 3:切流(读取切换到新列)**
293
+ \`\`\`typescript
294
+ // 确认回填完成后,切换读取源
295
+ // 应用代码:读 name,写 name(不再写 username)
296
+ const user = await db.users.findUnique({
297
+ select: { id: true, name: true, email: true }, // 不再 select username
298
+ });
299
+ \`\`\`
300
+
301
+ **Step 4:清旧(移除废弃列)**
302
+ \`\`\`sql
303
+ -- 确认应用代码已完全切换(观察 1-2 周)
304
+ ALTER TABLE users DROP COLUMN username;
305
+ -- 观察指标:慢查询、错误日志中是否有 username 相关错误
306
+ \`\`\`
307
+
308
+ **关键时间线**
309
+ \`\`\`
310
+ Day 0: Step 1(扩列)→ 部署新版本(双写)
311
+ Day 1: Step 2(确认双写正常,后台回填完成)
312
+ Day 3: Step 3(切换读取到新列)→ 部署
313
+ Day 14: Step 4(确认无问题,删除旧列)
314
+ \`\`\`
315
+
316
+ **输出**:零停机迁移四步代码示例 + 时间线 + 监控指标`,
317
+ },
318
+ ],
319
+ outputFormat: '迁移评估报告 + UP/DOWN 迁移脚本 + 数据验证 SQL 套件 + 多级回滚方案 + 零停机迁移四步代码示例',
320
+ examples: [],
321
+ notes: [
322
+ '数据迁移前必须备份,无论多紧急。备份是回滚的最后防线',
323
+ '分批处理大表(BATCH_SIZE = 1000-5000),避免长事务和表锁',
324
+ '生产迁移应先在测试环境全流程演练,记录实际耗时',
325
+ '零停机迁移每个步骤之间至少观察 24 小时,确认监控指标正常再进行下一步',
326
+ 'DROP COLUMN 是不可逆操作,在确认切换完成前绝对不要执行',
327
+ ],
328
+ category: '执行侧',
329
+ nextSkill: 'database-optimize',
330
+ };
331
+ //# sourceMappingURL=29-data-migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"29-data-migration.js","sourceRoot":"","sources":["../../src/skills/29-data-migration.ts"],"names":[],"mappings":";;;AAEa,QAAA,kBAAkB,GAAoB;IACjD,EAAE,EAAE,gBAAgB;IACpB,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,gBAAgB;IACxB,KAAK,EAAE,EAAE;IACT,WAAW,EAAE,qCAAqC;IAClD,aAAa,EAAE,mHAAmH;IAClI,iBAAiB,EAAE;;;sBAGC;IACpB,QAAQ,EAAE;QACR,MAAM;QACN,gBAAgB;QAChB,kBAAkB;QAClB,cAAc;QACd,OAAO;QACP,kBAAkB;QAClB,kBAAkB;QAClB,MAAM;QACN,uBAAuB;QACvB,iBAAiB;KAClB;IACD,KAAK,EAAE;QACL;YACE,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qCAwCsB;SAChC;QACD;YACE,KAAK,EAAE,oBAAoB;YAC3B,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAyEU;SACpB;QACD;YACE,KAAK,EAAE,WAAW;YAClB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAmDgB;SAC1B;QACD;YACE,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAyCc;SACxB;QACD;YACE,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAiEiB;SAC3B;KACF;IACD,YAAY,EAAE,4DAA4D;IAC1E,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE;QACL,4BAA4B;QAC5B,yCAAyC;QACzC,yBAAyB;QACzB,sCAAsC;QACtC,mCAAmC;KACpC;IACD,QAAQ,EAAE,KAAK;IACf,SAAS,EAAE,mBAAmB;CAC/B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SkillDefinition } from './types';
2
+ export declare const llmFeatureSkill: SkillDefinition;
3
+ //# sourceMappingURL=30-llm-feature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"30-llm-feature.d.ts","sourceRoot":"","sources":["../../src/skills/30-llm-feature.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,eAAO,MAAM,eAAe,EAAE,eAmU7B,CAAC"}