@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,168 @@
1
+ > **原 skill**: `jstest-execute`(已合并入 jstest)
2
+ > **适用**: 测试代理的测试执行阶段,运行已创建的用例并收集结果。
3
+ > **不适用**: 创建测试用例(使用 jstest-case-create)、修复失败用例(使用 jstest-case-fix)。
4
+
5
+ ---
6
+
7
+ # 测试执行
8
+
9
+ ## 工作流模式
10
+
11
+ 本 Skill 采用 **顺序执行** 工作流:
12
+
13
+ 1. **选择目录**:根据 test_type 选择测试目录
14
+ 2. **执行测试**:运行 jest
15
+ 3. **解析结果**:提取通过/失败/跳过统计
16
+ 4. **提取失败详情**:对每个失败用例提取详细信息
17
+ 5. **输出报告**:测试报告 + 失败详情
18
+
19
+ ## 输入
20
+
21
+ | 参数 | 必填 | 类型 | 说明 |
22
+ |------|------|------|------|
23
+ | target | 是 | string | 被测模块路径 |
24
+ | test_dir | 是 | string | 测试目录路径(由 agent Step 1 统一解析) |
25
+ | test_type | 是 | string | 测试类型:unit 或 integration |
26
+ | config | 是 | object | 来自 jstest-env-ensure 的配置对象 |
27
+ | test_files | 否 | list | 指定测试文件(不指定则运行 test_dir 下全部) |
28
+
29
+ ## 输出
30
+
31
+ - 测试报告(通过数/失败数/跳过数)
32
+ - 失败详情列表(文件路径、用例名、错误类型、错误信息、堆栈)
33
+ - 决策结论(全部通过 → 回到 Step 4(按 references/coverage-analyze.md 执行);有失败 → 交给 case-fix)
34
+
35
+ ---
36
+
37
+ ## 阶段一:选择目录
38
+
39
+ **操作**:使用传入的 `test_dir` 作为测试目录
40
+
41
+ 如果指定了 test_files,则只运行指定的文件。
42
+
43
+ ---
44
+
45
+ ## 阶段二:执行测试
46
+
47
+ **操作**:
48
+
49
+ ```
50
+ # 运行 test_dir 下的测试(unit 超时 60000ms,integration 超时 300000ms)
51
+ node --experimental-vm-modules node_modules/jest/bin/jest.js --no-cache --verbose --testTimeout={timeout} {test_dir}
52
+
53
+ # 如果指定了 test_files
54
+ node --experimental-vm-modules node_modules/jest/bin/jest.js --no-cache --verbose --testTimeout={timeout} {test_files}
55
+ ```
56
+
57
+ **参数说明**:
58
+ - `--no-cache`:禁用缓存,确保每次重新执行
59
+ - `--verbose`:详细输出每个用例结果
60
+ - `--testTimeout`:单用例超时毫秒数(unit=60000, integration=300000),来自 config
61
+ - ESM 项目必须使用 `--experimental-vm-modules` 标志
62
+
63
+ ---
64
+
65
+ ## 阶段三:解析结果
66
+
67
+ **操作**:从 jest 输出中提取:
68
+
69
+ 1. 统计数据:
70
+ - 通过数(passed)— jest 输出 `✓` 标记
71
+ - 失败数(failed)— jest 输出 `✕` 标记
72
+ - 跳过数(skipped)
73
+ - 总数
74
+ - jest 输出格式:`Tests: X passed, Y failed, Z total`
75
+
76
+ 2. 对每个失败用例提取:
77
+ - 文件路径(jest 输出 `FAIL tests/unit/xxx/MyService.test.js`)
78
+ - describe 名
79
+ - test/it 名
80
+ - 错误类型(AssertionError, TypeError, ReferenceError 等)
81
+ - 错误信息(第一行错误描述)
82
+ - 堆栈信息
83
+
84
+ **jest 输出格式示例**:
85
+
86
+ ```
87
+ PASS tests/unit/mypackage/MyService.test.js
88
+ MyService
89
+ ✓ should create with valid data (5ms)
90
+ ✕ should throw on empty name (3ms)
91
+
92
+ FAIL tests/unit/mypackage/OtherService.test.js
93
+ OtherService
94
+ ✕ should update with valid data (2ms)
95
+
96
+ Tests: 1 passed, 2 failed, 3 total
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 阶段四:输出报告
102
+
103
+ **输出格式**:
104
+
105
+ ```
106
+ ## 测试执行报告
107
+
108
+ ### 统计
109
+ - 总数:XX
110
+ - 通过:XX
111
+ - 失败:XX
112
+ - 跳过:XX
113
+ - 通过率:XX%
114
+
115
+ ### 失败详情(如有)
116
+ | # | 文件 | describe | test 名 | 错误类型 | 错误信息 |
117
+ |---|------|----------|---------|---------|---------|
118
+ | 1 | tests/unit/xxx/MyService.test.js | MyService | should create with valid data | AssertionError | expect(result.id).toBeDefined() |
119
+ | 2 | tests/unit/xxx/MyService.test.js | MyService | should parse on empty input | TypeError | Cannot read property 'x' of undefined |
120
+
121
+ ### 失败堆栈(如有)
122
+ #### 1. should create with valid data
123
+ ```
124
+ expect(result.id).toBeDefined()
125
+ Expected: defined
126
+ Received: undefined
127
+ at Object.<anonymous> (tests/unit/xxx/MyService.test.js:15:17)
128
+ ```
129
+
130
+ ### 结论
131
+ - [全部通过] → 建议回到覆盖率分析阶段
132
+ - [有失败] → 建议进入用例修复阶段,共 X 个失败需修复
133
+ ```
134
+
135
+ ---
136
+
137
+ ## 决策
138
+
139
+ | 条件 | 动作 |
140
+ |------|------|
141
+ | 全部通过(failed=0) | 输出"全部通过",回到 Step 4(按 references/coverage-analyze.md 执行) |
142
+ | 有失败(failed>0) | 输出失败详情,交给 Step 7(按 references/case-fix.md 执行) |
143
+
144
+ ## 失败处理
145
+
146
+ | 场景 | 处理方式 |
147
+ |------|---------|
148
+ | 测试目录不存在 | 输出"测试目录不存在",提示需要先创建用例 |
149
+ | jest 执行错误(非测试失败) | 退出,输出错误信息 |
150
+ | 无测试文件 | 输出"无测试文件",通过率 0% |
151
+ | jest 超时 | 退出,提示测试执行超时,建议检查 hang 住的用例或增加 timeout |
152
+
153
+ ## 一定要做
154
+
155
+ 1. 必须使用传入的 test_dir 运行测试
156
+ 2. 必须使用 --verbose 输出详细结果
157
+ 3. 必须提取每个失败用例的详细信息
158
+ 4. 必须输出通过率
159
+ 5. 如果指定了 test_files,只运行指定文件
160
+ 6. ESM 项目必须使用 --experimental-vm-modules 标志
161
+
162
+ ## 一定不要做
163
+
164
+ 1. 不要修改测试文件或源码
165
+ 2. 不要跳过失败的用例
166
+ 3. 不要混合 unit 和 integration 的测试
167
+ 4. 不要忽略 jest 执行错误
168
+ 5. 不要使用 --silent(需要详细输出用于分析)
@@ -0,0 +1,166 @@
1
+ > **原 skill**: `jstest-sample`(已合并入 jstest,通过 test_type=sample 触发)
2
+ > **适用**: 首次使用 jstest skill 前的演示验证、测试流程的端到端测试。
3
+ > **不适用**: 生产代码、非测试目的的代码生成。
4
+
5
+ ---
6
+
7
+ # Demo 样例代码注入
8
+
9
+ ## 工作流模式
10
+
11
+ 本 Skill 采用 **顺序执行** 工作流:
12
+
13
+ 1. **创建目录**:在 src/ 下创建 jstest-sample 包目录
14
+ 2. **写入文件**:从 references/src/jstest-sample/ 读取模板,写入到 src/jstest-sample/
15
+ 3. **验证**:检查所有文件语法正确
16
+
17
+ ## 输入
18
+
19
+ | 参数 | 必填 | 类型 | 说明 |
20
+ |------|------|------|------|
21
+ | 无 | - | - | 自动在当前工程 src/ 下创建 |
22
+
23
+ ## 输出
24
+
25
+ - src/jstest-sample/ 目录及 7 个文件
26
+ - 各文件覆盖的测试场景清单
27
+ - 故意 bug 清单及难度分级
28
+
29
+ ---
30
+
31
+ ## 阶段一:创建目录
32
+
33
+ **操作**:创建 `src/jstest-sample/` 目录
34
+
35
+ ---
36
+
37
+ ## 阶段二:写入文件
38
+
39
+ 从 `references/src/jstest-sample/` 目录读取以下模板文件,写入到 `src/jstest-sample/`:
40
+
41
+ | 文件 | 覆盖场景 |
42
+ |------|---------|
43
+ | index.js | 包入口(export 所有模块) |
44
+ | Calculator.js | 纯函数、分支(if/else)、异常(throw)、边界值、参数化 |
45
+ | UserService.js | HTTP 请求(fetch mock)、数据库(mock)、时间依赖(new Date mock)、验证逻辑 |
46
+ | FileProcessor.js | 文件系统(fs mock)、JSON 解析、文件不存在异常、目录操作 |
47
+ | TokenGenerator.js | 随机值(Math.random mock)、时间(Date mock)、哈希(crypto)、批量生成、边界检查 |
48
+ | ConfigManager.js | 环境变量(process.env)、单例模式、类型转换、默认值 |
49
+ | OrderService.js | 复杂业务逻辑、状态机、多依赖、折扣计算(分支)、外部服务(fetch mock) |
50
+
51
+ ---
52
+
53
+ ## 阶段三:验证
54
+
55
+ **操作**:
56
+
57
+ ```
58
+ node --check src/jstest-sample/Calculator.js
59
+ node --check src/jstest-sample/UserService.js
60
+ node --check src/jstest-sample/FileProcessor.js
61
+ node --check src/jstest-sample/TokenGenerator.js
62
+ node --check src/jstest-sample/ConfigManager.js
63
+ node --check src/jstest-sample/OrderService.js
64
+ node --check src/jstest-sample/index.js
65
+ ```
66
+
67
+ **决策**:
68
+
69
+ | 条件 | 动作 |
70
+ |------|------|
71
+ | 全部语法正确 | 输出创建报告 |
72
+ | 有语法错误 | 修复后重新验证 |
73
+
74
+ ---
75
+
76
+ ## 故意 Bug 设计
77
+
78
+ demo 代码中故意注入了 15 个 bug,分为 3 个难度梯度,用于尽可能触发测试代理的循环迭代。
79
+
80
+ ### Easy 难度(基础测试即可发现)
81
+
82
+ | # | 文件 | 方法 | Bug 描述 | 触发方式 |
83
+ |---|------|------|---------|---------|
84
+ | 1 | Calculator.js | clamp | min/max 返回值互换 | 断言 clamp(0,1,10)===1 |
85
+ | 2 | FileProcessor.js | writeFile | 返回 content.length 而非文件大小 | 写入多字节字符后断言 |
86
+ | 3 | UserService.js | validateEmail | TLD 正则 {2,}→{2} 拒绝 .com | 断言 validateEmail("a@b.com")===true |
87
+
88
+ ### Medium 难度(需边界值/序列测试)
89
+
90
+ | # | 文件 | 方法 | Bug 描述 | 触发方式 |
91
+ |---|------|------|---------|---------|
92
+ | 4 | Calculator.js | isPrime | Math.floor(Math.sqrt(n)) 缺少 +1 漏检平方数 | 断言 isPrime(9)===false |
93
+ | 5 | Calculator.js | fibonacci | 循环 off-by-one | 断言 fibonacci(3)===2 |
94
+ | 6 | FileProcessor.js | countLines | 缺少 trim() 计入空白行 | 带首尾空行的文件断言 |
95
+ | 7 | OrderService.js | _getDiscount | > 1000 边界(应为 >=) | 断言 1000 元折扣为 10% |
96
+ | 8 | OrderService.js | _getTax | > 500 边界(应为 >=) | 断言 500 元税率为 8% |
97
+ | 9 | TokenGenerator.js | generateUuidLike | 末段 12→11 字符 | UUID 格式正则验证 |
98
+
99
+ ### Hard 难度(需深入理解/非标准输入测试)
100
+
101
+ | # | 文件 | 方法 | Bug 描述 | 触发方式 |
102
+ |---|------|------|---------|---------|
103
+ | 10 | ConfigManager.js | getBool | 反转 truthy 逻辑,非标准字符串返回 True | 断言 getBool(key, "unknown")===false |
104
+ | 11 | ConfigManager.js | clearInstance | 单例 _instance 未重置 | clearInstance 后断言新实例 id 不同 |
105
+ | 12 | OrderService.js | TRANSITIONS | created→delivered 非法跳转 | 断言该转换抛出 Error |
106
+ | 13 | UserService.js | searchUsers | 返回完整 JSON 而非 results 字段 | 断言返回值为 Array |
107
+ | 14 | OrderService.js | createOrder | 缺少 userId 类型校验 | 传入字符串 userId 断言 Error |
108
+ | 15 | TokenGenerator.js | generateTokenWithTimestamp | 时间戳含小数点 | 正则验证 token 格式 |
109
+
110
+ ---
111
+
112
+ ## 输出格式
113
+
114
+ ```
115
+ ## Demo 样例代码创建报告
116
+
117
+ ### 创建文件
118
+ | 文件路径 | 模块名 | 覆盖场景 |
119
+ |---------|--------|---------|
120
+ | src/jstest-sample/index.js | - | 包入口 |
121
+ | src/jstest-sample/Calculator.js | Calculator | 纯函数、分支、异常、边界值 |
122
+ | src/jstest-sample/UserService.js | UserService | HTTP Mock、DB Mock、时间 patch |
123
+ | src/jstest-sample/FileProcessor.js | FileProcessor | 文件系统、JSON、异常 |
124
+ | src/jstest-sample/TokenGenerator.js | TokenGenerator | 随机 Mock、时间 Mock、哈希 |
125
+ | src/jstest-sample/ConfigManager.js | ConfigManager | 环境变量、单例、类型转换 |
126
+ | src/jstest-sample/OrderService.js | OrderService | 状态机、多依赖、折扣计算 |
127
+
128
+ ### 故意 Bug 统计
129
+ - Easy: 3 个(基础测试可发现)
130
+ - Medium: 6 个(需边界值/序列测试)
131
+ - Hard: 6 个(需深入理解/非标准输入)
132
+ - 总计: 15 个
133
+
134
+ ### 使用方式
135
+ 1. 单元测试: jstest unit src/jstest-sample
136
+ 2. 集成测试: jstest integration src/jstest-sample
137
+
138
+ ### 场景覆盖说明
139
+ - 纯函数测试: Calculator.add, Calculator.isPrime
140
+ - 分支覆盖: Calculator.clamp, OrderService._getDiscount
141
+ - 异常路径: Calculator.divide(0), UserService.createUser(无email)
142
+ - 边界值: Calculator.factorial(0), TokenGenerator.generateToken(1)
143
+ - HTTP Mock: UserService.createUser (jest.mock fetch)
144
+ - DB Mock: UserService.getUser, OrderService.createOrder
145
+ - 文件系统: FileProcessor.readFile, FileProcessor.writeFile (jest.mock fs)
146
+ - 时间 Mock: UserService.updateLastLogin, OrderService.createOrder (jest.useFakeTimers)
147
+ - 随机 Mock: TokenGenerator.generateToken (jest.spyOn Math.random)
148
+ - 环境变量: ConfigManager.getDatabaseUrl (process.env)
149
+ - 状态机: OrderService.updateStatus
150
+ - 参数化: Calculator.isPrime, Calculator.factorial (test.each)
151
+ ```
152
+
153
+ ## 一定要做
154
+
155
+ 1. 必须在 src/jstest-sample/ 目录下创建文件
156
+ 2. 必须从 references/src/jstest-sample/ 读取模板内容写入
157
+ 3. 创建后必须验证语法正确(node --check)
158
+ 4. 必须输出场景覆盖说明
159
+ 5. 必须保留 demo 中的故意 bug,不要修复
160
+
161
+ ## 一定不要做
162
+
163
+ 1. 不要修改 src/ 下已有的其他文件
164
+ 2. 不要在 demo 代码中导入项目特定的三方库
165
+ 3. 不要跳过语法验证
166
+ 4. 不要修复 demo 中的故意 bug(它们是设计意图)
@@ -0,0 +1,75 @@
1
+ # 评分规则
2
+
3
+ > 覆盖率数据完全来自 `coverage/coverage-summary.json`(jest --coverage 生成),质量维度通过文件扫描检查,无主观判断。
4
+
5
+ ## jest coverage-summary.json 格式
6
+
7
+ ```json
8
+ {
9
+ "total": {
10
+ "lines": { "total": 100, "covered": 85, "pct": 85 },
11
+ "statements": { "total": 100, "covered": 85, "pct": 85 },
12
+ "functions": { "total": 20, "covered": 18, "pct": 90 },
13
+ "branches": { "total": 40, "covered": 28, "pct": 70 }
14
+ }
15
+ }
16
+ ```
17
+
18
+ - 行覆盖率 = `total.lines.pct`
19
+ - 分支覆盖率 = `total.branches.pct`
20
+ - 函数覆盖率 = `total.functions.pct`
21
+
22
+ ## 单元测试评分(unit)— 满分 100
23
+
24
+ ### 覆盖率维度(80 分)
25
+
26
+ | 维度 | 满分 | 阈值 | 数据来源 | 计分公式 |
27
+ |------|------|------|---------|---------|
28
+ | 行覆盖率 | 30 | 90% | coverage-summary.json: `total.lines.pct` | `min(实际/阈值, 1) × 30` |
29
+ | 分支覆盖率 | 30 | 70% | coverage-summary.json: `total.branches.pct` | `min(实际/阈值, 1) × 30` |
30
+ | 函数覆盖率 | 20 | 90% | coverage-summary.json: `total.functions.pct` | `min(实际/阈值, 1) × 20` |
31
+
32
+ ### 质量维度(20 分)
33
+
34
+ | 维度 | 满分 | 检查方法 | 计分公式 |
35
+ |------|------|---------|---------|
36
+ | 断言完整性 | 5 | 扫描测试文件,统计含 `expect(` 关键字的 test 函数比例 | `(有expect函数数 / 总函数数) × 5` |
37
+ | 异常路径覆盖 | 5 | 源码含 `throw`/`try` 的函数,测试中是否有对应 `toThrow` 或 `try-catch` | `(有异常测试数 / 应测异常函数数) × 5` |
38
+ | 边界值覆盖 | 5 | 源码含 `if`/`switch` 的函数,测试用例数 ≥ 分支数(检查 `test.each`/`it.each`) | `(达标函数数 / 应测函数数) × 5` |
39
+ | 命名规范 | 5 | test 函数名匹配 `test('should ...')` 或 `it('should ...')` 模式 | `(规范命名数 / 总数) × 5` |
40
+
41
+ ## 集成测试评分(integration)— 满分 100
42
+
43
+ ### 覆盖率维度(50 分)
44
+
45
+ | 维度 | 满分 | 阈值 | 数据来源 | 计分公式 |
46
+ |------|------|------|---------|---------|
47
+ | 行覆盖率 | 20 | 70% | coverage-summary.json: `total.lines.pct` | `min(实际/阈值, 1) × 20` |
48
+ | 分支覆盖率 | 20 | 50% | coverage-summary.json: `total.branches.pct` | `min(实际/阈值, 1) × 20` |
49
+ | 函数覆盖率 | 10 | 70% | coverage-summary.json: `total.functions.pct` | `min(实际/阈值, 1) × 10` |
50
+
51
+ ### 质量维度(50 分)
52
+
53
+ | 维度 | 满分 | 检查方法 | 计分公式 |
54
+ |------|------|---------|---------|
55
+ | 场景完整性 | 15 | 核心业务场景是否都有对应测试(按源码公开方法数统计) | `(有测试的场景数 / 应测场景数) × 15` |
56
+ | 数据流验证 | 10 | 测试中是否验证了输入→处理→输出完整链路(含多步 `expect`) | `(有数据流验证的函数数 / 总函数数) × 10` |
57
+ | 断言完整性 | 10 | 每个测试函数至少含 1 个 `expect(` | `(有expect函数数 / 总函数数) × 10` |
58
+ | setup/teardown 使用 | 10 | 是否合理使用 `beforeEach`/`afterEach`/`beforeAll`/`afterAll` | `(有setup/teardown的测试文件数 / 总文件数) × 10` |
59
+ | 命名规范 | 5 | test 函数名匹配 `test('should ...')` 或 `it('should ...')` 模式 | `(规范命名数 / 总数) × 5` |
60
+
61
+ ## 通用规则
62
+
63
+ - **分母为 0**:若应测数为 0(无适用项),该项直接给满分。
64
+ - **超限封顶**:`min(实际/阈值, 1.0)` 确保单项不超过满分。
65
+ - **数据来源**:覆盖率维度完全依赖 coverage-summary.json;质量维度通过 grep/扫描测试文件检查。
66
+ - **pct 为 0**:当 `total.lines.total` 为 0(空文件)时,pct 可能为 0 或 "Unknown",按 0 处理。
67
+
68
+ ## 等级
69
+
70
+ | 等级 | 分数范围 | 含义 |
71
+ |------|---------|------|
72
+ | A | 90-100 | 优秀 |
73
+ | B | 80-89 | 合格 |
74
+ | C | 60-79 | 需改进 |
75
+ | D | <60 | 不合格 |
@@ -0,0 +1,80 @@
1
+ export class Calculator {
2
+
3
+ static add(a, b) {
4
+ return a + b
5
+ }
6
+
7
+ static subtract(a, b) {
8
+ return a - b
9
+ }
10
+
11
+ static multiply(a, b) {
12
+ return a * b
13
+ }
14
+
15
+ static divide(a, b) {
16
+ if (b === 0) {
17
+ throw new Error('Cannot divide by zero')
18
+ }
19
+ return a / b
20
+ }
21
+
22
+ static clamp(value, minVal, maxVal) {
23
+ if (value < minVal) {
24
+ return maxVal
25
+ }
26
+ if (value > maxVal) {
27
+ return minVal
28
+ }
29
+ return value
30
+ }
31
+
32
+ static isPrime(n) {
33
+ if (n < 2) {
34
+ return false
35
+ }
36
+ if (n === 2) {
37
+ return true
38
+ }
39
+ if (n % 2 === 0) {
40
+ return false
41
+ }
42
+ for (let i = 3; i < Math.floor(Math.sqrt(n)); i += 2) {
43
+ if (n % i === 0) {
44
+ return false
45
+ }
46
+ }
47
+ return true
48
+ }
49
+
50
+ static fibonacci(n) {
51
+ if (n < 0) {
52
+ throw new Error('n must be non-negative')
53
+ }
54
+ if (n === 0) {
55
+ return 0
56
+ }
57
+ if (n === 1) {
58
+ return 1
59
+ }
60
+ let a = 0, b = 1
61
+ for (let i = 0; i < n - 1; i++) {
62
+ [a, b] = [b, a + b]
63
+ }
64
+ return b
65
+ }
66
+
67
+ static factorial(n) {
68
+ if (n < 0) {
69
+ throw new Error('n must be non-negative')
70
+ }
71
+ if (n === 0) {
72
+ return 1
73
+ }
74
+ let result = 1
75
+ for (let i = 2; i <= n; i++) {
76
+ result *= i
77
+ }
78
+ return result
79
+ }
80
+ }
@@ -0,0 +1,72 @@
1
+ export class ConfigManager {
2
+
3
+ static _instance = null
4
+
5
+ constructor() {
6
+ if (ConfigManager._instance === null) {
7
+ this._config = {}
8
+ ConfigManager._instance = this
9
+ }
10
+ return ConfigManager._instance
11
+ }
12
+
13
+ get(key, defaultValue = null) {
14
+ return process.env[key] ?? defaultValue
15
+ }
16
+
17
+ set(key, value) {
18
+ process.env[key] = String(value)
19
+ }
20
+
21
+ getInt(key, defaultValue = 0) {
22
+ const value = process.env[key]
23
+ if (value === undefined) {
24
+ return defaultValue
25
+ }
26
+ const parsed = parseInt(value, 10)
27
+ return isNaN(parsed) ? defaultValue : parsed
28
+ }
29
+
30
+ getBool(key, defaultValue = false) {
31
+ const value = process.env[key]
32
+ if (value === undefined) {
33
+ return defaultValue
34
+ }
35
+ const truthy = ['true', '1', 'yes', 'on']
36
+ const falsy = ['false', '0', 'no', 'off', '']
37
+ const lower = value.toLowerCase()
38
+ if (truthy.includes(lower)) {
39
+ return false
40
+ }
41
+ if (falsy.includes(lower)) {
42
+ return true
43
+ }
44
+ return true
45
+ }
46
+
47
+ getFloat(key, defaultValue = 0.0) {
48
+ const value = process.env[key]
49
+ if (value === undefined) {
50
+ return defaultValue
51
+ }
52
+ const parsed = parseFloat(value)
53
+ return isNaN(parsed) ? defaultValue : parsed
54
+ }
55
+
56
+ getDatabaseUrl() {
57
+ const host = this.get('DB_HOST', 'localhost')
58
+ const port = this.get('DB_PORT', '5432')
59
+ const name = this.get('DB_NAME', 'app')
60
+ const user = this.get('DB_USER', 'admin')
61
+ const password = this.get('DB_PASSWORD', 'secret')
62
+ return `postgresql://${user}:${password}@${host}:${port}/${name}`
63
+ }
64
+
65
+ reset() {
66
+ this._config = {}
67
+ }
68
+
69
+ static clearInstance() {
70
+ // BUG: should set _instance = null but does nothing
71
+ }
72
+ }
@@ -0,0 +1,57 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ export class FileProcessor {
5
+
6
+ static readFile(filePath) {
7
+ if (!fs.existsSync(filePath)) {
8
+ throw new Error(`File not found: ${filePath}`)
9
+ }
10
+ return fs.readFileSync(filePath, 'utf-8')
11
+ }
12
+
13
+ static writeFile(filePath, content) {
14
+ fs.writeFileSync(filePath, content, 'utf-8')
15
+ return content.length
16
+ }
17
+
18
+ static readJson(filePath) {
19
+ const content = FileProcessor.readFile(filePath)
20
+ return JSON.parse(content)
21
+ }
22
+
23
+ static writeJson(filePath, data) {
24
+ const content = JSON.stringify(data, null, 2)
25
+ FileProcessor.writeFile(filePath, content)
26
+ }
27
+
28
+ static countLines(filePath) {
29
+ const content = FileProcessor.readFile(filePath)
30
+ const lines = content.split('\n')
31
+ return lines.length
32
+ }
33
+
34
+ static listFiles(directory) {
35
+ if (!fs.existsSync(directory)) {
36
+ throw new Error(`Directory not found: ${directory}`)
37
+ }
38
+ if (!fs.statSync(directory).isDirectory()) {
39
+ throw new Error(`Not a directory: ${directory}`)
40
+ }
41
+ const result = []
42
+ for (const entry of fs.readdirSync(directory)) {
43
+ const fullPath = path.join(directory, entry)
44
+ if (fs.statSync(fullPath).isFile()) {
45
+ result.push(entry)
46
+ }
47
+ }
48
+ return result
49
+ }
50
+
51
+ static ensureDirectory(directory) {
52
+ if (!fs.existsSync(directory)) {
53
+ fs.mkdirSync(directory, { recursive: true })
54
+ }
55
+ return directory
56
+ }
57
+ }