@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.
- package/CHANGELOG.md +159 -15
- package/LICENSE +1 -1
- package/README.md +51 -23
- package/README.zh.md +54 -25
- package/dist/cloud/auth.d.ts +7 -1
- package/dist/cloud/auth.d.ts.map +1 -1
- package/dist/cloud/auth.js +65 -1
- package/dist/cloud/auth.js.map +1 -1
- package/dist/cloud/client.d.ts +4 -1
- package/dist/cloud/client.d.ts.map +1 -1
- package/dist/cloud/client.js +9 -2
- package/dist/cloud/client.js.map +1 -1
- package/dist/cloud/types.d.ts +3 -1
- package/dist/cloud/types.d.ts.map +1 -1
- package/dist/cloud/types.js.map +1 -1
- package/dist/commands/agent-create.d.ts.map +1 -1
- package/dist/commands/agent-create.js +66 -3
- package/dist/commands/agent-create.js.map +1 -1
- package/dist/commands/agent-run.d.ts.map +1 -1
- package/dist/commands/agent-run.js +296 -32
- package/dist/commands/agent-run.js.map +1 -1
- package/dist/commands/arena-submit.d.ts.map +1 -1
- package/dist/commands/arena-submit.js +45 -17
- package/dist/commands/arena-submit.js.map +1 -1
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +9 -3
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +13 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +23 -16
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/network.js +4 -4
- package/dist/commands/network.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +162 -44
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +209 -23
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/wrap.d.ts.map +1 -1
- package/dist/commands/wrap.js +17 -1
- package/dist/commands/wrap.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/runtime/network-gateway.d.ts +53 -0
- package/dist/runtime/network-gateway.d.ts.map +1 -0
- package/dist/runtime/network-gateway.js +147 -0
- package/dist/runtime/network-gateway.js.map +1 -0
- package/dist/utils/binding.d.ts +25 -0
- package/dist/utils/binding.d.ts.map +1 -1
- package/dist/utils/binding.js.map +1 -1
- package/dist/utils/open-browser.d.ts +10 -0
- package/dist/utils/open-browser.d.ts.map +1 -0
- package/dist/utils/open-browser.js +23 -0
- package/dist/utils/open-browser.js.map +1 -0
- package/genes/academic-writer/.cloud-manifest.json +6 -0
- package/genes/academic-writer/.gene-manifest.json +8 -0
- package/genes/academic-writer/SKILL.md +274 -0
- package/genes/academic-writer/phenotype.json +28 -0
- package/genes/ai-components/.cloud-manifest.json +6 -0
- package/genes/ai-components/.gene-manifest.json +8 -0
- package/genes/ai-components/SKILL.md +381 -0
- package/genes/ai-components/phenotype.json +28 -0
- package/genes/algorithmic-art/.cloud-manifest.json +6 -0
- package/genes/algorithmic-art/.gene-manifest.json +8 -0
- package/genes/algorithmic-art/SKILL.md +405 -0
- package/genes/algorithmic-art/phenotype.json +28 -0
- package/genes/answer-synthesizer/.cloud-manifest.json +6 -0
- package/genes/answer-synthesizer/index.ts +194 -0
- package/genes/answer-synthesizer/phenotype.json +61 -0
- package/genes/api-designer/.cloud-manifest.json +6 -0
- package/genes/api-designer/.gene-manifest.json +8 -0
- package/genes/api-designer/SKILL.md +456 -0
- package/genes/api-designer/phenotype.json +28 -0
- package/genes/auto-coder/.cloud-manifest.json +6 -0
- package/genes/auto-coder/.gene-manifest.json +8 -0
- package/genes/auto-coder/SKILL.md +400 -0
- package/genes/auto-coder/phenotype.json +28 -0
- package/genes/auto-writer/.cloud-manifest.json +6 -0
- package/genes/auto-writer/.gene-manifest.json +8 -0
- package/genes/auto-writer/SKILL.md +361 -0
- package/genes/auto-writer/phenotype.json +28 -0
- package/genes/brand-personality/.cloud-manifest.json +6 -0
- package/genes/brand-personality/.gene-manifest.json +8 -0
- package/genes/brand-personality/SKILL.md +549 -0
- package/genes/brand-personality/phenotype.json +28 -0
- package/genes/business-writer/.cloud-manifest.json +6 -0
- package/genes/business-writer/.gene-manifest.json +8 -0
- package/genes/business-writer/SKILL.md +448 -0
- package/genes/business-writer/phenotype.json +28 -0
- package/genes/citation-manager/.cloud-manifest.json +6 -0
- package/genes/citation-manager/.gene-manifest.json +8 -0
- package/genes/citation-manager/SKILL.md +279 -0
- package/genes/citation-manager/index.ts +162 -0
- package/genes/citation-manager/package.json +1 -0
- package/genes/citation-manager/phenotype.json +50 -0
- package/genes/code-complexity/.cloud-manifest.json +6 -0
- package/genes/code-complexity/README.md +35 -0
- package/genes/code-complexity/index.ts +101 -0
- package/genes/code-complexity/phenotype.json +34 -0
- package/genes/copywriter/.cloud-manifest.json +6 -0
- package/genes/copywriter/.gene-manifest.json +8 -0
- package/genes/copywriter/SKILL.md +329 -0
- package/genes/copywriter/phenotype.json +28 -0
- package/genes/creative-writer/.cloud-manifest.json +6 -0
- package/genes/creative-writer/.gene-manifest.json +8 -0
- package/genes/creative-writer/SKILL.md +356 -0
- package/genes/creative-writer/phenotype.json +28 -0
- package/genes/data-modeler/.cloud-manifest.json +6 -0
- package/genes/data-modeler/.gene-manifest.json +8 -0
- package/genes/data-modeler/SKILL.md +486 -0
- package/genes/data-modeler/phenotype.json +28 -0
- package/genes/debugger/.cloud-manifest.json +6 -0
- package/genes/debugger/.gene-manifest.json +8 -0
- package/genes/debugger/SKILL.md +416 -0
- package/genes/debugger/phenotype.json +28 -0
- package/genes/design-tokens/.cloud-manifest.json +6 -0
- package/genes/design-tokens/.gene-manifest.json +8 -0
- package/genes/design-tokens/SKILL.md +222 -0
- package/genes/design-tokens/index.ts +128 -0
- package/genes/design-tokens/package.json +1 -0
- package/genes/design-tokens/phenotype.json +1 -0
- package/genes/devops-automator/.cloud-manifest.json +6 -0
- package/genes/devops-automator/.gene-manifest.json +8 -0
- package/genes/devops-automator/SKILL.md +490 -0
- package/genes/devops-automator/phenotype.json +28 -0
- package/genes/doc-coauthoring/.cloud-manifest.json +6 -0
- package/genes/doc-coauthoring/.gene-manifest.json +8 -0
- package/genes/doc-coauthoring/SKILL.md +375 -0
- package/genes/doc-coauthoring/phenotype.json +28 -0
- package/genes/doc-retrieval/.cloud-manifest.json +6 -0
- package/genes/doc-retrieval/index.ts +134 -0
- package/genes/doc-retrieval/phenotype.json +54 -0
- package/genes/docs-writer/.cloud-manifest.json +6 -0
- package/genes/docs-writer/.gene-manifest.json +8 -0
- package/genes/docs-writer/SKILL.md +492 -0
- package/genes/docs-writer/phenotype.json +28 -0
- package/genes/evolve-life/.cloud-manifest.json +6 -0
- package/genes/evolve-life/.compile-result.json +12 -0
- package/genes/evolve-life/README.md +52 -0
- package/genes/evolve-life/gene.ir.wasm +0 -0
- package/genes/evolve-life/gene.wasm +0 -0
- package/genes/evolve-life/index.ts +255 -0
- package/genes/evolve-life/phenotype.json +129 -0
- package/genes/evolve-life-bitwise/.cloud-manifest.json +6 -0
- package/genes/evolve-life-bitwise/.compile-result.json +12 -0
- package/genes/evolve-life-bitwise/gene.ir.wasm +0 -0
- package/genes/evolve-life-bitwise/gene.wasm +0 -0
- package/genes/evolve-life-bitwise/index.ts +273 -0
- package/genes/evolve-life-bitwise/phenotype.json +129 -0
- package/genes/evolve-life-sparse/.cloud-manifest.json +6 -0
- package/genes/evolve-life-sparse/.compile-result.json +12 -0
- package/genes/evolve-life-sparse/gene.ir.wasm +0 -0
- package/genes/evolve-life-sparse/gene.wasm +0 -0
- package/genes/evolve-life-sparse/index.ts +236 -0
- package/genes/evolve-life-sparse/phenotype.json +129 -0
- package/genes/fact-checker/.cloud-manifest.json +6 -0
- package/genes/fact-checker/.gene-manifest.json +8 -0
- package/genes/fact-checker/SKILL.md +373 -0
- package/genes/fact-checker/phenotype.json +28 -0
- package/genes/genesis-code-format/.cloud-manifest.json +6 -0
- package/genes/genesis-code-format/package.json +1 -0
- package/genes/genesis-code-format/phenotype.json +1 -0
- package/genes/genesis-file-read/.cloud-manifest.json +6 -0
- package/genes/genesis-file-read/index.ts +11 -1
- package/genes/genesis-file-read/package.json +1 -0
- package/genes/genesis-file-read/phenotype.json +1 -0
- package/genes/genesis-l0-constraint/.cloud-manifest.json +6 -0
- package/genes/genesis-l0-constraint/package.json +1 -0
- package/genes/genesis-l0-constraint/phenotype.json +1 -0
- package/genes/genesis-web-search/.cloud-manifest.json +2 -2
- package/genes/genesis-web-search/package.json +1 -0
- package/genes/genesis-web-search/phenotype.json +1 -0
- package/genes/genesis-web-search-lite/.cloud-manifest.json +6 -0
- package/genes/genesis-web-search-lite/package.json +1 -0
- package/genes/genesis-web-search-lite/phenotype.json +1 -0
- package/genes/git-workflow/.cloud-manifest.json +6 -0
- package/genes/git-workflow/.gene-manifest.json +8 -0
- package/genes/git-workflow/SKILL.md +407 -0
- package/genes/git-workflow/phenotype.json +28 -0
- package/genes/grammar-checker/.cloud-manifest.json +6 -0
- package/genes/grammar-checker/.gene-manifest.json +8 -0
- package/genes/grammar-checker/SKILL.md +194 -0
- package/genes/grammar-checker/index.ts +168 -0
- package/genes/grammar-checker/package.json +1 -0
- package/genes/grammar-checker/phenotype.json +52 -0
- package/genes/json-validator/.cloud-manifest.json +6 -0
- package/genes/json-validator/README.md +42 -0
- package/genes/json-validator/index.ts +112 -0
- package/genes/json-validator/phenotype.json +42 -0
- package/genes/license-advisor/.cloud-manifest.json +6 -0
- package/genes/license-advisor/.gene-manifest.json +8 -0
- package/genes/license-advisor/SKILL.md +117 -0
- package/genes/license-advisor/phenotype.json +28 -0
- package/genes/logic-architect/.cloud-manifest.json +6 -0
- package/genes/logic-architect/.gene-manifest.json +8 -0
- package/genes/logic-architect/SKILL.md +451 -0
- package/genes/logic-architect/phenotype.json +28 -0
- package/genes/markdown-formatter/.cloud-manifest.json +6 -0
- package/genes/markdown-formatter/README.md +34 -0
- package/genes/markdown-formatter/index.ts +86 -0
- package/genes/markdown-formatter/phenotype.json +32 -0
- package/genes/orch/.cloud-manifest.json +6 -0
- package/genes/orch/.gene-manifest.json +8 -0
- package/genes/orch/SKILL.md +504 -0
- package/genes/orch/phenotype.json +28 -0
- package/genes/particle-barneshut/.cloud-manifest.json +6 -0
- package/genes/particle-barneshut/.compile-result.json +12 -0
- package/genes/particle-barneshut/README.md +55 -0
- package/genes/particle-barneshut/gene.ir.wasm +0 -0
- package/genes/particle-barneshut/gene.wasm +0 -0
- package/genes/particle-barneshut/index.ts +486 -0
- package/genes/particle-barneshut/phenotype.json +137 -0
- package/genes/particle-brute/.cloud-manifest.json +6 -0
- package/genes/particle-brute/.compile-result.json +12 -0
- package/genes/particle-brute/README.md +55 -0
- package/genes/particle-brute/gene.ir.wasm +0 -0
- package/genes/particle-brute/gene.wasm +0 -0
- package/genes/particle-brute/index.ts +277 -0
- package/genes/particle-brute/phenotype.json +137 -0
- package/genes/particle-spatial/.cloud-manifest.json +6 -0
- package/genes/particle-spatial/.compile-result.json +12 -0
- package/genes/particle-spatial/README.md +53 -0
- package/genes/particle-spatial/gene.ir.wasm +0 -0
- package/genes/particle-spatial/gene.wasm +0 -0
- package/genes/particle-spatial/index.ts +352 -0
- package/genes/particle-spatial/phenotype.json +137 -0
- package/genes/performance-optimizer/.cloud-manifest.json +6 -0
- package/genes/performance-optimizer/.gene-manifest.json +8 -0
- package/genes/performance-optimizer/SKILL.md +480 -0
- package/genes/performance-optimizer/phenotype.json +28 -0
- package/genes/plagiarism-checker/.cloud-manifest.json +6 -0
- package/genes/plagiarism-checker/.gene-manifest.json +8 -0
- package/genes/plagiarism-checker/SKILL.md +342 -0
- package/genes/plagiarism-checker/phenotype.json +28 -0
- package/genes/product-manager/.cloud-manifest.json +6 -0
- package/genes/product-manager/.gene-manifest.json +8 -0
- package/genes/product-manager/SKILL.md +249 -0
- package/genes/product-manager/phenotype.json +28 -0
- package/genes/project-reviewer/.cloud-manifest.json +6 -0
- package/genes/project-reviewer/.gene-manifest.json +8 -0
- package/genes/project-reviewer/SKILL.md +312 -0
- package/genes/project-reviewer/phenotype.json +28 -0
- package/genes/prompt-engineer/.cloud-manifest.json +6 -0
- package/genes/prompt-engineer/.gene-manifest.json +8 -0
- package/genes/prompt-engineer/SKILL.md +411 -0
- package/genes/prompt-engineer/phenotype.json +28 -0
- package/genes/readability-analyzer/.cloud-manifest.json +6 -0
- package/genes/readability-analyzer/.gene-manifest.json +8 -0
- package/genes/readability-analyzer/SKILL.md +357 -0
- package/genes/readability-analyzer/index.ts +123 -0
- package/genes/readability-analyzer/package.json +1 -0
- package/genes/readability-analyzer/phenotype.json +35 -0
- package/genes/rotifer-protocol/SKILL.md +121 -0
- package/genes/security-auditor/.cloud-manifest.json +6 -0
- package/genes/security-auditor/.gene-manifest.json +8 -0
- package/genes/security-auditor/SKILL.md +494 -0
- package/genes/security-auditor/phenotype.json +28 -0
- package/genes/seo-optimizer/.cloud-manifest.json +6 -0
- package/genes/seo-optimizer/.gene-manifest.json +8 -0
- package/genes/seo-optimizer/SKILL.md +327 -0
- package/genes/seo-optimizer/index.ts +206 -0
- package/genes/seo-optimizer/package.json +1 -0
- package/genes/seo-optimizer/phenotype.json +1 -0
- package/genes/source-linker/.cloud-manifest.json +6 -0
- package/genes/source-linker/index.ts +88 -0
- package/genes/source-linker/phenotype.json +45 -0
- package/genes/style-optimizer/.cloud-manifest.json +6 -0
- package/genes/style-optimizer/.gene-manifest.json +8 -0
- package/genes/style-optimizer/SKILL.md +285 -0
- package/genes/style-optimizer/phenotype.json +28 -0
- package/genes/tech-lead/.cloud-manifest.json +6 -0
- package/genes/tech-lead/.gene-manifest.json +8 -0
- package/genes/tech-lead/SKILL.md +451 -0
- package/genes/tech-lead/phenotype.json +28 -0
- package/genes/test-wrap/.cloud-manifest.json +6 -0
- package/genes/test-wrap/.gene-manifest.json +8 -0
- package/genes/test-wrap/phenotype.json +28 -0
- package/genes/testing-strategist/.cloud-manifest.json +6 -0
- package/genes/testing-strategist/.gene-manifest.json +8 -0
- package/genes/testing-strategist/SKILL.md +500 -0
- package/genes/testing-strategist/phenotype.json +28 -0
- package/genes/text-summarizer/.cloud-manifest.json +6 -0
- package/genes/text-summarizer/README.md +34 -0
- package/genes/text-summarizer/index.ts +122 -0
- package/genes/text-summarizer/phenotype.json +32 -0
- package/genes/tone-analyzer/.cloud-manifest.json +6 -0
- package/genes/tone-analyzer/.gene-manifest.json +8 -0
- package/genes/tone-analyzer/SKILL.md +410 -0
- package/genes/tone-analyzer/phenotype.json +28 -0
- package/genes/translator/.cloud-manifest.json +6 -0
- package/genes/translator/.gene-manifest.json +8 -0
- package/genes/translator/SKILL.md +355 -0
- package/genes/translator/phenotype.json +28 -0
- package/genes/ui-components/.cloud-manifest.json +6 -0
- package/genes/ui-components/.gene-manifest.json +8 -0
- package/genes/ui-components/SKILL.md +467 -0
- package/genes/ui-components/phenotype.json +28 -0
- package/genes/uiux-designer/.cloud-manifest.json +6 -0
- package/genes/uiux-designer/.gene-manifest.json +8 -0
- package/genes/uiux-designer/SKILL.md +353 -0
- package/genes/uiux-designer/phenotype.json +28 -0
- package/genes/url-extractor/.cloud-manifest.json +6 -0
- package/genes/url-extractor/README.md +37 -0
- package/genes/url-extractor/index.ts +86 -0
- package/genes/url-extractor/phenotype.json +48 -0
- package/genes/ux-patterns/.cloud-manifest.json +6 -0
- package/genes/ux-patterns/.gene-manifest.json +8 -0
- package/genes/ux-patterns/SKILL.md +872 -0
- package/genes/ux-patterns/phenotype.json +28 -0
- package/genes/web3-components/.cloud-manifest.json +6 -0
- package/genes/web3-components/.gene-manifest.json +8 -0
- package/genes/web3-components/SKILL.md +390 -0
- package/genes/web3-components/phenotype.json +28 -0
- 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,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
|
+
}
|