@wwkit/harness 1.0.27 → 1.0.29

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 (38) hide show
  1. package/bin/index.js +10 -0
  2. package/package.json +3 -2
  3. package/skills/extract/SKILL.md +43 -68
  4. package/skills/extract/references/detail.md +1 -1
  5. package/skills/extract/references/list.md +3 -3
  6. package/skills/extract/references/navi.md +1 -1
  7. package/skills/jstest/SKILL.md +561 -0
  8. package/skills/jstest/references/case-create.md +327 -0
  9. package/skills/jstest/references/case-fix.md +273 -0
  10. package/skills/jstest/references/config.md +148 -0
  11. package/skills/jstest/references/coverage-analyze.md +247 -0
  12. package/skills/jstest/references/env-ensure.md +210 -0
  13. package/skills/jstest/references/execute.md +168 -0
  14. package/skills/jstest/references/sample.md +166 -0
  15. package/skills/jstest/references/scoring-rules.md +75 -0
  16. package/skills/jstest/references/src/jstest-sample/Calculator.js +80 -0
  17. package/skills/jstest/references/src/jstest-sample/ConfigManager.js +72 -0
  18. package/skills/jstest/references/src/jstest-sample/FileProcessor.js +57 -0
  19. package/skills/jstest/references/src/jstest-sample/OrderService.js +98 -0
  20. package/skills/jstest/references/src/jstest-sample/TokenGenerator.js +60 -0
  21. package/skills/jstest/references/src/jstest-sample/UserService.js +56 -0
  22. package/skills/jstest/references/src/jstest-sample/index.js +6 -0
  23. package/skills/jstest/references/suitability-check.md +232 -0
  24. package/skills/jstest/references/test-standards.md +288 -0
  25. package/skills/pytest/SKILL.md +38 -30
  26. package/skills/query/SKILL.md +16 -47
  27. package/skills/revise/SKILL.md +52 -96
  28. package/skills/revise/references/article.md +12 -15
  29. package/skills/revise/references/gallery.md +11 -15
  30. package/skills/revise/references/question.md +11 -15
  31. package/skills/revise/references/status.md +10 -14
  32. package/src/config.js +29 -0
  33. package/src/config.json5 +19 -0
  34. package/skills/extract/references/format-aliases.json5 +0 -22
  35. package/skills/extract/references/input.schema.json5 +0 -23
  36. package/skills/query/references/input.schema.json5 +0 -25
  37. package/skills/revise/references/format-aliases.json5 +0 -22
  38. package/skills/revise/references/input.schema.json5 +0 -28
@@ -0,0 +1,98 @@
1
+ export class OrderService {
2
+
3
+ static TRANSITIONS = {
4
+ 'created': ['paid', 'cancelled'],
5
+ 'paid': ['shipped', 'refunded'],
6
+ 'shipped': ['delivered', 'returned'],
7
+ 'delivered': ['returned'],
8
+ 'cancelled': [],
9
+ 'refunded': [],
10
+ 'returned': []
11
+ }
12
+
13
+ constructor(baseUrl = 'https://api.example.com') {
14
+ this.baseUrl = baseUrl
15
+ }
16
+
17
+ _getDiscount(amount) {
18
+ if (amount > 1000) {
19
+ return 0.10
20
+ } else if (amount > 500) {
21
+ return 0.05
22
+ } else if (amount > 100) {
23
+ return 0.03
24
+ }
25
+ return 0.0
26
+ }
27
+
28
+ _getTax(amount) {
29
+ if (amount > 500) {
30
+ return 0.08
31
+ } else if (amount > 100) {
32
+ return 0.05
33
+ }
34
+ return 0.0
35
+ }
36
+
37
+ async createOrder(userId, items) {
38
+ const total = items.reduce((sum, item) => sum + (item.price || 0) * (item.quantity || 1), 0)
39
+ const discount = this._getDiscount(total)
40
+ const tax = this._getTax(total)
41
+ const discounted = total * (1 - discount)
42
+ const taxed = discounted * (1 + tax)
43
+ const response = await fetch(`${this.baseUrl}/orders`, {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({
47
+ user_id: userId,
48
+ items,
49
+ total: Math.round(taxed * 100) / 100,
50
+ status: 'created',
51
+ created_at: new Date().toISOString()
52
+ })
53
+ })
54
+ return response.json()
55
+ }
56
+
57
+ async updateStatus(orderId, newStatus) {
58
+ const response = await fetch(`${this.baseUrl}/orders/${orderId}`)
59
+ const order = await response.json()
60
+ const currentStatus = order.status
61
+ const allowed = OrderService.TRANSITIONS[currentStatus] || []
62
+ if (!allowed.includes(newStatus)) {
63
+ throw new Error(`Cannot transition from ${currentStatus} to ${newStatus}`)
64
+ }
65
+ const patchResponse = await fetch(`${this.baseUrl}/orders/${orderId}`, {
66
+ method: 'PATCH',
67
+ headers: { 'Content-Type': 'application/json' },
68
+ body: JSON.stringify({ status: newStatus })
69
+ })
70
+ return patchResponse.json()
71
+ }
72
+
73
+ async getOrder(orderId) {
74
+ const response = await fetch(`${this.baseUrl}/orders/${orderId}`)
75
+ if (response.status === 404) {
76
+ return null
77
+ }
78
+ return response.json()
79
+ }
80
+
81
+ async cancelOrder(orderId) {
82
+ return this.updateStatus(orderId, 'cancelled')
83
+ }
84
+
85
+ async listOrders(userId) {
86
+ const response = await fetch(`${this.baseUrl}/orders?user_id=${userId}`)
87
+ return response.json()
88
+ }
89
+
90
+ calculateTotal(items) {
91
+ const total = items.reduce((sum, item) => sum + (item.price || 0) * (item.quantity || 1), 0)
92
+ const discount = this._getDiscount(total)
93
+ const tax = this._getTax(total)
94
+ const discounted = total * (1 - discount)
95
+ const taxed = discounted * (1 + tax)
96
+ return Math.round(taxed * 100) / 100
97
+ }
98
+ }
@@ -0,0 +1,60 @@
1
+ import crypto from 'crypto'
2
+
3
+ export class TokenGenerator {
4
+
5
+ static generateToken(length = 32) {
6
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
7
+ let result = ''
8
+ for (let i = 0; i < length; i++) {
9
+ result += chars[Math.floor(Math.random() * chars.length)]
10
+ }
11
+ return result
12
+ }
13
+
14
+ static generateTokenWithTimestamp(length = 16) {
15
+ const timestamp = String(Date.now() / 1000)
16
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
17
+ let randomPart = ''
18
+ for (let i = 0; i < length; i++) {
19
+ randomPart += chars[Math.floor(Math.random() * chars.length)]
20
+ }
21
+ return `${timestamp}${randomPart}`
22
+ }
23
+
24
+ static generateUuidLike() {
25
+ const chars = '0123456789abcdef'
26
+ let randomPart = ''
27
+ for (let i = 0; i < 32; i++) {
28
+ randomPart += chars[Math.floor(Math.random() * chars.length)]
29
+ }
30
+ const parts = [
31
+ randomPart.slice(0, 8),
32
+ randomPart.slice(8, 12),
33
+ randomPart.slice(12, 16),
34
+ randomPart.slice(16, 20),
35
+ randomPart.slice(20, 31)
36
+ ]
37
+ return parts.join('-')
38
+ }
39
+
40
+ static generateHash(data, algorithm = 'sha256') {
41
+ if (typeof data === 'string') {
42
+ data = Buffer.from(data, 'utf-8')
43
+ }
44
+ const hasher = crypto.createHash(algorithm)
45
+ hasher.update(data)
46
+ return hasher.digest('hex')
47
+ }
48
+
49
+ static generateBatch(count, length = 32) {
50
+ const tokens = []
51
+ for (let i = 0; i < count; i++) {
52
+ tokens.push(TokenGenerator.generateToken(length))
53
+ }
54
+ return tokens
55
+ }
56
+
57
+ static validateTokenLength(token, expectedLength) {
58
+ return token.length === expectedLength
59
+ }
60
+ }
@@ -0,0 +1,56 @@
1
+ export class UserService {
2
+
3
+ constructor(baseUrl = 'https://api.example.com') {
4
+ this.baseUrl = baseUrl
5
+ }
6
+
7
+ validateEmail(email) {
8
+ const pattern = /^[\w.+-]+@[\w-]+\.[\w.]{2}$/
9
+ return pattern.test(email)
10
+ }
11
+
12
+ async createUser(username, email) {
13
+ if (!username) {
14
+ throw new Error('Username is required')
15
+ }
16
+ if (!this.validateEmail(email)) {
17
+ throw new Error('Invalid email format')
18
+ }
19
+ const response = await fetch(`${this.baseUrl}/users`, {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json' },
22
+ body: JSON.stringify({ username, email })
23
+ })
24
+ return response.json()
25
+ }
26
+
27
+ async getUser(userId) {
28
+ const response = await fetch(`${this.baseUrl}/users/${userId}`)
29
+ if (response.status === 404) {
30
+ return null
31
+ }
32
+ return response.json()
33
+ }
34
+
35
+ async updateLastLogin(userId) {
36
+ const now = new Date()
37
+ const response = await fetch(`${this.baseUrl}/users/${userId}`, {
38
+ method: 'PATCH',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body: JSON.stringify({ last_login: now.toISOString() })
41
+ })
42
+ return response.json()
43
+ }
44
+
45
+ async searchUsers(query) {
46
+ const response = await fetch(`${this.baseUrl}/users?q=${encodeURIComponent(query)}`)
47
+ return response.json()
48
+ }
49
+
50
+ async deleteUser(userId) {
51
+ const response = await fetch(`${this.baseUrl}/users/${userId}`, {
52
+ method: 'DELETE'
53
+ })
54
+ return response.status === 204
55
+ }
56
+ }
@@ -0,0 +1,6 @@
1
+ export { Calculator } from './Calculator.js'
2
+ export { UserService } from './UserService.js'
3
+ export { FileProcessor } from './FileProcessor.js'
4
+ export { TokenGenerator } from './TokenGenerator.js'
5
+ export { ConfigManager } from './ConfigManager.js'
6
+ export { OrderService } from './OrderService.js'
@@ -0,0 +1,232 @@
1
+ > **原 skill**: `jstest-suitability-check`(已合并入 jstest)
2
+ > **适用**: 测试代理的适用性评估阶段,在创建用例前过滤不适合的模块。
3
+ > **不适用**: 非 JS/TS 项目、非测试类型判断。
4
+
5
+ ---
6
+
7
+ # 模块适用性评估
8
+
9
+ ## 工作流模式
10
+
11
+ 本 Skill 采用 **顺序执行** 工作流:
12
+
13
+ 1. **递归扫描**:遍历包目录及子目录下所有 .js/.ts 文件
14
+ 2. **模块提取**:从每个文件中提取导出的类、函数和常量
15
+ 3. **分类判定**:按信号规则对每个模块分类
16
+ 4. **适用性过滤**:根据 test_type 判断每个模块是否适合
17
+ 5. **输出清单**:返回可测试模块清单
18
+
19
+ ## 输入
20
+
21
+ | 参数 | 必填 | 类型 | 说明 |
22
+ |------|------|------|------|
23
+ | target | 是 | string | 源码包目录路径 |
24
+ | test_type | 是 | string | 测试类型:`unit` 或 `integration`(也接受 `ut`/`it` 简写,内部归一化) |
25
+
26
+ ## 输出
27
+
28
+ - 可测试模块清单(文件路径 + 模块名 + 分类)
29
+ - 不适合的模块清单(文件路径 + 模块名 + 分类 + 原因)
30
+ - 如果可测试清单为空,agent 应终止流程
31
+
32
+ ---
33
+
34
+ ## 阶段一:递归扫描
35
+
36
+ **操作**:递归遍历 target 目录及所有子目录
37
+
38
+ 1. 遍历 target 目录,收集所有 .js/.ts 文件
39
+ 2. 排除以下文件:
40
+ - `index.js` / `index.ts`(入口文件,通常仅 re-export)
41
+ - `*.test.js` / `*.test.ts`(已有测试文件)
42
+ - `*.spec.js` / `*.spec.ts`(已有测试文件)
43
+ - `*.config.js` / `*.config.ts`(配置文件)
44
+ - `*.setup.js` / `*.setup.ts`(setup 文件)
45
+ 3. 记录每个文件的相对路径(相对于 target)
46
+
47
+ **决策**:
48
+
49
+ | 条件 | 动作 |
50
+ |------|------|
51
+ | 找到 .js/.ts 文件 | 进入阶段二 |
52
+ | 未找到 .js/.ts 文件 | 返回空清单 |
53
+
54
+ ---
55
+
56
+ ## 阶段二:模块提取
57
+
58
+ **操作**:从每个 .js/.ts 文件中提取导出的类、函数和常量
59
+
60
+ 1. 读取文件内容
61
+ 2. 提取所有 `export class Name` 定义
62
+ 3. 提取所有 `export function name` 定义
63
+ 4. 提取所有 `export const name` 定义(箭头函数形式)
64
+ 5. 提取所有 `module.exports = { ... }` 定义(CommonJS)
65
+ 6. 对每个导出提取:
66
+ - 模块名(类名/函数名/常量名)
67
+ - 导出类型(class / function / const / module.exports)
68
+ - 装饰器/注解(TypeScript 装饰器、JSDoc 标签等)
69
+ - import/require 语句(文件级,共享给同文件模块)
70
+ - 方法列表及方法体(对 class)
71
+ 7. 对无导出的文件(仅副作用执行),跳过
72
+
73
+ **决策**:
74
+
75
+ | 条件 | 动作 |
76
+ |------|------|
77
+ | 文件有 `export class` 定义 | 逐个类进入阶段三 |
78
+ | 文件有 `export function`/`export const` | 作为模块级单元进入阶段三 |
79
+ | 文件有 `module.exports` | 作为模块级单元进入阶段三 |
80
+ | 文件为空或仅含 import/require | 跳过 |
81
+ | 文件无导出(仅副作用) | 跳过 |
82
+
83
+ ---
84
+
85
+ ## 阶段三:分类判定
86
+
87
+ **操作**:按信号规则对每个类(或模块级函数单元)分类
88
+
89
+ ### 分类规则
90
+
91
+ | 模块类型 | 适合 UT | 适合 IT | 说明 |
92
+ |---------|---------|---------|------|
93
+ | Pure Utility | ✅ | ❌ | 纯函数,无外部依赖,无副作用 |
94
+ | Data Model | ✅ | ❌ | 数据容器,无业务逻辑 |
95
+ | Service Class | ✅ | ✅ | 有 DB/HTTP/文件 I/O 操作 |
96
+ | Integration Point | ✅ | ✅ | 编排多个 Service 调用 |
97
+ | External Adapter | ✅ | ⚠️ | 封装外部 API/SDK,需测试环境 |
98
+
99
+ ### 信号采集
100
+
101
+ #### 1. import/require 分析
102
+
103
+ | import/require 内容 | 指向分类 |
104
+ |---------------------|---------|
105
+ | 仅 Node.js 内置(path, fs, url, crypto, util) | Pure Utility |
106
+ | fetch, axios, http, https | Service Class (HTTP) |
107
+ | mongoose, sequelize, pg, mysql2, prisma | Service Class (DB) |
108
+ | fs, path, fs/promises 中的文件操作 | Service Class (文件) |
109
+ | 内部 service 模块(相对路径 import) | Integration Point |
110
+ | 外部 SDK(aws-sdk, googleapis, @octokit 等) | External Adapter |
111
+
112
+ #### 2. 方法体分析
113
+
114
+ | 方法体特征 | 指向分类 |
115
+ |-----------|---------|
116
+ | 无外部调用、无副作用、纯计算 | Pure Utility |
117
+ | 含 `fetch()`/`axios.get()`/`db.query()` | Service Class |
118
+ | 调用多个其他 Service 的方法 | Integration Point |
119
+ | 封装外部 SDK 调用 | External Adapter |
120
+ | 仅 getter/setter/属性定义 | Data Model |
121
+
122
+ #### 3. 模块结构分析
123
+
124
+ | 模块结构特征 | 指向分类 |
125
+ |-----------|---------|
126
+ | 仅含类型定义(interface/type) | Data Model |
127
+ | 构造函数接收 config/依赖注入 | Service Class |
128
+ | 抽象类且仅含抽象方法 | 跳过(无可测实现) |
129
+ | 仅含 `export const` 常量定义 | 跳过(无可测逻辑) |
130
+ | `process.env` 读取 | Service Class (环境变量) |
131
+
132
+ ### 分类优先级
133
+
134
+ 当一个模块有多个信号时,按优先级判定:
135
+
136
+ 1. Integration Point > Service Class > External Adapter > Pure Utility > Data Model
137
+ 2. 即:如果一个模块既调用 DB 又编排多个 Service,归类为 Integration Point
138
+
139
+ ---
140
+
141
+ ## 阶段四:适用性过滤
142
+
143
+ **操作**:根据 test_type 过滤
144
+
145
+ ### test_type = unit / ut
146
+
147
+ UT 对所有有逻辑的模块都适合,不过滤,返回全部模块。
148
+
149
+ | 模块类型 | 是否返回 | 说明 |
150
+ |---------|---------|------|
151
+ | Pure Utility | ✅ | 纯函数逻辑测试 |
152
+ | Data Model | ✅ | 验证逻辑、计算属性测试 |
153
+ | Service Class | ✅ | Mock 依赖,测业务逻辑 |
154
+ | Integration Point | ✅ | Mock 子 Service,测编排逻辑 |
155
+ | External Adapter | ✅ | Mock 外部 API,测适配逻辑 |
156
+
157
+ ### test_type = integration / it
158
+
159
+ IT 仅适合有外部依赖可集成的模块。
160
+
161
+ | 模块类型 | 是否返回 | 原因 |
162
+ |---------|---------|------|
163
+ | Pure Utility | ❌ | 无外部依赖,IT = UT,无意义 |
164
+ | Data Model | ❌ | 无交互可测,IT 无意义 |
165
+ | Service Class | ✅ | 真实连接 DB/HTTP 测试 |
166
+ | Integration Point | ✅ | 多 Service 真实编排测试 |
167
+ | External Adapter | ✅ | 真实调用外部 API(需测试环境) |
168
+
169
+ ---
170
+
171
+ ## 阶段五:输出清单
172
+
173
+ **输出格式**:
174
+
175
+ ```
176
+ ## 模块适用性评估
177
+
178
+ ### 可测试模块清单
179
+ | 文件路径 | 模块名 | 分类 |
180
+ |---------|--------|------|
181
+ | src/services/UserService.js | UserService | Service Class |
182
+ | src/services/OrderService.js | OrderService | Integration Point |
183
+
184
+ ### 不适合的模块
185
+ | 文件路径 | 模块名 | 分类 | 原因 |
186
+ |---------|--------|------|------|
187
+ | src/utils/stringUtils.js | stringUtils | Pure Utility | 纯函数无外部依赖,IT 无意义 |
188
+ | src/models/User.js | User | Data Model | 数据容器,IT 无意义 |
189
+
190
+ ### 结论
191
+ - 适合的模块: X 个
192
+ - 不适合的模块: Y 个
193
+ - 可测试清单: [文件路径列表]
194
+ ```
195
+
196
+ ---
197
+
198
+ ## 决策
199
+
200
+ | 条件 | 动作 |
201
+ |------|------|
202
+ | 可测试清单非空 | 返回清单给 agent,agent 继续后续步骤 |
203
+ | 可测试清单为空 | 返回空清单,agent 终止流程 |
204
+
205
+ ## 失败处理
206
+
207
+ | 场景 | 处理方式 |
208
+ |------|---------|
209
+ | target 路径不存在 | 返回空清单,提示路径无效 |
210
+ | target 不是目录 | 返回空清单,提示需要目录路径 |
211
+ | .js/.ts 文件语法错误 | 跳过该文件,记录警告 |
212
+ | 无法判定分类 | 默认归类为 Service Class(保守策略) |
213
+
214
+ ## 一定要做
215
+
216
+ 1. 必须递归扫描所有子目录
217
+ 2. 必须逐个模块分类,不按文件整体判断
218
+ 3. 必须排除 index.js、*.test.js、*.spec.js、*.config.js、*.setup.js
219
+ 4. 必须对纯函数文件做模块级评估
220
+ 5. unit 类型必须返回全部模块(不过滤)
221
+ 6. integration 类型必须过滤掉 Pure Utility 和 Data Model
222
+ 7. 必须输出不适合的模块及原因
223
+ 8. 无法判定时必须保守归类为 Service Class
224
+
225
+ ## 一定不要做
226
+
227
+ 1. 不要修改源码文件
228
+ 2. 不要跳过子目录
229
+ 3. 不要按文件/模块整体判断(必须逐个导出)
230
+ 4. 不要对 ut 类型做过滤
231
+ 5. 不要将空文件或纯类型定义文件纳入清单
232
+ 6. 不要主观臆断分类,必须基于信号规则