openmatrix 0.2.22 → 0.2.24

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.
@@ -163,31 +163,16 @@ function generateImports(exports, importPath, usesTypeScript) {
163
163
  : `const { ${exports.join(', ')} } = require('../../${importPath}');`;
164
164
  }
165
165
  /**
166
- * 生成测试块
166
+ * 生成测试块 - 根据文件类型生成有意义的业务测试
167
167
  */
168
168
  function generateTestBlock(exportName, framework, usesDescribeIt, fileType) {
169
169
  const useDescribe = usesDescribeIt ?? true;
170
- const describeOrTest = usesDescribeIt ? 'describe' : 'test';
171
170
  if (useDescribe) {
171
+ // 根据文件类型生成不同的测试场景
172
+ const testCases = generateBusinessTestCases(exportName, fileType);
173
+ const testBlocks = testCases.map(tc => generateTestCaseBlock(tc, exportName));
172
174
  return `describe('${getDescribeTitle(exportName, fileType)}', () => {
173
- it('should work correctly', () => {
174
- // Arrange
175
- const input = {};
176
-
177
- // Act
178
- const result = ${exportName}(input);
179
-
180
- // Assert
181
- expect(result).toBeDefined();
182
- });
183
-
184
- it('should handle edge cases', () => {
185
- // Arrange
186
- const input = null;
187
-
188
- // Act & Assert
189
- expect(() => ${exportName}(input)).not.toThrow();
190
- });
175
+ ${testBlocks.join('\n\n')}
191
176
  });`;
192
177
  }
193
178
  else {
@@ -197,6 +182,245 @@ function generateTestBlock(exportName, framework, usesDescribeIt, fileType) {
197
182
  });`;
198
183
  }
199
184
  }
185
+ /**
186
+ * 根据文件类型生成业务测试用例
187
+ */
188
+ function generateBusinessTestCases(exportName, fileType) {
189
+ switch (fileType) {
190
+ case 'service':
191
+ return [
192
+ {
193
+ name: 'should return valid result on successful execution',
194
+ arrange: `// Arrange - 准备有效的输入参数
195
+ const params = { id: 'test-id', data: {} };
196
+ const mockDependencies = {};`,
197
+ act: `// Act - 执行服务方法
198
+ const result = await ${exportName}(params);`,
199
+ assert: [
200
+ 'expect(result).toBeDefined();',
201
+ 'expect(result).toHaveProperty(\'data\');',
202
+ 'expect(result.error).toBeUndefined();'
203
+ ]
204
+ },
205
+ {
206
+ name: 'should handle invalid input gracefully',
207
+ arrange: `// Arrange - 准备无效输入
208
+ const invalidParams = { id: null, data: undefined };`,
209
+ act: `// Act - 使用无效参数调用
210
+ const result = await ${exportName}(invalidParams);`,
211
+ assert: [
212
+ 'expect(result).toBeDefined();',
213
+ 'expect(result).toHaveProperty(\'error\');'
214
+ ]
215
+ },
216
+ {
217
+ name: 'should handle empty parameters',
218
+ arrange: `// Arrange - 空参数测试`,
219
+ act: `// Act & Assert - 验证空参数处理
220
+ const result = await ${exportName}({});`,
221
+ assert: [
222
+ 'expect(result).toBeDefined();',
223
+ '// 应该返回默认值或错误信息,而非崩溃'
224
+ ]
225
+ },
226
+ {
227
+ name: 'should handle missing required fields',
228
+ arrange: `// Arrange - 缺少必填字段`,
229
+ act: `// Act`,
230
+ assert: [
231
+ `expect(() => ${exportName}(null)).not.toThrow();`,
232
+ 'expect(() => ${exportName}(undefined)).not.toThrow();'
233
+ ]
234
+ }
235
+ ];
236
+ case 'util':
237
+ return [
238
+ {
239
+ name: 'should process valid input correctly',
240
+ arrange: `// Arrange - 准备有效输入
241
+ const input = 'test-value';
242
+ const expected = 'expected-result';`,
243
+ act: `// Act
244
+ const result = ${exportName}(input);`,
245
+ assert: [
246
+ 'expect(result).toBeDefined();',
247
+ 'expect(typeof result).toBe(\'string\');'
248
+ ]
249
+ },
250
+ {
251
+ name: 'should handle null input',
252
+ arrange: `// Arrange - null 输入`,
253
+ act: `// Act & Assert`,
254
+ assert: [
255
+ `expect(() => ${exportName}(null)).not.toThrow();`,
256
+ 'const result = ${exportName}(null);',
257
+ 'expect(result).toBeDefined();'
258
+ ]
259
+ },
260
+ {
261
+ name: 'should handle empty string input',
262
+ arrange: `// Arrange - 空字符串输入`,
263
+ act: `// Act
264
+ const result = ${exportName}('');`,
265
+ assert: [
266
+ 'expect(result).toBeDefined();',
267
+ '// 验证空字符串的预期行为'
268
+ ]
269
+ },
270
+ {
271
+ name: 'should handle various input types',
272
+ arrange: `// Arrange - 多种输入类型测试`,
273
+ act: `// Act - 测试不同类型`,
274
+ assert: [
275
+ 'const numResult = ${exportName}(123);',
276
+ 'const boolResult = ${exportName}(true);',
277
+ 'const objResult = ${exportName}({ key: \'value\' });',
278
+ 'expect(numResult).toBeDefined();',
279
+ 'expect(boolResult).toBeDefined();',
280
+ 'expect(objResult).toBeDefined();'
281
+ ]
282
+ }
283
+ ];
284
+ case 'component':
285
+ return [
286
+ {
287
+ name: 'should render without crashing',
288
+ arrange: `// Arrange - 准备组件 props
289
+ const props = { title: \'Test Title\', onClick: vi.fn() };`,
290
+ act: `// Act - 渲染组件
291
+ const { container } = render(<${exportName} {...props} />);`,
292
+ assert: [
293
+ 'expect(container).toBeDefined();',
294
+ 'expect(container.firstChild).toBeInTheDocument();'
295
+ ]
296
+ },
297
+ {
298
+ name: 'should handle user interaction',
299
+ arrange: `// Arrange - 准备交互测试
300
+ const handleClick = vi.fn();
301
+ const props = { onClick: handleClick };`,
302
+ act: `// Act - 模拟用户点击
303
+ const { getByRole } = render(<${exportName} {...props} />);
304
+ fireEvent.click(getByRole(\'button\'));`,
305
+ assert: [
306
+ 'expect(handleClick).toHaveBeenCalledTimes(1);'
307
+ ]
308
+ },
309
+ {
310
+ name: 'should display correct content with props',
311
+ arrange: `// Arrange - 准备显示内容测试
312
+ const testContent = \'Hello World\';
313
+ const props = { children: testContent };`,
314
+ act: `// Act - 渲染并检查内容
315
+ const { getByText } = render(<${exportName} {...props} />);`,
316
+ assert: [
317
+ 'expect(getByText(testContent)).toBeInTheDocument();'
318
+ ]
319
+ },
320
+ {
321
+ name: 'should handle missing optional props',
322
+ arrange: `// Arrange - 无可选 props`,
323
+ act: `// Act - 仅传递必需 props
324
+ const { container } = render(<${exportName} />);`,
325
+ assert: [
326
+ 'expect(container).toBeDefined();',
327
+ '// 组件应使用默认值正常渲染'
328
+ ]
329
+ }
330
+ ];
331
+ case 'api':
332
+ return [
333
+ {
334
+ name: 'should return success response for valid request',
335
+ arrange: `// Arrange - 准备有效的 API 请求参数
336
+ const request = { method: \'GET\', path: \'/api/test\' };`,
337
+ act: `// Act - 执行 API 调用
338
+ const response = await ${exportName}(request);`,
339
+ assert: [
340
+ 'expect(response).toBeDefined();',
341
+ 'expect(response.status).toBe(200);',
342
+ 'expect(response.data).toBeDefined();'
343
+ ]
344
+ },
345
+ {
346
+ name: 'should handle 404 not found',
347
+ arrange: `// Arrange - 准备不存在的资源请求
348
+ const request = { method: \'GET\', path: \'/api/nonexistent\' };`,
349
+ act: `// Act - 请求不存在的资源
350
+ const response = await ${exportName}(request);`,
351
+ assert: [
352
+ 'expect(response.status).toBe(404);',
353
+ 'expect(response.error).toBeDefined();'
354
+ ]
355
+ },
356
+ {
357
+ name: 'should handle invalid request body',
358
+ arrange: `// Arrange - 准备无效的请求体
359
+ const request = { method: \'POST\', path: \'/api/test\', body: null };`,
360
+ act: `// Act - 发送无效请求
361
+ const response = await ${exportName}(request);`,
362
+ assert: [
363
+ 'expect(response.status).toBe(400);',
364
+ 'expect(response.error).toContain(\'invalid\');'
365
+ ]
366
+ },
367
+ {
368
+ name: 'should handle network errors gracefully',
369
+ arrange: `// Arrange - 模拟网络错误
370
+ const request = { method: \'GET\', path: \'/api/error\' };
371
+ // Mock network failure`,
372
+ act: `// Act - 处理错误响应`,
373
+ assert: [
374
+ `await expect(${exportName}(request)).resolves.toBeDefined();`,
375
+ '// 应该返回错误对象而非抛出异常'
376
+ ]
377
+ }
378
+ ];
379
+ default:
380
+ // 通用模块测试
381
+ return [
382
+ {
383
+ name: 'should return defined result for valid input',
384
+ arrange: `// Arrange - 准备有效输入
385
+ const input = {};`,
386
+ act: `// Act - 执行函数
387
+ const result = ${exportName}(input);`,
388
+ assert: [
389
+ 'expect(result).toBeDefined();'
390
+ ]
391
+ },
392
+ {
393
+ name: 'should handle null input',
394
+ arrange: `// Arrange - null 输入`,
395
+ act: `// Act`,
396
+ assert: [
397
+ `expect(() => ${exportName}(null)).not.toThrow();`
398
+ ]
399
+ },
400
+ {
401
+ name: 'should handle undefined input',
402
+ arrange: `// Arrange - undefined 输入`,
403
+ act: `// Act`,
404
+ assert: [
405
+ `expect(() => ${exportName}(undefined)).not.toThrow();`
406
+ ]
407
+ }
408
+ ];
409
+ }
410
+ }
411
+ /**
412
+ * 生成单个测试用例块
413
+ */
414
+ function generateTestCaseBlock(tc, exportName) {
415
+ const indent = ' ';
416
+ const actIndent = tc.act.includes('\n') ? indent : indent + ' ';
417
+ const assertLines = tc.assert.map(a => `${indent} ${a}`).join('\n');
418
+ return `${indent}it('${tc.name}', () => {
419
+ ${indent} ${tc.arrange}
420
+ ${actIndent}${tc.act}
421
+ ${assertLines}
422
+ ${indent}});`;
423
+ }
200
424
  /**
201
425
  * 获取 describe 标题
202
426
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmatrix",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "description": "AI Agent task orchestration system with Claude Code Skills integration",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/skills/deploy.md CHANGED
@@ -91,6 +91,80 @@ cat requirements.txt pyproject.toml 2>/dev/null
91
91
 
92
92
  ---
93
93
 
94
+ ## Step 2.1: 检测并优化依赖源
95
+
96
+ 检测项目使用的包管理器和镜像源,如果是国外慢源则建议替换为国内镜像:
97
+
98
+ ```bash
99
+ # npm/yarn/pnpm 源
100
+ npm config get registry 2>/dev/null
101
+ cat .npmrc 2>/dev/null
102
+ cat .yarnrc 2>/dev/null
103
+ cat .yarnrc.yml 2>/dev/null
104
+
105
+ # pip 源
106
+ cat pip.conf 2>/dev/null
107
+ pip config get global.index-url 2>/dev/null
108
+
109
+ # Docker 镜像源
110
+ cat /etc/docker/daemon.json 2>/dev/null
111
+ cat ~/.docker/daemon.json 2>/dev/null
112
+
113
+ # Go 代理
114
+ go env GOPROXY 2>/dev/null
115
+
116
+ # Rust 源
117
+ cat ~/.cargo/config.toml 2>/dev/null
118
+
119
+ # Gradle/Maven 源
120
+ cat build.gradle 2>/dev/null | grep -i "repositories" -A 5
121
+ cat pom.xml 2>/dev/null | grep -i "mirror" -A 5
122
+ ```
123
+
124
+ **AI 分析源速度,如果检测到国外源,建议替换:**
125
+
126
+ | 包管理器 | 国外源 | 国内镜像 |
127
+ |---------|--------|---------|
128
+ | npm/yarn/pnpm | registry.npmjs.org | https://registry.npmmirror.com |
129
+ | pip | pypi.org | https://pypi.tuna.tsinghua.edu.cn/simple |
130
+ | Docker | docker.io | mirror.ccs.tencentyun.com |
131
+ | Go | proxy.golang.org | https://goproxy.cn,direct |
132
+ | Rust/Cargo | crates.io | https://rsproxy.cn/crates.io-index |
133
+ | Gradle/Maven | repo.maven.apache.org | https://maven.aliyun.com/repository/public |
134
+
135
+ **如果检测到慢源,输出建议并询问:**
136
+
137
+ ```
138
+ AskUserQuestion:
139
+ header: "镜像源"
140
+ question: "检测到 npm 使用官方源(registry.npmjs.org),国内访问较慢。是否切换到国内镜像?"
141
+
142
+ options:
143
+ - label: "切换到淘宝镜像 (推荐)"
144
+ description: "npmmirror.com,国内速度快,同步延迟 < 10 分钟"
145
+ - label: "保持不变"
146
+ description: "当前源可用,不需要切换"
147
+ - label: "使用其他镜像"
148
+ description: "指定自定义镜像地址"
149
+ ```
150
+
151
+ **用户确认后执行切换:**
152
+ ```bash
153
+ # npm
154
+ npm config set registry https://registry.npmmirror.com
155
+
156
+ # pip
157
+ pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
158
+
159
+ # Go
160
+ go env -w GOPROXY=https://goproxy.cn,direct
161
+
162
+ # Dockerfile 中的 apt 源(如果有)
163
+ # AI 读取 Dockerfile,将 archive.ubuntu.com 替换为 mirrors.aliyun.com
164
+ ```
165
+
166
+ ---
167
+
94
168
  ## Step 3: 检测系统环境
95
169
 
96
170
  ```bash
@@ -181,6 +255,40 @@ AskUserQuestion:
181
255
 
182
256
  ---
183
257
 
258
+ ## Step 6.1: 生成部署任务清单
259
+
260
+ 根据前面的分析和用户选择,将所有部署步骤写入 TodoWrite,逐条执行:
261
+
262
+ **示例(Docker 本地部署 + 源优化 + 脚本生成):**
263
+
264
+ ```typescript
265
+ TodoWrite({
266
+ todos: [
267
+ { content: "切换 npm 源到淘宝镜像", activeForm: "正在切换 npm 镜像源", status: "pending" },
268
+ { content: "构建 Docker 镜像", activeForm: "正在构建 Docker 镜像", status: "pending" },
269
+ { content: "启动容器", activeForm: "正在启动容器", status: "pending" },
270
+ { content: "验证部署:容器状态", activeForm: "正在检查容器状态", status: "pending" },
271
+ { content: "验证部署:端口监听", activeForm: "正在检查端口", status: "pending" },
272
+ { content: "验证部署:HTTP 连通", activeForm: "正在验证 HTTP 连通性", status: "pending" },
273
+ { content: "生成 Taskfile.yml 一键脚本", activeForm: "正在生成一键脚本", status: "pending" },
274
+ { content: "保存部署配置", activeForm: "正在保存部署配置", status: "pending" }
275
+ ]
276
+ })
277
+ ```
278
+
279
+ **任务清单根据实际情况动态生成:**
280
+ - 需要切换源 → 加入源切换任务
281
+ - 需要生成 Dockerfile → 加入生成任务
282
+ - 多服务 → 每个服务一条任务
283
+ - 选择生成脚本 → 加入脚本生成任务
284
+
285
+ **执行规则:**
286
+ - 每次只执行一条,完成后立即标记 completed
287
+ - 失败时停止,输出错误信息
288
+ - 用户可以看到整体进度
289
+
290
+ ---
291
+
184
292
  ## Step 7: 执行部署
185
293
 
186
294
  根据用户选择,AI 执行对应命令:
package/skills/test.md CHANGED
@@ -392,6 +392,35 @@ ${uiComponents.map(c => `
392
392
 
393
393
  ## Step 7: 自动验证测试(循环机制)
394
394
 
395
+ <LOOP_ENFORCEMENT>
396
+ **此步骤是验证循环,必须执行直到验证通过或达到最大重试次数。**
397
+
398
+ ❌ **禁止在验证失败时停止** — 即使 Agent 返回了大段输出,也必须继续循环
399
+ ❌ **禁止询问"是否继续验证"** — 自动执行验证,无需用户确认
400
+ ❌ **禁止输出"让我知道是否..."后停止** — 继续执行验证循环
401
+ ❌ **禁止因为上下文压缩而忘记重试次数** — 使用 TodoWrite 持久化 retryCount
402
+
403
+ **retryCount 持久化机制(防止上下文压缩丢失状态):**
404
+
405
+ 验证循环开始时,使用 TodoWrite 记录状态:
406
+ ```
407
+ TodoWrite([
408
+ { content: "验证测试(第 N 次)", activeForm: "正在验证测试(第 N 次)", status: "in_progress" }
409
+ ])
410
+ ```
411
+
412
+ 每次验证失败后:
413
+ 1. 更新 TodoWrite 的 retryCount(N → N+1)
414
+ 2. 检查是否达到最大次数(>= 3)
415
+ 3. 未达到 → 自动回到 Step 6 重新生成
416
+ 4. 已达到 → 暂停并报告问题
417
+
418
+ **循环铁律:**
419
+ - retryCount 从 TodoWrite 读取,不依赖上下文记忆
420
+ - 验证失败必须自动循环,不得跳过
421
+ - 最大重试 3 次,超过必须暂停
422
+ </LOOP_ENFORCEMENT>
423
+
395
424
  **⛔ 自动验证流程 - 无需用户手动确认**
396
425
 
397
426
  ### 7.1 执行验证命令(自动判断结果)
@@ -414,15 +443,15 @@ fi
414
443
  **验证失败时自动处理:**
415
444
 
416
445
  ```
417
- 验证失败 → retryCount + 1 → 检查重试次数 →
446
+ 验证失败 → TodoWrite 更新 retryCount → 检查重试次数 →
418
447
  ├─ < 3 次 → 自动回到 Step 6 重新生成(带失败信息)
419
448
  └─ >= 3 次 → 暂停,报告问题,可能是测试框架配置问题
420
449
  ```
421
450
 
422
- **重试计数器更新:**
423
- - Skill 内部维护 `retryCount` 变量
424
- - 每次验证失败 +1
425
- - 验证通过后重置为 0
451
+ **重试计数器持久化:**
452
+ - 使用 TodoWrite 记录 retryCount
453
+ - 每次验证失败后更新 TodoWrite(retryCount + 1
454
+ - 验证通过后更新 TodoWrite 为 completed
426
455
 
427
456
  ### 7.3 自动循环回生成步骤
428
457