universal-dev-standards 5.1.0-beta.6 → 5.1.0-beta.7
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/bin/uds.js +12 -0
- package/bundled/ai/standards/agent-communication-protocol.ai.yaml +34 -0
- package/bundled/ai/standards/anti-sycophancy-prompting.ai.yaml +111 -0
- package/bundled/ai/standards/capability-declaration.ai.yaml +113 -0
- package/bundled/ai/standards/circuit-breaker.ai.yaml +93 -0
- package/bundled/ai/standards/developer-memory.ai.yaml +13 -0
- package/bundled/ai/standards/dual-phase-output.ai.yaml +108 -0
- package/bundled/ai/standards/failure-source-taxonomy.ai.yaml +115 -0
- package/bundled/ai/standards/frontend-design-standards.ai.yaml +305 -0
- package/bundled/ai/standards/health-check-standards.ai.yaml +140 -0
- package/bundled/ai/standards/immutability-first.ai.yaml +112 -0
- package/bundled/ai/standards/model-selection.ai.yaml +111 -3
- package/bundled/ai/standards/packaging-standards.ai.yaml +142 -0
- package/bundled/ai/standards/recovery-recipe-registry.ai.yaml +200 -0
- package/bundled/ai/standards/retry-standards.ai.yaml +134 -0
- package/bundled/ai/standards/security-decision.ai.yaml +87 -0
- package/bundled/ai/standards/skill-standard-alignment-check.ai.yaml +119 -0
- package/bundled/ai/standards/standard-admission-criteria.ai.yaml +107 -0
- package/bundled/ai/standards/standard-lifecycle-management.ai.yaml +144 -0
- package/bundled/ai/standards/timeout-standards.ai.yaml +104 -0
- package/bundled/ai/standards/token-budget.ai.yaml +108 -0
- package/bundled/core/anti-sycophancy-prompting.md +184 -0
- package/bundled/core/capability-declaration.md +59 -0
- package/bundled/core/circuit-breaker.md +58 -0
- package/bundled/core/developer-memory.md +29 -1
- package/bundled/core/dual-phase-output.md +56 -0
- package/bundled/core/failure-source-taxonomy.md +72 -0
- package/bundled/core/frontend-design-standards.md +474 -0
- package/bundled/core/health-check-standards.md +72 -0
- package/bundled/core/immutability-first.md +105 -0
- package/bundled/core/model-selection.md +80 -0
- package/bundled/core/packaging-standards.md +216 -0
- package/bundled/core/recovery-recipe-registry.md +69 -0
- package/bundled/core/retry-standards.md +62 -0
- package/bundled/core/security-decision.md +65 -0
- package/bundled/core/skill-standard-alignment-check.md +79 -0
- package/bundled/core/standard-admission-criteria.md +84 -0
- package/bundled/core/standard-lifecycle-management.md +94 -0
- package/bundled/core/timeout-standards.md +63 -0
- package/bundled/core/token-budget.md +58 -0
- package/bundled/locales/zh-CN/CHANGELOG.md +22 -3
- package/bundled/locales/zh-CN/README.md +1 -1
- package/bundled/locales/zh-TW/CHANGELOG.md +22 -3
- package/bundled/locales/zh-TW/README.md +1 -1
- package/bundled/locales/zh-TW/core/anti-sycophancy-prompting.md +184 -0
- package/bundled/locales/zh-TW/core/packaging-standards.md +224 -0
- package/bundled/skills/e2e-assistant/SKILL.md +19 -5
- package/bundled/skills/testing-guide/SKILL.md +5 -0
- package/bundled/skills/testing-guide/test-skeleton-templates.md +316 -0
- package/package.json +1 -1
- package/src/commands/config.js +9 -0
- package/src/commands/init.js +91 -46
- package/src/commands/mcp.js +26 -0
- package/src/commands/run-intent.js +66 -0
- package/src/commands/update.js +35 -4
- package/src/core/command-router.js +85 -0
- package/src/core/project-config.js +91 -0
- package/src/flows/init-flow.js +6 -1
- package/src/i18n/messages.js +6 -6
- package/src/mcp/__tests__/server.test.js +251 -0
- package/src/mcp/server.js +352 -0
- package/src/prompts/init.js +157 -1
- package/src/reconciler/actual-state-scanner.js +24 -0
- package/src/uninstallers/hook-uninstaller.js +32 -1
- package/src/utils/e2e-analyzer.js +88 -5
- package/src/utils/e2e-detector.js +73 -1
- package/src/utils/integration-generator.js +22 -3
- package/standards-registry.json +193 -5
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
---
|
|
2
|
+
source: ../../../core/packaging-standards.md
|
|
3
|
+
source_version: 1.0.0
|
|
4
|
+
translation_version: 1.0.0
|
|
5
|
+
last_synced: 2026-04-15
|
|
6
|
+
status: current
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# 打包標準
|
|
10
|
+
|
|
11
|
+
> **語言**: [English](../../../core/packaging-standards.md) | 繁體中文
|
|
12
|
+
|
|
13
|
+
**版本**: 1.0.0
|
|
14
|
+
**最後更新**: 2026-04-15
|
|
15
|
+
**適用性**: 使用 UDS/DevAP 工具鏈的專案
|
|
16
|
+
**範圍**: 通用 (Universal)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 目的
|
|
21
|
+
|
|
22
|
+
本標準定義一套基於 Recipe 的打包框架,讓使用者專案可在 `.devap/packaging.yaml` 中宣告打包目標(target)。UDS 負責提供 Recipe 定義與內建 Recipe 函式庫;DevAP 則在 pipeline 中執行編排。
|
|
23
|
+
|
|
24
|
+
框架的關注點分離如下:
|
|
25
|
+
- **使用者專案**:宣告「打包什麼」(targets + 設定覆蓋)
|
|
26
|
+
- **UDS**:定義「如何打包」(Recipe 結構 + 內建 Recipes)
|
|
27
|
+
- **DevAP**:執行「何時打包」(在 Review 與 Deploy 之間的 pipeline 階段)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 核心原則
|
|
32
|
+
|
|
33
|
+
| 原則 | 說明 |
|
|
34
|
+
|------|------|
|
|
35
|
+
| **Recipe-based** | 每個打包目標都參照一個具名 Recipe;不在 pipeline YAML 中撰寫臨時腳本 |
|
|
36
|
+
| **宣告式 targets** | 專案在 `.devap/packaging.yaml` 中宣告 targets;DevAP 負責解析與執行 |
|
|
37
|
+
| **可客製化** | 四個客製化層允許設定覆蓋、Hook 注入、自訂 Recipe 及 Escape Hatch |
|
|
38
|
+
| **整合至 Pipeline** | 打包作為獨立階段運行於 VibeOps pipeline 的 Review 與 Deploy 之間 |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Recipe 結構
|
|
43
|
+
|
|
44
|
+
Recipe 是定義如何打包專案的 YAML 檔案,欄位定義如下:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
# Recipe: <name>.yaml
|
|
48
|
+
name: <string> # 必填 — 唯一識別符(kebab-case)
|
|
49
|
+
description: <string> # 選填 — 人類可讀描述
|
|
50
|
+
requires: # 選填 — 執行前必須存在的檔案
|
|
51
|
+
- <file-path>
|
|
52
|
+
steps: # 必填 — 有序的建置/打包步驟清單
|
|
53
|
+
- run: <shell-command>
|
|
54
|
+
description: <string> # 選填 — 步驟描述
|
|
55
|
+
config: # 選填 — 預設設定值(可被使用者專案覆蓋)
|
|
56
|
+
<key>: <value>
|
|
57
|
+
hooks: # 選填 — 生命週期 hooks(~ 表示不執行)
|
|
58
|
+
preBuild: ~
|
|
59
|
+
postBuild: ~
|
|
60
|
+
prePublish: ~
|
|
61
|
+
postPublish: ~
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 必填與選填欄位
|
|
65
|
+
|
|
66
|
+
| 欄位 | 必填 | 說明 |
|
|
67
|
+
|------|------|------|
|
|
68
|
+
| `name` | 是 | 唯一識別符,kebab-case 格式 |
|
|
69
|
+
| `steps` | 是 | 至少需要一個步驟 |
|
|
70
|
+
| `description` | 否 | 人類可讀描述 |
|
|
71
|
+
| `requires` | 否 | 前置條件檔案檢查 |
|
|
72
|
+
| `config` | 否 | 預設設定值;所有 key 均可被使用者專案覆蓋 |
|
|
73
|
+
| `hooks` | 否 | 生命週期 hook 插入點;`~` 表示不執行 |
|
|
74
|
+
|
|
75
|
+
### 步驟變數
|
|
76
|
+
|
|
77
|
+
設定值與執行時期情境可在 `run` 指令中使用 `{variable}` 占位符:
|
|
78
|
+
|
|
79
|
+
| 變數 | 來源 | 範例 |
|
|
80
|
+
|------|------|------|
|
|
81
|
+
| `{registry}` | `config.registry` | `ghcr.io` |
|
|
82
|
+
| `{name}` | `package.json#name` 或 `config.name` | `my-app` |
|
|
83
|
+
| `{version}` | `package.json#version` 或 `config.version` | `1.2.3` |
|
|
84
|
+
| `{platforms}` | `config.platforms` | `linux/amd64,linux/arm64` |
|
|
85
|
+
| `{output_dir}` | `config.output_dir` | `dist/installers` |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 內建 Recipes
|
|
90
|
+
|
|
91
|
+
UDS 隨附四個內建 Recipe,位於 `recipes/` 目錄:
|
|
92
|
+
|
|
93
|
+
| Recipe | 檔案 | 使用場景 |
|
|
94
|
+
|--------|------|----------|
|
|
95
|
+
| `npm-library` | `recipes/npm-library.yaml` | 不含執行入口的 npm 套件 |
|
|
96
|
+
| `npm-cli` | `recipes/npm-cli.yaml` | 含 `bin` 欄位的 npm 套件(CLI 工具) |
|
|
97
|
+
| `docker-service` | `recipes/docker-service.yaml` | Docker 容器映像建置與推送 |
|
|
98
|
+
| `windows-installer` | `recipes/windows-installer.yaml` | Windows 安裝程式(.msi / .exe)透過使用者腳本 |
|
|
99
|
+
|
|
100
|
+
### 選擇 Recipe 的決策流程
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
產出物是 npm 套件嗎?
|
|
104
|
+
├── 是 → package.json 是否含有 "bin" 欄位?
|
|
105
|
+
│ ├── 是 → npm-cli
|
|
106
|
+
│ └── 否 → npm-library
|
|
107
|
+
└── 否 → 產出物是容器映像嗎?
|
|
108
|
+
├── 是 → docker-service
|
|
109
|
+
└── 否 → 產出物是 Windows 安裝程式嗎?
|
|
110
|
+
├── 是 → windows-installer
|
|
111
|
+
└── 否 → 撰寫自訂 Recipe(參見客製化層)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 客製化層
|
|
117
|
+
|
|
118
|
+
需要偏離內建 Recipe 預設值的專案,應使用最低適用層:
|
|
119
|
+
|
|
120
|
+
| 層級 | 機制 | 使用時機 |
|
|
121
|
+
|------|------|----------|
|
|
122
|
+
| **L1 — 設定覆蓋** | `.devap/packaging.yaml` 中的 `config:` 區塊 | 更改預設值(registry URL、tag、輸出目錄)|
|
|
123
|
+
| **L2 — Hook 注入** | `.devap/packaging.yaml` 中的 `hooks:` 區塊 | 在建置或發佈前後執行額外指令 |
|
|
124
|
+
| **L3 — 自訂 Recipe** | 專案 `.devap/recipes/` 中的新 `.yaml` 檔案 | 完全不同的建置流程;內建 Recipe 不適用 |
|
|
125
|
+
| **L4 — Escape Hatch** | target 定義中以 `script:` 取代 `recipe:` | 原始 shell 腳本,無適合的 Recipe 抽象 |
|
|
126
|
+
|
|
127
|
+
### L1 範例 — 設定覆蓋
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
# .devap/packaging.yaml
|
|
131
|
+
targets:
|
|
132
|
+
- name: publish-npm
|
|
133
|
+
recipe: npm-library
|
|
134
|
+
config:
|
|
135
|
+
registry: https://npm.pkg.github.com
|
|
136
|
+
access: restricted
|
|
137
|
+
tag: beta
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### L2 範例 — Hook 注入
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
# .devap/packaging.yaml
|
|
144
|
+
targets:
|
|
145
|
+
- name: docker-push
|
|
146
|
+
recipe: docker-service
|
|
147
|
+
hooks:
|
|
148
|
+
postPush: |
|
|
149
|
+
curl -X POST https://hooks.example.com/deploy-notify \
|
|
150
|
+
-d "{\"version\": \"{version}\"}"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### L3 範例 — 自訂 Recipe
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
# .devap/recipes/electron-app.yaml
|
|
157
|
+
name: electron-app
|
|
158
|
+
description: 建置 Electron 桌面應用程式
|
|
159
|
+
requires:
|
|
160
|
+
- package.json
|
|
161
|
+
- electron-builder.yml
|
|
162
|
+
steps:
|
|
163
|
+
- run: npm run build
|
|
164
|
+
- run: npx electron-builder --publish never
|
|
165
|
+
config:
|
|
166
|
+
output_dir: dist
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### L4 範例 — Escape Hatch
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
# .devap/packaging.yaml
|
|
173
|
+
targets:
|
|
174
|
+
- name: legacy-bundle
|
|
175
|
+
script: |
|
|
176
|
+
./scripts/legacy-bundle.sh
|
|
177
|
+
mv output/ dist/bundle/
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 打包驗收標準
|
|
183
|
+
|
|
184
|
+
當以下**所有**條件均滿足時,打包執行視為**成功**:
|
|
185
|
+
|
|
186
|
+
| 標準 | 閾值 | 備註 |
|
|
187
|
+
|------|------|------|
|
|
188
|
+
| 所有 `requires` 檔案存在 | 100% | 在任何步驟執行前檢查 |
|
|
189
|
+
| 所有步驟以 exit code 0 結束 | 100% | 任何非零 exit 使執行失敗 |
|
|
190
|
+
| `postBuild` 產出物存在 | 存在於預期路徑 | 建置步驟後由 DevAP 驗證 |
|
|
191
|
+
| Hook 指令以 exit code 0 結束 | 100% | Hook 失敗會傳播為步驟失敗 |
|
|
192
|
+
| 已發佈產出物可被取回 | HTTP 200 / registry 查詢成功 | 由 DevAP 在發佈後進行 smoke check |
|
|
193
|
+
|
|
194
|
+
### 失敗處理
|
|
195
|
+
|
|
196
|
+
| 失敗類型 | 行動 | 可重試? |
|
|
197
|
+
|----------|------|----------|
|
|
198
|
+
| `requires` 檔案缺失 | 立即失敗,回報缺失路徑 | 否 |
|
|
199
|
+
| 步驟非零 exit | 立即失敗,若已定義則執行 `postBuild` hook | 可設定(預設:否)|
|
|
200
|
+
| Hook 非零 exit | 立即失敗 | 否 |
|
|
201
|
+
| 發佈無法連線 | 以指數退避重試最多 3 次 | 是(3×)|
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 相關標準
|
|
206
|
+
|
|
207
|
+
- [部署標準](deployment-standards.md) — 打包後的部署階段
|
|
208
|
+
- [Pipeline 整合標準](pipeline-integration-standards.md) — CI/CD pipeline 設定
|
|
209
|
+
- [Check-in 標準](checkin-standards.md) — 打包前的品質關卡
|
|
210
|
+
- [版本控制標準](versioning.md) — 套件產出物使用的版本號
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 版本歷史
|
|
215
|
+
|
|
216
|
+
| 版本 | 日期 | 變更 |
|
|
217
|
+
|------|------|------|
|
|
218
|
+
| 1.0.0 | 2026-04-15 | 初始發行 — XSPEC-034 Phase 1 |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 授權
|
|
223
|
+
|
|
224
|
+
本標準以 [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 授權發布。
|
|
@@ -53,11 +53,25 @@ Scan coverage gaps between BDD features and existing E2E tests.
|
|
|
53
53
|
|
|
54
54
|
## Supported Frameworks | 支援框架
|
|
55
55
|
|
|
56
|
-
| Framework | Auto-detected |
|
|
57
|
-
|
|
58
|
-
| Playwright | ✅ | `@playwright/test` |
|
|
59
|
-
| Cypress | ✅ | `
|
|
60
|
-
| Vitest | ✅ | `
|
|
56
|
+
| Ecosystem | Framework | Auto-detected | Detection signal |
|
|
57
|
+
|-----------|-----------|:------------:|----------|
|
|
58
|
+
| JavaScript | Playwright | ✅ | `@playwright/test` in package.json |
|
|
59
|
+
| JavaScript | Cypress | ✅ | `cypress` dep + config file |
|
|
60
|
+
| JavaScript | Vitest | ✅ | `vitest` dep + e2e directory |
|
|
61
|
+
| JavaScript | WebdriverIO | ❌ Manual | listed in SUPPORTED_FRAMEWORKS |
|
|
62
|
+
| Python | pytest-playwright | ✅ | `pytest-playwright` in requirements |
|
|
63
|
+
| Python | Selenium + pytest | ✅ | `selenium` in requirements |
|
|
64
|
+
| Python | Robot Framework | ✅ | `robotframework` in requirements |
|
|
65
|
+
| Python | Behave | ✅ | `behave` in requirements |
|
|
66
|
+
| Go | chromedp | ✅ | `chromedp` in go.mod |
|
|
67
|
+
| Go | rod | ✅ | `rod` in go.mod |
|
|
68
|
+
| Java | Selenium WebDriver | ✅ | `selenium-java` in pom.xml / build.gradle |
|
|
69
|
+
| Java | Playwright for Java | ✅ | `playwright` in pom.xml / build.gradle |
|
|
70
|
+
| Java | Gauge | ✅ | `gauge-java` in pom.xml / build.gradle |
|
|
71
|
+
| Ruby | Capybara | ✅ | `capybara` in Gemfile |
|
|
72
|
+
| Ruby | Watir | ✅ | `watir` in Gemfile |
|
|
73
|
+
| C# | Playwright for .NET | ❌ Manual | not yet auto-detected |
|
|
74
|
+
| C# | Selenium WebDriver | ❌ Manual | not yet auto-detected |
|
|
61
75
|
|
|
62
76
|
## Usage | 使用方式
|
|
63
77
|
|
|
@@ -96,6 +96,7 @@ For complete standards, see:
|
|
|
96
96
|
- [Testing Standards](../../core/testing-standards.md) - Actionable rules
|
|
97
97
|
- [Testing Theory](./testing-theory.md) - Educational knowledge base
|
|
98
98
|
- [Testing Pyramid](./testing-pyramid.md) - Detailed pyramid ratios
|
|
99
|
+
- [Test Skeleton Templates](./test-skeleton-templates.md) - Multi-language skeletons for UT/IT/ST/Perf/Contract
|
|
99
100
|
|
|
100
101
|
### AI-Optimized Format (Token-Efficient)
|
|
101
102
|
|
|
@@ -109,6 +110,10 @@ For AI assistants, use the YAML format files for reduced token usage:
|
|
|
109
110
|
- Integration Testing: `ai/options/testing/integration-testing.ai.yaml`
|
|
110
111
|
- System Testing: `ai/options/testing/system-testing.ai.yaml`
|
|
111
112
|
- E2E Testing: `ai/options/testing/e2e-testing.ai.yaml`
|
|
113
|
+
- Security Testing: `ai/options/testing/security-testing.ai.yaml`
|
|
114
|
+
- Performance Testing: `ai/options/testing/performance-testing.ai.yaml`
|
|
115
|
+
- Contract Testing: `ai/options/testing/contract-testing.ai.yaml`
|
|
116
|
+
- Skeleton templates (all levels, multi-language): [test-skeleton-templates.md](./test-skeleton-templates.md)
|
|
112
117
|
|
|
113
118
|
## Naming Conventions
|
|
114
119
|
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Test Skeleton Templates | 測試骨架模板
|
|
2
|
+
|
|
3
|
+
> **Language**: English | [繁體中文](../../locales/zh-TW/skills/testing-guide/test-skeleton-templates.md)
|
|
4
|
+
|
|
5
|
+
**Version**: 2.0.0
|
|
6
|
+
**Last Updated**: 2026-04-14
|
|
7
|
+
|
|
8
|
+
Language-agnostic test skeleton patterns for all testing pyramid levels.
|
|
9
|
+
AI should read `uds.project.yaml` at runtime to determine the target ecosystem,
|
|
10
|
+
then generate code using the appropriate framework for that language.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## How to Use This Document | 如何使用本文件
|
|
15
|
+
|
|
16
|
+
This document contains **patterns only** — pseudocode that applies to any language.
|
|
17
|
+
|
|
18
|
+
**AI workflow for generating tests:**
|
|
19
|
+
1. Read `uds.project.yaml` to identify `ecosystem` (e.g. `python`, `go`, `java`, `rust`)
|
|
20
|
+
2. Select the test framework standard for that ecosystem (e.g. pytest, testing, JUnit, #[test])
|
|
21
|
+
3. Apply the pseudocode pattern below, translated into the target language
|
|
22
|
+
4. Fill in `[TODO]` markers with real implementation details
|
|
23
|
+
|
|
24
|
+
> If `uds.project.yaml` is not present, ask the user for their language/framework before generating.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Unit Test (UT) | 單元測試
|
|
29
|
+
|
|
30
|
+
**Purpose**: Test a single function/method in isolation — no external dependencies.
|
|
31
|
+
**Speed**: < 100ms per test.
|
|
32
|
+
**Isolation**: Complete (use stubs/mocks for all dependencies).
|
|
33
|
+
|
|
34
|
+
### Pattern: Arrange-Act-Assert
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
# [TODO: Replace with target language syntax]
|
|
38
|
+
# Arrange
|
|
39
|
+
subject = new TargetClass()
|
|
40
|
+
input = <test_value>
|
|
41
|
+
|
|
42
|
+
# Act
|
|
43
|
+
result = subject.method(input)
|
|
44
|
+
|
|
45
|
+
# Assert
|
|
46
|
+
assert result == <expected_value>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Pattern: Error / Edge Case
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
# [TODO: Replace with target language syntax]
|
|
53
|
+
# Assert that invalid input raises / returns error
|
|
54
|
+
assert throws_error( subject.method(invalid_input) )
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Naming convention
|
|
58
|
+
```
|
|
59
|
+
methodName_scenario_expectedResult
|
|
60
|
+
# Examples:
|
|
61
|
+
# calculateTotal_withEmptyCart_returnsZero
|
|
62
|
+
# validateEmail_withInvalidFormat_returnsFalse
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### AI generation instruction
|
|
66
|
+
> Read `uds.project.yaml → ecosystem`, then:
|
|
67
|
+
> - Wrap the above pseudocode in the ecosystem's test runner structure
|
|
68
|
+
> - Use the ecosystem's assertion library
|
|
69
|
+
> - Use the ecosystem's mock/stub mechanism for dependencies
|
|
70
|
+
> - Apply the ecosystem's file naming convention (e.g. `_test.go`, `*_spec.rb`, `Test*.java`)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Integration Test (IT) | 整合測試
|
|
75
|
+
|
|
76
|
+
**Purpose**: Test the boundary between two components (DB, HTTP, queue).
|
|
77
|
+
**Speed**: < 1 second per test.
|
|
78
|
+
**Isolation**: Partial — use real implementations when possible, stubs for external services.
|
|
79
|
+
|
|
80
|
+
### Pattern: Database Integration
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
# [TODO: Replace with target language syntax]
|
|
84
|
+
# Arrange — start test database (in-memory or container)
|
|
85
|
+
db = start_test_database()
|
|
86
|
+
repo = new Repository(db)
|
|
87
|
+
|
|
88
|
+
# Act
|
|
89
|
+
repo.save(entity)
|
|
90
|
+
found = repo.findById(entity.id)
|
|
91
|
+
|
|
92
|
+
# Assert
|
|
93
|
+
assert found == entity
|
|
94
|
+
|
|
95
|
+
# Teardown — rollback or truncate
|
|
96
|
+
db.cleanup()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Pattern: HTTP API Integration
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
# [TODO: Replace with target language syntax]
|
|
103
|
+
# Act
|
|
104
|
+
response = http_client.post("/api/resource", body = { key: value })
|
|
105
|
+
|
|
106
|
+
# Assert
|
|
107
|
+
assert response.status == 201
|
|
108
|
+
assert response.body contains expected_fields
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Pattern: Message Queue Integration
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
# [TODO: Replace with target language syntax]
|
|
115
|
+
# Act
|
|
116
|
+
publisher.send("topic.created", event)
|
|
117
|
+
received = consumer.receive(timeout = 5s)
|
|
118
|
+
|
|
119
|
+
# Assert
|
|
120
|
+
assert received.payload == event
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### AI generation instruction
|
|
124
|
+
> Read `uds.project.yaml → ecosystem`, then select the appropriate:
|
|
125
|
+
> - In-memory or Testcontainers-compatible DB driver for that ecosystem
|
|
126
|
+
> - HTTP test client for that ecosystem
|
|
127
|
+
> - Embedded queue or mock transport for that ecosystem
|
|
128
|
+
> Never hardcode a specific library — choose based on detected ecosystem.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## System Test (ST) | 系統測試
|
|
133
|
+
|
|
134
|
+
**Purpose**: Test a complete business flow within one subsystem, with external services stubbed.
|
|
135
|
+
**Isolation**: Stub all external HTTP/queue calls; use real DB.
|
|
136
|
+
|
|
137
|
+
### Pattern: Full Flow with Stubbed External Service
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
# [TODO: Replace with target language syntax]
|
|
141
|
+
# Arrange — stub external dependency
|
|
142
|
+
stub_server = start_http_stub("/external-api/charge",
|
|
143
|
+
response = { status: "approved", id: "TX-001" })
|
|
144
|
+
|
|
145
|
+
# Boot subsystem under test
|
|
146
|
+
app = start_system(config = { external_url: stub_server.url })
|
|
147
|
+
|
|
148
|
+
# Act
|
|
149
|
+
result = app.run_flow(input_data)
|
|
150
|
+
|
|
151
|
+
# Assert
|
|
152
|
+
assert result.status == "confirmed"
|
|
153
|
+
assert result.id is not null
|
|
154
|
+
|
|
155
|
+
# Teardown
|
|
156
|
+
stub_server.stop()
|
|
157
|
+
app.stop()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### AI generation instruction
|
|
161
|
+
> Read `uds.project.yaml → ecosystem`, then select the appropriate:
|
|
162
|
+
> - HTTP stub/mock server for that ecosystem (e.g. WireMock, MSW, responses, httpretty, httpmock)
|
|
163
|
+
> - Application test harness / test client for that framework
|
|
164
|
+
> Never hardcode a specific stub library — choose based on detected ecosystem.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Performance Test | 效能測試
|
|
169
|
+
|
|
170
|
+
**Purpose**: Validate throughput, latency, and resource usage under load.
|
|
171
|
+
**Focus**: SLOs — p95 latency target, requests-per-second target, error rate threshold.
|
|
172
|
+
|
|
173
|
+
### Pattern: Load Test
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
# [TODO: Replace with target load testing tool syntax]
|
|
177
|
+
# Configuration
|
|
178
|
+
rate = <RPS target> # [TODO] e.g. 50 requests/sec
|
|
179
|
+
duration = <test duration> # [TODO] e.g. 60 seconds
|
|
180
|
+
target = "<API_URL>/api/resource"
|
|
181
|
+
|
|
182
|
+
# Thresholds
|
|
183
|
+
p95_latency_threshold = <ms> # [TODO] e.g. 500ms
|
|
184
|
+
error_rate_threshold = <rate> # [TODO] e.g. 1%
|
|
185
|
+
|
|
186
|
+
# Execute
|
|
187
|
+
results = run_load_test(rate, duration, target)
|
|
188
|
+
|
|
189
|
+
# Assert
|
|
190
|
+
assert results.p95_latency < p95_latency_threshold
|
|
191
|
+
assert results.error_rate < error_rate_threshold
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### AI generation instruction
|
|
195
|
+
> Read `uds.project.yaml → ecosystem`, then select the appropriate load testing tool:
|
|
196
|
+
> - Performance tests are **language-independent by default** (k6, Gatling, Locust, vegeta, wrk)
|
|
197
|
+
> - Prefer the tool already present in the project (check for existing config files)
|
|
198
|
+
> - If none present, suggest the most common tool for the detected ecosystem
|
|
199
|
+
> Set `[TODO]` thresholds based on the project's SLO definition if available.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Contract Test | 合約測試
|
|
204
|
+
|
|
205
|
+
**Purpose**: Verify API contracts between consumer and provider without deploying both.
|
|
206
|
+
**When**: Microservices, shared APIs, CI on every PR.
|
|
207
|
+
|
|
208
|
+
### Pattern: Consumer Contract (Consumer-Driven)
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
# [TODO: Replace with target language / Pact version syntax]
|
|
212
|
+
# Define the contract
|
|
213
|
+
pact = new ConsumerPactBuilder(
|
|
214
|
+
consumer = "[ConsumerName]", # [TODO]
|
|
215
|
+
provider = "[ProviderName]" # [TODO]
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
pact.given("resource 123 exists")
|
|
219
|
+
.upon_receiving("a GET request for resource 123")
|
|
220
|
+
.with_request(method = GET, path = "/resources/123")
|
|
221
|
+
.will_respond_with(
|
|
222
|
+
status = 200,
|
|
223
|
+
body = { id: like("123"), name: like("Alice") } # [TODO] add fields
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Run consumer test against mock provider
|
|
227
|
+
pact.run_test(lambda mock_url:
|
|
228
|
+
client = new ApiClient(mock_url)
|
|
229
|
+
resource = client.get_resource("123")
|
|
230
|
+
assert resource.id == "123"
|
|
231
|
+
)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Pattern: Provider Verification
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
# [TODO: Replace with target language / Pact version syntax]
|
|
238
|
+
# Verify all consumer pacts against the real provider
|
|
239
|
+
verifier = new ProviderVerifier(
|
|
240
|
+
provider_url = "http://localhost:[PORT]", # [TODO]
|
|
241
|
+
pact_broker = env("PACT_BROKER_URL"),
|
|
242
|
+
provider_name = "[ProviderName]", # [TODO]
|
|
243
|
+
state_handlers = {
|
|
244
|
+
"resource 123 exists": lambda: seed_database(id = "123") # [TODO]
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
verifier.verify()
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Pattern: OpenAPI / Schema Contract Test
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
# [TODO: Replace with target language syntax]
|
|
254
|
+
# Load OpenAPI spec and validate responses
|
|
255
|
+
spec = load_openapi_spec("./api/openapi.yaml") # [TODO] spec path
|
|
256
|
+
server = start_test_server()
|
|
257
|
+
|
|
258
|
+
for each critical_endpoint in spec.paths:
|
|
259
|
+
response = http_client.request(critical_endpoint)
|
|
260
|
+
assert spec.validates(critical_endpoint, response)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### AI generation instruction
|
|
264
|
+
> Read `uds.project.yaml → ecosystem`, then select:
|
|
265
|
+
> - Consumer-Driven: Pact has SDKs for most languages — use the SDK for the detected ecosystem
|
|
266
|
+
> - OpenAPI: Use the OpenAPI validation library available for the detected ecosystem
|
|
267
|
+
> Never hardcode a specific Pact version or library — choose based on detected ecosystem.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Choosing the Right Test Level | 選擇正確的測試層
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
Use UT when:
|
|
275
|
+
✓ Testing a single function or method
|
|
276
|
+
✓ All dependencies can be replaced by stubs
|
|
277
|
+
✓ Should run in < 100ms
|
|
278
|
+
|
|
279
|
+
Use IT when:
|
|
280
|
+
✓ Testing interaction with a real DB, HTTP endpoint, or message queue
|
|
281
|
+
✓ Crossing a component boundary
|
|
282
|
+
✓ Needs a real or containerised dependency
|
|
283
|
+
|
|
284
|
+
Use ST when:
|
|
285
|
+
✓ Testing a complete business flow within one subsystem
|
|
286
|
+
✓ External services should be stubbed (not mocked)
|
|
287
|
+
✓ Validates non-functional requirements (latency, throughput)
|
|
288
|
+
|
|
289
|
+
Use E2E when:
|
|
290
|
+
✓ Testing a full user journey across multiple systems
|
|
291
|
+
✓ Uses a production-like environment
|
|
292
|
+
✓ Critical paths only (top 5–10 journeys)
|
|
293
|
+
|
|
294
|
+
Use Performance when:
|
|
295
|
+
✓ Validating SLOs before production
|
|
296
|
+
✓ Checking throughput or latency under load
|
|
297
|
+
|
|
298
|
+
Use Contract when:
|
|
299
|
+
✓ Two services share an API contract
|
|
300
|
+
✓ You want CI to catch consumer/provider drift early
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Related Documents | 相關文件
|
|
306
|
+
|
|
307
|
+
- [Testing Pyramid](./testing-pyramid.md)
|
|
308
|
+
- [E2E Assistant](../e2e-assistant/SKILL.md)
|
|
309
|
+
- [Contract Test Assistant](../contract-test-assistant/SKILL.md)
|
|
310
|
+
- Core: [testing-standards.md](../../core/testing-standards.md)
|
|
311
|
+
- Options:
|
|
312
|
+
- [unit-testing.ai.yaml](../../.standards/options/testing/unit-testing.ai.yaml)
|
|
313
|
+
- [integration-testing.ai.yaml](../../.standards/options/testing/integration-testing.ai.yaml)
|
|
314
|
+
- [system-testing.ai.yaml](../../.standards/options/testing/system-testing.ai.yaml)
|
|
315
|
+
- [performance-testing.ai.yaml](../../.standards/options/testing/performance-testing.ai.yaml)
|
|
316
|
+
- [contract-testing.ai.yaml](../../.standards/options/testing/contract-testing.ai.yaml)
|
package/package.json
CHANGED
package/src/commands/config.js
CHANGED
|
@@ -562,6 +562,8 @@ export async function runProjectConfiguration(options) {
|
|
|
562
562
|
}
|
|
563
563
|
|
|
564
564
|
baseChoices.push(
|
|
565
|
+
new DynSeparator(),
|
|
566
|
+
{ name: t('config.projectContractOption', 'Project Command Contract (uds.project.yaml)'), value: 'project_contract' },
|
|
565
567
|
new DynSeparator(),
|
|
566
568
|
{ name: t('config.vibeMode', 'Vibe Coding Mode'), value: 'vibe_coding' },
|
|
567
569
|
new DynSeparator(),
|
|
@@ -588,6 +590,13 @@ export async function runProjectConfiguration(options) {
|
|
|
588
590
|
process.exit(0);
|
|
589
591
|
}
|
|
590
592
|
|
|
593
|
+
// Handle project_contract — guided uds.project.yaml creation (XSPEC-029 Phase 3)
|
|
594
|
+
if (configType === 'project_contract') {
|
|
595
|
+
const { promptProjectCommandContract } = await import('../prompts/init.js');
|
|
596
|
+
await promptProjectCommandContract(projectPath);
|
|
597
|
+
process.exit(0);
|
|
598
|
+
}
|
|
599
|
+
|
|
591
600
|
// Handle show (flat menu item)
|
|
592
601
|
if (configType === 'show') {
|
|
593
602
|
const currentConfig = config.init();
|