@rotifer/playground 0.5.0-alpha.2 → 0.7.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 (316) hide show
  1. package/CHANGELOG.md +159 -15
  2. package/LICENSE +1 -1
  3. package/README.md +51 -23
  4. package/README.zh.md +54 -25
  5. package/dist/cloud/auth.d.ts +7 -1
  6. package/dist/cloud/auth.d.ts.map +1 -1
  7. package/dist/cloud/auth.js +65 -1
  8. package/dist/cloud/auth.js.map +1 -1
  9. package/dist/cloud/client.d.ts +4 -1
  10. package/dist/cloud/client.d.ts.map +1 -1
  11. package/dist/cloud/client.js +9 -2
  12. package/dist/cloud/client.js.map +1 -1
  13. package/dist/cloud/types.d.ts +3 -1
  14. package/dist/cloud/types.d.ts.map +1 -1
  15. package/dist/cloud/types.js.map +1 -1
  16. package/dist/commands/agent-create.d.ts.map +1 -1
  17. package/dist/commands/agent-create.js +66 -3
  18. package/dist/commands/agent-create.js.map +1 -1
  19. package/dist/commands/agent-run.d.ts.map +1 -1
  20. package/dist/commands/agent-run.js +296 -32
  21. package/dist/commands/agent-run.js.map +1 -1
  22. package/dist/commands/arena-submit.d.ts.map +1 -1
  23. package/dist/commands/arena-submit.js +45 -17
  24. package/dist/commands/arena-submit.js.map +1 -1
  25. package/dist/commands/compile.d.ts.map +1 -1
  26. package/dist/commands/compile.js +9 -3
  27. package/dist/commands/compile.js.map +1 -1
  28. package/dist/commands/init.d.ts.map +1 -1
  29. package/dist/commands/init.js +13 -3
  30. package/dist/commands/init.js.map +1 -1
  31. package/dist/commands/login.d.ts.map +1 -1
  32. package/dist/commands/login.js +23 -16
  33. package/dist/commands/login.js.map +1 -1
  34. package/dist/commands/network.js +4 -4
  35. package/dist/commands/network.js.map +1 -1
  36. package/dist/commands/publish.d.ts.map +1 -1
  37. package/dist/commands/publish.js +162 -44
  38. package/dist/commands/publish.js.map +1 -1
  39. package/dist/commands/test.d.ts.map +1 -1
  40. package/dist/commands/test.js +209 -23
  41. package/dist/commands/test.js.map +1 -1
  42. package/dist/commands/wrap.d.ts.map +1 -1
  43. package/dist/commands/wrap.js +17 -1
  44. package/dist/commands/wrap.js.map +1 -1
  45. package/dist/index.js +0 -0
  46. package/dist/runtime/network-gateway.d.ts +53 -0
  47. package/dist/runtime/network-gateway.d.ts.map +1 -0
  48. package/dist/runtime/network-gateway.js +147 -0
  49. package/dist/runtime/network-gateway.js.map +1 -0
  50. package/dist/utils/binding.d.ts +25 -0
  51. package/dist/utils/binding.d.ts.map +1 -1
  52. package/dist/utils/binding.js.map +1 -1
  53. package/dist/utils/open-browser.d.ts +10 -0
  54. package/dist/utils/open-browser.d.ts.map +1 -0
  55. package/dist/utils/open-browser.js +23 -0
  56. package/dist/utils/open-browser.js.map +1 -0
  57. package/genes/academic-writer/.cloud-manifest.json +6 -0
  58. package/genes/academic-writer/.gene-manifest.json +8 -0
  59. package/genes/academic-writer/SKILL.md +274 -0
  60. package/genes/academic-writer/phenotype.json +28 -0
  61. package/genes/ai-components/.cloud-manifest.json +6 -0
  62. package/genes/ai-components/.gene-manifest.json +8 -0
  63. package/genes/ai-components/SKILL.md +381 -0
  64. package/genes/ai-components/phenotype.json +28 -0
  65. package/genes/algorithmic-art/.cloud-manifest.json +6 -0
  66. package/genes/algorithmic-art/.gene-manifest.json +8 -0
  67. package/genes/algorithmic-art/SKILL.md +405 -0
  68. package/genes/algorithmic-art/phenotype.json +28 -0
  69. package/genes/answer-synthesizer/.cloud-manifest.json +6 -0
  70. package/genes/answer-synthesizer/index.ts +194 -0
  71. package/genes/answer-synthesizer/phenotype.json +61 -0
  72. package/genes/api-designer/.cloud-manifest.json +6 -0
  73. package/genes/api-designer/.gene-manifest.json +8 -0
  74. package/genes/api-designer/SKILL.md +456 -0
  75. package/genes/api-designer/phenotype.json +28 -0
  76. package/genes/auto-coder/.cloud-manifest.json +6 -0
  77. package/genes/auto-coder/.gene-manifest.json +8 -0
  78. package/genes/auto-coder/SKILL.md +400 -0
  79. package/genes/auto-coder/phenotype.json +28 -0
  80. package/genes/auto-writer/.cloud-manifest.json +6 -0
  81. package/genes/auto-writer/.gene-manifest.json +8 -0
  82. package/genes/auto-writer/SKILL.md +361 -0
  83. package/genes/auto-writer/phenotype.json +28 -0
  84. package/genes/brand-personality/.cloud-manifest.json +6 -0
  85. package/genes/brand-personality/.gene-manifest.json +8 -0
  86. package/genes/brand-personality/SKILL.md +549 -0
  87. package/genes/brand-personality/phenotype.json +28 -0
  88. package/genes/business-writer/.cloud-manifest.json +6 -0
  89. package/genes/business-writer/.gene-manifest.json +8 -0
  90. package/genes/business-writer/SKILL.md +448 -0
  91. package/genes/business-writer/phenotype.json +28 -0
  92. package/genes/citation-manager/.cloud-manifest.json +6 -0
  93. package/genes/citation-manager/.gene-manifest.json +8 -0
  94. package/genes/citation-manager/SKILL.md +279 -0
  95. package/genes/citation-manager/index.ts +162 -0
  96. package/genes/citation-manager/package.json +1 -0
  97. package/genes/citation-manager/phenotype.json +50 -0
  98. package/genes/code-complexity/.cloud-manifest.json +6 -0
  99. package/genes/code-complexity/README.md +35 -0
  100. package/genes/code-complexity/index.ts +101 -0
  101. package/genes/code-complexity/phenotype.json +34 -0
  102. package/genes/copywriter/.cloud-manifest.json +6 -0
  103. package/genes/copywriter/.gene-manifest.json +8 -0
  104. package/genes/copywriter/SKILL.md +329 -0
  105. package/genes/copywriter/phenotype.json +28 -0
  106. package/genes/creative-writer/.cloud-manifest.json +6 -0
  107. package/genes/creative-writer/.gene-manifest.json +8 -0
  108. package/genes/creative-writer/SKILL.md +356 -0
  109. package/genes/creative-writer/phenotype.json +28 -0
  110. package/genes/data-modeler/.cloud-manifest.json +6 -0
  111. package/genes/data-modeler/.gene-manifest.json +8 -0
  112. package/genes/data-modeler/SKILL.md +486 -0
  113. package/genes/data-modeler/phenotype.json +28 -0
  114. package/genes/debugger/.cloud-manifest.json +6 -0
  115. package/genes/debugger/.gene-manifest.json +8 -0
  116. package/genes/debugger/SKILL.md +416 -0
  117. package/genes/debugger/phenotype.json +28 -0
  118. package/genes/design-tokens/.cloud-manifest.json +6 -0
  119. package/genes/design-tokens/.gene-manifest.json +8 -0
  120. package/genes/design-tokens/SKILL.md +222 -0
  121. package/genes/design-tokens/index.ts +128 -0
  122. package/genes/design-tokens/package.json +1 -0
  123. package/genes/design-tokens/phenotype.json +1 -0
  124. package/genes/devops-automator/.cloud-manifest.json +6 -0
  125. package/genes/devops-automator/.gene-manifest.json +8 -0
  126. package/genes/devops-automator/SKILL.md +490 -0
  127. package/genes/devops-automator/phenotype.json +28 -0
  128. package/genes/doc-coauthoring/.cloud-manifest.json +6 -0
  129. package/genes/doc-coauthoring/.gene-manifest.json +8 -0
  130. package/genes/doc-coauthoring/SKILL.md +375 -0
  131. package/genes/doc-coauthoring/phenotype.json +28 -0
  132. package/genes/doc-retrieval/.cloud-manifest.json +6 -0
  133. package/genes/doc-retrieval/index.ts +134 -0
  134. package/genes/doc-retrieval/phenotype.json +54 -0
  135. package/genes/docs-writer/.cloud-manifest.json +6 -0
  136. package/genes/docs-writer/.gene-manifest.json +8 -0
  137. package/genes/docs-writer/SKILL.md +492 -0
  138. package/genes/docs-writer/phenotype.json +28 -0
  139. package/genes/evolve-life/.cloud-manifest.json +6 -0
  140. package/genes/evolve-life/.compile-result.json +12 -0
  141. package/genes/evolve-life/README.md +52 -0
  142. package/genes/evolve-life/gene.ir.wasm +0 -0
  143. package/genes/evolve-life/gene.wasm +0 -0
  144. package/genes/evolve-life/index.ts +255 -0
  145. package/genes/evolve-life/phenotype.json +129 -0
  146. package/genes/evolve-life-bitwise/.cloud-manifest.json +6 -0
  147. package/genes/evolve-life-bitwise/.compile-result.json +12 -0
  148. package/genes/evolve-life-bitwise/gene.ir.wasm +0 -0
  149. package/genes/evolve-life-bitwise/gene.wasm +0 -0
  150. package/genes/evolve-life-bitwise/index.ts +273 -0
  151. package/genes/evolve-life-bitwise/phenotype.json +129 -0
  152. package/genes/evolve-life-sparse/.cloud-manifest.json +6 -0
  153. package/genes/evolve-life-sparse/.compile-result.json +12 -0
  154. package/genes/evolve-life-sparse/gene.ir.wasm +0 -0
  155. package/genes/evolve-life-sparse/gene.wasm +0 -0
  156. package/genes/evolve-life-sparse/index.ts +236 -0
  157. package/genes/evolve-life-sparse/phenotype.json +129 -0
  158. package/genes/fact-checker/.cloud-manifest.json +6 -0
  159. package/genes/fact-checker/.gene-manifest.json +8 -0
  160. package/genes/fact-checker/SKILL.md +373 -0
  161. package/genes/fact-checker/phenotype.json +28 -0
  162. package/genes/genesis-code-format/.cloud-manifest.json +6 -0
  163. package/genes/genesis-code-format/package.json +1 -0
  164. package/genes/genesis-code-format/phenotype.json +1 -0
  165. package/genes/genesis-file-read/.cloud-manifest.json +6 -0
  166. package/genes/genesis-file-read/index.ts +11 -1
  167. package/genes/genesis-file-read/package.json +1 -0
  168. package/genes/genesis-file-read/phenotype.json +1 -0
  169. package/genes/genesis-l0-constraint/.cloud-manifest.json +6 -0
  170. package/genes/genesis-l0-constraint/package.json +1 -0
  171. package/genes/genesis-l0-constraint/phenotype.json +1 -0
  172. package/genes/genesis-web-search/.cloud-manifest.json +2 -2
  173. package/genes/genesis-web-search/package.json +1 -0
  174. package/genes/genesis-web-search/phenotype.json +1 -0
  175. package/genes/genesis-web-search-lite/.cloud-manifest.json +6 -0
  176. package/genes/genesis-web-search-lite/package.json +1 -0
  177. package/genes/genesis-web-search-lite/phenotype.json +1 -0
  178. package/genes/git-workflow/.cloud-manifest.json +6 -0
  179. package/genes/git-workflow/.gene-manifest.json +8 -0
  180. package/genes/git-workflow/SKILL.md +407 -0
  181. package/genes/git-workflow/phenotype.json +28 -0
  182. package/genes/grammar-checker/.cloud-manifest.json +6 -0
  183. package/genes/grammar-checker/.gene-manifest.json +8 -0
  184. package/genes/grammar-checker/SKILL.md +194 -0
  185. package/genes/grammar-checker/index.ts +168 -0
  186. package/genes/grammar-checker/package.json +1 -0
  187. package/genes/grammar-checker/phenotype.json +52 -0
  188. package/genes/json-validator/.cloud-manifest.json +6 -0
  189. package/genes/json-validator/README.md +42 -0
  190. package/genes/json-validator/index.ts +112 -0
  191. package/genes/json-validator/phenotype.json +42 -0
  192. package/genes/license-advisor/.cloud-manifest.json +6 -0
  193. package/genes/license-advisor/.gene-manifest.json +8 -0
  194. package/genes/license-advisor/SKILL.md +117 -0
  195. package/genes/license-advisor/phenotype.json +28 -0
  196. package/genes/logic-architect/.cloud-manifest.json +6 -0
  197. package/genes/logic-architect/.gene-manifest.json +8 -0
  198. package/genes/logic-architect/SKILL.md +451 -0
  199. package/genes/logic-architect/phenotype.json +28 -0
  200. package/genes/markdown-formatter/.cloud-manifest.json +6 -0
  201. package/genes/markdown-formatter/README.md +34 -0
  202. package/genes/markdown-formatter/index.ts +86 -0
  203. package/genes/markdown-formatter/phenotype.json +32 -0
  204. package/genes/orch/.cloud-manifest.json +6 -0
  205. package/genes/orch/.gene-manifest.json +8 -0
  206. package/genes/orch/SKILL.md +504 -0
  207. package/genes/orch/phenotype.json +28 -0
  208. package/genes/particle-barneshut/.cloud-manifest.json +6 -0
  209. package/genes/particle-barneshut/.compile-result.json +12 -0
  210. package/genes/particle-barneshut/README.md +55 -0
  211. package/genes/particle-barneshut/gene.ir.wasm +0 -0
  212. package/genes/particle-barneshut/gene.wasm +0 -0
  213. package/genes/particle-barneshut/index.ts +486 -0
  214. package/genes/particle-barneshut/phenotype.json +137 -0
  215. package/genes/particle-brute/.cloud-manifest.json +6 -0
  216. package/genes/particle-brute/.compile-result.json +12 -0
  217. package/genes/particle-brute/README.md +55 -0
  218. package/genes/particle-brute/gene.ir.wasm +0 -0
  219. package/genes/particle-brute/gene.wasm +0 -0
  220. package/genes/particle-brute/index.ts +277 -0
  221. package/genes/particle-brute/phenotype.json +137 -0
  222. package/genes/particle-spatial/.cloud-manifest.json +6 -0
  223. package/genes/particle-spatial/.compile-result.json +12 -0
  224. package/genes/particle-spatial/README.md +53 -0
  225. package/genes/particle-spatial/gene.ir.wasm +0 -0
  226. package/genes/particle-spatial/gene.wasm +0 -0
  227. package/genes/particle-spatial/index.ts +352 -0
  228. package/genes/particle-spatial/phenotype.json +137 -0
  229. package/genes/performance-optimizer/.cloud-manifest.json +6 -0
  230. package/genes/performance-optimizer/.gene-manifest.json +8 -0
  231. package/genes/performance-optimizer/SKILL.md +480 -0
  232. package/genes/performance-optimizer/phenotype.json +28 -0
  233. package/genes/plagiarism-checker/.cloud-manifest.json +6 -0
  234. package/genes/plagiarism-checker/.gene-manifest.json +8 -0
  235. package/genes/plagiarism-checker/SKILL.md +342 -0
  236. package/genes/plagiarism-checker/phenotype.json +28 -0
  237. package/genes/product-manager/.cloud-manifest.json +6 -0
  238. package/genes/product-manager/.gene-manifest.json +8 -0
  239. package/genes/product-manager/SKILL.md +249 -0
  240. package/genes/product-manager/phenotype.json +28 -0
  241. package/genes/project-reviewer/.cloud-manifest.json +6 -0
  242. package/genes/project-reviewer/.gene-manifest.json +8 -0
  243. package/genes/project-reviewer/SKILL.md +312 -0
  244. package/genes/project-reviewer/phenotype.json +28 -0
  245. package/genes/prompt-engineer/.cloud-manifest.json +6 -0
  246. package/genes/prompt-engineer/.gene-manifest.json +8 -0
  247. package/genes/prompt-engineer/SKILL.md +411 -0
  248. package/genes/prompt-engineer/phenotype.json +28 -0
  249. package/genes/readability-analyzer/.cloud-manifest.json +6 -0
  250. package/genes/readability-analyzer/.gene-manifest.json +8 -0
  251. package/genes/readability-analyzer/SKILL.md +357 -0
  252. package/genes/readability-analyzer/index.ts +123 -0
  253. package/genes/readability-analyzer/package.json +1 -0
  254. package/genes/readability-analyzer/phenotype.json +35 -0
  255. package/genes/rotifer-protocol/SKILL.md +121 -0
  256. package/genes/security-auditor/.cloud-manifest.json +6 -0
  257. package/genes/security-auditor/.gene-manifest.json +8 -0
  258. package/genes/security-auditor/SKILL.md +494 -0
  259. package/genes/security-auditor/phenotype.json +28 -0
  260. package/genes/seo-optimizer/.cloud-manifest.json +6 -0
  261. package/genes/seo-optimizer/.gene-manifest.json +8 -0
  262. package/genes/seo-optimizer/SKILL.md +327 -0
  263. package/genes/seo-optimizer/index.ts +206 -0
  264. package/genes/seo-optimizer/package.json +1 -0
  265. package/genes/seo-optimizer/phenotype.json +1 -0
  266. package/genes/source-linker/.cloud-manifest.json +6 -0
  267. package/genes/source-linker/index.ts +88 -0
  268. package/genes/source-linker/phenotype.json +45 -0
  269. package/genes/style-optimizer/.cloud-manifest.json +6 -0
  270. package/genes/style-optimizer/.gene-manifest.json +8 -0
  271. package/genes/style-optimizer/SKILL.md +285 -0
  272. package/genes/style-optimizer/phenotype.json +28 -0
  273. package/genes/tech-lead/.cloud-manifest.json +6 -0
  274. package/genes/tech-lead/.gene-manifest.json +8 -0
  275. package/genes/tech-lead/SKILL.md +451 -0
  276. package/genes/tech-lead/phenotype.json +28 -0
  277. package/genes/test-wrap/.cloud-manifest.json +6 -0
  278. package/genes/test-wrap/.gene-manifest.json +8 -0
  279. package/genes/test-wrap/phenotype.json +28 -0
  280. package/genes/testing-strategist/.cloud-manifest.json +6 -0
  281. package/genes/testing-strategist/.gene-manifest.json +8 -0
  282. package/genes/testing-strategist/SKILL.md +500 -0
  283. package/genes/testing-strategist/phenotype.json +28 -0
  284. package/genes/text-summarizer/.cloud-manifest.json +6 -0
  285. package/genes/text-summarizer/README.md +34 -0
  286. package/genes/text-summarizer/index.ts +122 -0
  287. package/genes/text-summarizer/phenotype.json +32 -0
  288. package/genes/tone-analyzer/.cloud-manifest.json +6 -0
  289. package/genes/tone-analyzer/.gene-manifest.json +8 -0
  290. package/genes/tone-analyzer/SKILL.md +410 -0
  291. package/genes/tone-analyzer/phenotype.json +28 -0
  292. package/genes/translator/.cloud-manifest.json +6 -0
  293. package/genes/translator/.gene-manifest.json +8 -0
  294. package/genes/translator/SKILL.md +355 -0
  295. package/genes/translator/phenotype.json +28 -0
  296. package/genes/ui-components/.cloud-manifest.json +6 -0
  297. package/genes/ui-components/.gene-manifest.json +8 -0
  298. package/genes/ui-components/SKILL.md +467 -0
  299. package/genes/ui-components/phenotype.json +28 -0
  300. package/genes/uiux-designer/.cloud-manifest.json +6 -0
  301. package/genes/uiux-designer/.gene-manifest.json +8 -0
  302. package/genes/uiux-designer/SKILL.md +353 -0
  303. package/genes/uiux-designer/phenotype.json +28 -0
  304. package/genes/url-extractor/.cloud-manifest.json +6 -0
  305. package/genes/url-extractor/README.md +37 -0
  306. package/genes/url-extractor/index.ts +86 -0
  307. package/genes/url-extractor/phenotype.json +48 -0
  308. package/genes/ux-patterns/.cloud-manifest.json +6 -0
  309. package/genes/ux-patterns/.gene-manifest.json +8 -0
  310. package/genes/ux-patterns/SKILL.md +872 -0
  311. package/genes/ux-patterns/phenotype.json +28 -0
  312. package/genes/web3-components/.cloud-manifest.json +6 -0
  313. package/genes/web3-components/.gene-manifest.json +8 -0
  314. package/genes/web3-components/SKILL.md +390 -0
  315. package/genes/web3-components/phenotype.json +28 -0
  316. package/package.json +6 -5
@@ -0,0 +1,500 @@
1
+ ---
2
+ name: testing-strategist
3
+ description: Design testing strategies for AI and Web3 applications. Create test cases, mock data, and CI configurations. Use when writing tests, setting up test infrastructure, or when the user mentions testing, test coverage, unit test, integration test, or e2e.
4
+ ---
5
+
6
+ # Testing Strategist (测试策略师)
7
+
8
+ **Goal**: 为 AI + Web3 应用设计高效的测试策略,确保代码质量和系统稳定性。
9
+
10
+ **OPC 原则**: 测试要精准,不追求 100% 覆盖率,聚焦核心路径。
11
+
12
+ ---
13
+
14
+ ## Testing Philosophy
15
+
16
+ ```
17
+ 测试金字塔 (OPC 版):
18
+ ┌─────────┐
19
+ │ E2E │ ← 最少 (关键用户流程)
20
+ ─┴─────────┴─
21
+ ┌───────────────┐
22
+ │ Integration │ ← 适量 (API/DB)
23
+ ──┴───────────────┴──
24
+ ┌───────────────────────┐
25
+ │ Unit Tests │ ← 最多 (纯函数)
26
+ ──┴───────────────────────┴──
27
+ ```
28
+
29
+ ---
30
+
31
+ ## 1. Unit Testing
32
+
33
+ ### Pure Functions (优先测试)
34
+
35
+ ```typescript
36
+ // utils/format.test.ts
37
+ import { describe, it, expect } from 'vitest'
38
+ import { truncateAddress, formatTokenAmount } from './format'
39
+
40
+ describe('truncateAddress', () => {
41
+ it('truncates correctly', () => {
42
+ expect(truncateAddress('0x1234567890abcdef1234567890abcdef12345678'))
43
+ .toBe('0x1234...5678')
44
+ })
45
+
46
+ it('handles short addresses', () => {
47
+ expect(truncateAddress('0x1234')).toBe('0x1234')
48
+ })
49
+
50
+ it('handles empty input', () => {
51
+ expect(truncateAddress('')).toBe('')
52
+ })
53
+ })
54
+
55
+ describe('formatTokenAmount', () => {
56
+ it('formats large numbers with decimals', () => {
57
+ expect(formatTokenAmount(1500000000000000000n, 18)).toBe('1.5')
58
+ })
59
+
60
+ it('handles zero', () => {
61
+ expect(formatTokenAmount(0n, 18)).toBe('0')
62
+ })
63
+ })
64
+ ```
65
+
66
+ ### Testing Hooks
67
+
68
+ ```typescript
69
+ // hooks/useWallet.test.ts
70
+ import { renderHook, act } from '@testing-library/react'
71
+ import { useWallet } from './useWallet'
72
+
73
+ describe('useWallet', () => {
74
+ it('starts disconnected', () => {
75
+ const { result } = renderHook(() => useWallet())
76
+ expect(result.current.status).toBe('disconnected')
77
+ })
78
+
79
+ it('connects successfully', async () => {
80
+ const { result } = renderHook(() => useWallet())
81
+
82
+ await act(async () => {
83
+ await result.current.connect()
84
+ })
85
+
86
+ expect(result.current.status).toBe('connected')
87
+ })
88
+ })
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 2. AI Testing Strategies
94
+
95
+ ### Response Structure Testing
96
+
97
+ ```typescript
98
+ // ai/analyzer.test.ts
99
+ import { describe, it, expect } from 'vitest'
100
+ import { analyzeText } from './analyzer'
101
+ import { AnalysisSchema } from './schemas'
102
+
103
+ describe('analyzeText', () => {
104
+ it('returns valid schema', async () => {
105
+ const result = await analyzeText('Test input')
106
+
107
+ // 验证结构
108
+ expect(() => AnalysisSchema.parse(result)).not.toThrow()
109
+ })
110
+
111
+ it('handles empty input gracefully', async () => {
112
+ const result = await analyzeText('')
113
+
114
+ expect(result.error).toBe('EMPTY_INPUT')
115
+ })
116
+ })
117
+ ```
118
+
119
+ ### AI Response Mocking
120
+
121
+ ```typescript
122
+ // __mocks__/ai.ts
123
+ export const mockAIResponse = (response: unknown) => {
124
+ vi.mock('ai', () => ({
125
+ generateObject: vi.fn().mockResolvedValue({ object: response }),
126
+ generateText: vi.fn().mockResolvedValue({ text: JSON.stringify(response) }),
127
+ }))
128
+ }
129
+
130
+ // 使用
131
+ describe('ChatService', () => {
132
+ beforeEach(() => {
133
+ mockAIResponse({
134
+ summary: 'Test summary',
135
+ sentiment: 'positive',
136
+ })
137
+ })
138
+
139
+ it('processes AI response correctly', async () => {
140
+ const result = await chatService.analyze('input')
141
+ expect(result.summary).toBe('Test summary')
142
+ })
143
+ })
144
+ ```
145
+
146
+ ### Snapshot Testing for Prompts
147
+
148
+ ```typescript
149
+ // prompts/analyzer.test.ts
150
+ import { buildAnalyzerPrompt } from './analyzer'
151
+
152
+ describe('Analyzer Prompt', () => {
153
+ it('generates consistent prompt', () => {
154
+ const prompt = buildAnalyzerPrompt({
155
+ input: 'Test data',
156
+ context: 'Financial',
157
+ })
158
+
159
+ expect(prompt).toMatchSnapshot()
160
+ })
161
+ })
162
+ ```
163
+
164
+ ### Deterministic Testing
165
+
166
+ ```typescript
167
+ // 使用固定 seed 测试 AI 行为一致性
168
+ describe('AI Consistency', () => {
169
+ it('produces consistent output for same input', async () => {
170
+ const input = 'Analyze this market data'
171
+
172
+ const results = await Promise.all([
173
+ analyzeWithSeed(input, 42),
174
+ analyzeWithSeed(input, 42),
175
+ ])
176
+
177
+ expect(results[0]).toEqual(results[1])
178
+ })
179
+ })
180
+ ```
181
+
182
+ ---
183
+
184
+ ## 3. Web3 Testing Strategies
185
+
186
+ ### Contract Interaction Mocking
187
+
188
+ ```typescript
189
+ // __mocks__/wagmi.ts
190
+ import { vi } from 'vitest'
191
+
192
+ export const mockUseAccount = (overrides = {}) => {
193
+ return {
194
+ address: '0x1234567890abcdef1234567890abcdef12345678',
195
+ isConnected: true,
196
+ chainId: 1,
197
+ ...overrides,
198
+ }
199
+ }
200
+
201
+ export const mockUseWriteContract = () => ({
202
+ writeContract: vi.fn(),
203
+ isPending: false,
204
+ error: null,
205
+ })
206
+ ```
207
+
208
+ ### Transaction Flow Testing
209
+
210
+ ```typescript
211
+ // hooks/useTransfer.test.ts
212
+ describe('useTransfer', () => {
213
+ it('handles successful transfer', async () => {
214
+ const { result } = renderHook(() => useTransfer())
215
+
216
+ await act(async () => {
217
+ await result.current.transfer('0x...', 1000000000000000000n)
218
+ })
219
+
220
+ expect(result.current.status).toBe('confirmed')
221
+ expect(result.current.hash).toBeDefined()
222
+ })
223
+
224
+ it('handles insufficient balance', async () => {
225
+ mockBalance(0n) // 余额为 0
226
+
227
+ const { result } = renderHook(() => useTransfer())
228
+
229
+ await act(async () => {
230
+ await result.current.transfer('0x...', 1000000000000000000n)
231
+ })
232
+
233
+ expect(result.current.error?.code).toBe('INSUFFICIENT_BALANCE')
234
+ })
235
+
236
+ it('handles network switch during tx', async () => {
237
+ const { result } = renderHook(() => useTransfer())
238
+
239
+ // 发起交易
240
+ act(() => { result.current.transfer('0x...', 1n) })
241
+
242
+ // 模拟网络切换
243
+ act(() => { mockChainSwitch(137) })
244
+
245
+ expect(result.current.error?.code).toBe('NETWORK_CHANGED')
246
+ })
247
+ })
248
+ ```
249
+
250
+ ### BigInt Edge Cases
251
+
252
+ ```typescript
253
+ describe('BigInt Handling', () => {
254
+ it('handles max uint256', () => {
255
+ const maxUint256 = 2n ** 256n - 1n
256
+ expect(formatTokenAmount(maxUint256, 18)).not.toThrow()
257
+ })
258
+
259
+ it('handles precision correctly', () => {
260
+ // 0.000000000000000001 ETH (1 wei)
261
+ expect(formatTokenAmount(1n, 18)).toBe('0.000000000000000001')
262
+ })
263
+ })
264
+ ```
265
+
266
+ ---
267
+
268
+ ## 4. Integration Testing
269
+
270
+ ### API Integration
271
+
272
+ ```typescript
273
+ // api/chat.integration.test.ts
274
+ import { describe, it, expect } from 'vitest'
275
+
276
+ describe('Chat API', () => {
277
+ it('POST /api/chat returns streamed response', async () => {
278
+ const response = await fetch('/api/chat', {
279
+ method: 'POST',
280
+ body: JSON.stringify({ message: 'Hello' }),
281
+ })
282
+
283
+ expect(response.status).toBe(200)
284
+ expect(response.headers.get('content-type')).toContain('text/event-stream')
285
+ })
286
+
287
+ it('handles rate limiting', async () => {
288
+ // 快速发送多个请求
289
+ const responses = await Promise.all(
290
+ Array(10).fill(null).map(() =>
291
+ fetch('/api/chat', { method: 'POST', body: '{}' })
292
+ )
293
+ )
294
+
295
+ const rateLimited = responses.filter(r => r.status === 429)
296
+ expect(rateLimited.length).toBeGreaterThan(0)
297
+ })
298
+ })
299
+ ```
300
+
301
+ ### Database Integration
302
+
303
+ ```typescript
304
+ // db/users.integration.test.ts
305
+ describe('User Repository', () => {
306
+ beforeEach(async () => {
307
+ await db.user.deleteMany()
308
+ })
309
+
310
+ it('creates and retrieves user', async () => {
311
+ const user = await userRepo.create({
312
+ address: '0x1234...',
313
+ email: 'test@example.com',
314
+ })
315
+
316
+ const found = await userRepo.findByAddress('0x1234...')
317
+ expect(found).toEqual(user)
318
+ })
319
+ })
320
+ ```
321
+
322
+ ---
323
+
324
+ ## 5. E2E Testing
325
+
326
+ ### Critical User Flows
327
+
328
+ ```typescript
329
+ // e2e/chat-flow.spec.ts
330
+ import { test, expect } from '@playwright/test'
331
+
332
+ test('complete chat flow', async ({ page }) => {
333
+ await page.goto('/')
334
+
335
+ // 连接钱包
336
+ await page.click('[data-testid="connect-wallet"]')
337
+ await page.click('[data-testid="metamask"]')
338
+
339
+ // 发送消息
340
+ await page.fill('[data-testid="chat-input"]', 'Hello AI')
341
+ await page.click('[data-testid="send-button"]')
342
+
343
+ // 等待响应
344
+ await expect(page.locator('[data-testid="ai-response"]')).toBeVisible({
345
+ timeout: 30000,
346
+ })
347
+
348
+ // 验证响应内容
349
+ const response = await page.textContent('[data-testid="ai-response"]')
350
+ expect(response?.length).toBeGreaterThan(0)
351
+ })
352
+ ```
353
+
354
+ ### Web3 Flow
355
+
356
+ ```typescript
357
+ // e2e/transaction-flow.spec.ts
358
+ test('complete transaction flow', async ({ page }) => {
359
+ await page.goto('/swap')
360
+
361
+ // 输入金额
362
+ await page.fill('[data-testid="amount-input"]', '0.1')
363
+
364
+ // 检查 gas 估算
365
+ await expect(page.locator('[data-testid="gas-estimate"]')).toBeVisible()
366
+
367
+ // 点击交易
368
+ await page.click('[data-testid="swap-button"]')
369
+
370
+ // 等待钱包确认
371
+ await expect(page.locator('[data-testid="tx-pending"]')).toBeVisible()
372
+
373
+ // 等待确认 (mock 环境)
374
+ await expect(page.locator('[data-testid="tx-confirmed"]')).toBeVisible({
375
+ timeout: 60000,
376
+ })
377
+ })
378
+ ```
379
+
380
+ ---
381
+
382
+ ## 6. Test Data & Mocks
383
+
384
+ ### Mock Data Factory
385
+
386
+ ```typescript
387
+ // __fixtures__/factory.ts
388
+ import { faker } from '@faker-js/faker'
389
+
390
+ export const createUser = (overrides = {}) => ({
391
+ id: faker.string.uuid(),
392
+ address: `0x${faker.string.hexadecimal({ length: 40 })}`,
393
+ email: faker.internet.email(),
394
+ createdAt: faker.date.past(),
395
+ ...overrides,
396
+ })
397
+
398
+ export const createTransaction = (overrides = {}) => ({
399
+ hash: `0x${faker.string.hexadecimal({ length: 64 })}`,
400
+ from: `0x${faker.string.hexadecimal({ length: 40 })}`,
401
+ to: `0x${faker.string.hexadecimal({ length: 40 })}`,
402
+ value: BigInt(faker.number.int({ min: 1, max: 1000000 })) * 10n ** 18n,
403
+ status: 'confirmed',
404
+ ...overrides,
405
+ })
406
+ ```
407
+
408
+ ### AI Response Fixtures
409
+
410
+ ```typescript
411
+ // __fixtures__/ai-responses.ts
412
+ export const AI_RESPONSES = {
413
+ ANALYSIS_SUCCESS: {
414
+ summary: 'Market shows bullish trends',
415
+ sentiment: 'positive',
416
+ confidence: 0.87,
417
+ },
418
+ ANALYSIS_UNCERTAIN: {
419
+ summary: 'Insufficient data',
420
+ sentiment: 'neutral',
421
+ confidence: 0.45,
422
+ },
423
+ ERROR_RATE_LIMITED: {
424
+ ok: false,
425
+ error: { code: 'RATE_LIMITED', retryAfter: 60 },
426
+ },
427
+ }
428
+ ```
429
+
430
+ ---
431
+
432
+ ## 7. CI Configuration
433
+
434
+ ### GitHub Actions
435
+
436
+ ```yaml
437
+ # .github/workflows/test.yml
438
+ name: Test
439
+
440
+ on: [push, pull_request]
441
+
442
+ jobs:
443
+ test:
444
+ runs-on: ubuntu-latest
445
+ steps:
446
+ - uses: actions/checkout@v4
447
+ - uses: pnpm/action-setup@v2
448
+ - uses: actions/setup-node@v4
449
+ with:
450
+ node-version: 20
451
+ cache: 'pnpm'
452
+
453
+ - run: pnpm install
454
+ - run: pnpm test:unit
455
+ - run: pnpm test:integration
456
+
457
+ e2e:
458
+ runs-on: ubuntu-latest
459
+ steps:
460
+ - uses: actions/checkout@v4
461
+ - uses: pnpm/action-setup@v2
462
+ - run: pnpm install
463
+ - run: pnpm exec playwright install
464
+ - run: pnpm test:e2e
465
+ ```
466
+
467
+ ---
468
+
469
+ ## Quick Reference
470
+
471
+ ### 测试命令
472
+
473
+ ```bash
474
+ pnpm test # 运行所有测试
475
+ pnpm test:unit # 仅单元测试
476
+ pnpm test:integration # 仅集成测试
477
+ pnpm test:e2e # 仅 E2E 测试
478
+ pnpm test:coverage # 覆盖率报告
479
+ pnpm test:watch # 监听模式
480
+ ```
481
+
482
+ ### 覆盖率目标 (OPC)
483
+
484
+ | 层级 | 目标 | 理由 |
485
+ |------|------|------|
486
+ | 工具函数 | 90%+ | 纯函数,容易测试 |
487
+ | Hooks | 70%+ | 核心逻辑 |
488
+ | API Routes | 80%+ | 关键入口 |
489
+ | 组件 | 50%+ | 视觉测试成本高 |
490
+ | E2E | 核心流程 | 不追求覆盖率 |
491
+
492
+ ### 推荐库
493
+
494
+ ```
495
+ vitest - 单元/集成测试
496
+ @testing-library/react - React 组件测试
497
+ playwright - E2E 测试
498
+ msw - API mocking
499
+ @faker-js/faker - 测试数据生成
500
+ ```
@@ -0,0 +1,28 @@
1
+ {
2
+ "domain": "code.testing",
3
+ "description": "Design testing strategies for AI and Web3 applications. Create test cases, mock data, and CI configurations. Use when writing tests, setting up test infrastructure, or when the user mentions testing, test coverage, unit test, integration test, or e2e.",
4
+ "inputSchema": {
5
+ "type": "object",
6
+ "properties": {
7
+ "prompt": {
8
+ "type": "string"
9
+ }
10
+ },
11
+ "required": []
12
+ },
13
+ "outputSchema": {
14
+ "type": "object",
15
+ "properties": {
16
+ "result": {
17
+ "type": "string"
18
+ }
19
+ }
20
+ },
21
+ "dependencies": [],
22
+ "version": "0.1.0",
23
+ "author": "rotifer-team",
24
+ "createdAt": 1771939409560,
25
+ "fidelity": "Wrapped",
26
+ "transparency": "Open",
27
+ "source": "skill"
28
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "cloud_id": "587a8430-1434-418b-b79b-7e9176ec1c96",
3
+ "owner": "Rotifer Protocol",
4
+ "version": "0.1.0",
5
+ "published_at": "2026-03-17T14:13:49.845Z"
6
+ }
@@ -0,0 +1,34 @@
1
+ # text-summarizer
2
+
3
+ A Native Gene that extracts key sentences from text to produce concise summaries.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ rotifer test text-summarizer --input '{"text": "Long article content here...", "maxWords": 50}'
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Extractive summarization using sentence scoring
14
+ - Configurable word limit
15
+ - Two output formats: paragraph or bullet points
16
+ - Key phrase extraction
17
+ - Compression ratio reporting
18
+
19
+ ## Input
20
+
21
+ | Field | Type | Required | Description |
22
+ |-------|------|----------|-------------|
23
+ | `text` | string | Yes | The text to summarize |
24
+ | `maxWords` | number | No | Maximum words (default: 100) |
25
+ | `format` | string | No | `"paragraph"` or `"bullets"` |
26
+
27
+ ## Output
28
+
29
+ | Field | Type | Description |
30
+ |-------|------|-------------|
31
+ | `summary` | string | The generated summary |
32
+ | `wordCount` | number | Word count of summary |
33
+ | `compressionRatio` | number | Summary/original length ratio |
34
+ | `keyPhrases` | string[] | Extracted key phrases |
@@ -0,0 +1,122 @@
1
+ interface SummarizerInput {
2
+ text: string;
3
+ maxWords?: number;
4
+ format?: "paragraph" | "bullets";
5
+ }
6
+
7
+ interface SummarizerOutput {
8
+ summary: string;
9
+ wordCount: number;
10
+ compressionRatio: number;
11
+ keyPhrases: string[];
12
+ }
13
+
14
+ function splitSentences(text: string): string[] {
15
+ return text
16
+ .replace(/([.!?])\s+/g, "$1\n")
17
+ .split("\n")
18
+ .map((s) => s.trim())
19
+ .filter((s) => s.length > 0);
20
+ }
21
+
22
+ function wordCount(text: string): number {
23
+ return text.split(/\s+/).filter((w) => w.length > 0).length;
24
+ }
25
+
26
+ function scoreSentence(sentence: string, wordFreq: Map<string, number>, position: number, total: number): number {
27
+ const words = sentence.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
28
+ if (words.length === 0) return 0;
29
+
30
+ let freqScore = 0;
31
+ for (const w of words) freqScore += wordFreq.get(w) || 0;
32
+ freqScore /= words.length;
33
+
34
+ const posScore = position < total * 0.2 ? 1.5 : position > total * 0.8 ? 1.2 : 1.0;
35
+ const lenPenalty = words.length > 35 ? 0.7 : words.length < 5 ? 0.8 : 1.0;
36
+
37
+ return freqScore * posScore * lenPenalty;
38
+ }
39
+
40
+ function extractKeyPhrases(text: string, topN: number): string[] {
41
+ const stopWords = new Set([
42
+ "the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
43
+ "have", "has", "had", "do", "does", "did", "will", "would", "could",
44
+ "should", "may", "might", "shall", "can", "to", "of", "in", "for",
45
+ "on", "with", "at", "by", "from", "as", "into", "through", "during",
46
+ "before", "after", "above", "below", "between", "and", "but", "or",
47
+ "not", "no", "nor", "so", "yet", "both", "either", "neither", "each",
48
+ "every", "all", "any", "few", "more", "most", "other", "some", "such",
49
+ "than", "too", "very", "just", "about", "also", "only", "own", "same",
50
+ "that", "this", "these", "those", "it", "its", "they", "them", "their",
51
+ "we", "us", "our", "you", "your", "he", "him", "his", "she", "her",
52
+ ]);
53
+
54
+ const freq = new Map<string, number>();
55
+ const words = text.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3 && !stopWords.has(w));
56
+ for (const w of words) freq.set(w, (freq.get(w) || 0) + 1);
57
+
58
+ return [...freq.entries()]
59
+ .sort((a, b) => b[1] - a[1])
60
+ .slice(0, topN)
61
+ .map(([w]) => w);
62
+ }
63
+
64
+ export async function express(input: SummarizerInput): Promise<SummarizerOutput> {
65
+ const text = (input.text || "").trim();
66
+ const maxWords = input.maxWords ?? 100;
67
+ const format = input.format ?? "paragraph";
68
+
69
+ if (!text) {
70
+ return { summary: "", wordCount: 0, compressionRatio: 0, keyPhrases: [] };
71
+ }
72
+
73
+ const originalWc = wordCount(text);
74
+ const sentences = splitSentences(text);
75
+
76
+ if (sentences.length <= 2 || originalWc <= maxWords) {
77
+ return {
78
+ summary: text,
79
+ wordCount: originalWc,
80
+ compressionRatio: 1,
81
+ keyPhrases: extractKeyPhrases(text, 5),
82
+ };
83
+ }
84
+
85
+ const stopWords = new Set(["the", "a", "an", "is", "are", "was", "were", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by"]);
86
+ const freq = new Map<string, number>();
87
+ const allWords = text.toLowerCase().split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
88
+ for (const w of allWords) freq.set(w, (freq.get(w) || 0) + 1);
89
+
90
+ const scored = sentences.map((s, i) => ({
91
+ text: s,
92
+ score: scoreSentence(s, freq, i, sentences.length),
93
+ index: i,
94
+ }));
95
+
96
+ scored.sort((a, b) => b.score - a.score);
97
+
98
+ const selected: typeof scored = [];
99
+ let currentWords = 0;
100
+ for (const s of scored) {
101
+ const wc = wordCount(s.text);
102
+ if (currentWords + wc > maxWords && selected.length > 0) break;
103
+ selected.push(s);
104
+ currentWords += wc;
105
+ }
106
+
107
+ selected.sort((a, b) => a.index - b.index);
108
+
109
+ let summary: string;
110
+ if (format === "bullets") {
111
+ summary = selected.map((s) => `- ${s.text}`).join("\n");
112
+ } else {
113
+ summary = selected.map((s) => s.text).join(" ");
114
+ }
115
+
116
+ return {
117
+ summary,
118
+ wordCount: wordCount(summary),
119
+ compressionRatio: +(wordCount(summary) / originalWc).toFixed(2),
120
+ keyPhrases: extractKeyPhrases(text, 5),
121
+ };
122
+ }