jsharness 1.12.4 → 1.12.5

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.
@@ -40,10 +40,10 @@ responsibilities:
40
40
 
41
41
  review_dimensions:
42
42
  - name: "A. 代码质量"
43
- weight: 30
43
+ weight: 25
44
44
  items: [命名, 复杂度, 类型, 错误处理, DRY]
45
45
  - name: "B. 规范遵循"
46
- weight: 15
46
+ weight: 10
47
47
  items: [Commit, 分支, PR描述, 文档]
48
48
  - name: "C. 安全与风险"
49
49
  weight: 25
@@ -52,8 +52,19 @@ review_dimensions:
52
52
  weight: 10
53
53
  items: [N+1, 内存, 体积]
54
54
  - name: "E. 测试覆盖"
55
- weight: 20
56
- items: [存在性, 覆盖率, 质量]
55
+ weight: 30
56
+ items: [单元测试, 覆盖率, 测试质量, 测试用例文档, 前端组件测试, 后端Service测试]
57
+
58
+ fail_fast_conditions:
59
+ - code: "E4-NO-TEST-CASES"
60
+ condition: "缺少测试用例文档(.harness/doc/test-cases/ 下无 feature-points.md)"
61
+ action: "直接 FAIL"
62
+ - code: "E5-NO-VUE-TEST"
63
+ condition: "新增/修改的 Vue 组件缺少对应测试文件"
64
+ action: "直接 FAIL"
65
+ - code: "E5-NO-JAVA-TEST"
66
+ condition: "新增/修改的 Service 类缺少对应测试类"
67
+ action: "直接 FAIL"
57
68
 
58
69
  constraints:
59
70
  - 不改代码(只评论)
@@ -41,6 +41,9 @@ dependencies:
41
41
  responsibilities:
42
42
  - 按设计文档编写代码
43
43
  - 编写/更新对应单元测试
44
+ - 前端组件必须有 .test.ts/.spec.ts 文件(Vitest/Jest)
45
+ - 后端 Service 必须有对应 Test 类(JUnit5+Mockito)
46
+ - 生成测试用例文档(test-case-designer Skill → .harness/doc/test-cases/)
44
47
  - 运行 Build/Test/Lint 三步自检
45
48
  - 更新 dev-map(如有结构性变化)
46
49
  - 规范 Commit(Conventional Commits + 关联 Issue)
@@ -57,3 +60,6 @@ quality_redlines:
57
60
  - 裸any类型(无注释说明)
58
61
  - console.log/debugger残留
59
62
  - 跳过任意一步自检流程
63
+ - 前端组件无测试文件
64
+ - 后端 Service 无测试类
65
+ - 缺少测试用例文档
@@ -51,18 +51,23 @@ dependencies:
51
51
  1. 阅读设计文档 + dev-map 相关部分
52
52
  2. 实现核心逻辑
53
53
  3. 编写/更新对应单元测试
54
- 4. 运行 Build Skill 编译通过?
55
- 5. 运行 Test Unit Skill 测试通过 + 覆盖率达标?
56
- 6. 运行 Lint Check Skill → 零 warning?
57
- 7. 更新 dev-map(如有结构性变化)
58
- 8. 规范 Commit(Conventional Commits + 关联 Issue)
59
- 9. 创建 PR/MR
54
+ - **前端**: 每个 .vue 组件必须有 .test.ts/.spec.ts 文件(Vitest/Jest)
55
+ - **后端**: 每个 Service/ServiceImpl 必须有对应 Test 类(JUnit5+Mockito)
56
+ 4. 生成测试用例文档(test-case-designer Skill
57
+ 5. 运行 Build Skill → 编译通过?
58
+ 6. 运行 Test Unit Skill → 测试通过 + 覆盖率达标?
59
+ 7. 运行 Lint Check Skill → 零 warning?
60
+ 8. 更新 dev-map(如有结构性变化)
61
+ 9. 规范 Commit(Conventional Commits + 关联 Issue)
62
+ 10. 创建 PR/MR
60
63
 
61
64
  ## 质量红线(触碰即打回)
62
65
  - 硬编码密钥/token
63
66
  - 裸 `any` 类型(无注释说明)
64
67
  - console.log / debugger 残留
65
68
  - 跳过任意一步自检流程
69
+ - **缺少单元测试文件**(前端组件无 .test.ts / 后端 Service 无 Test 类)
70
+ - **缺少测试用例文档**(.harness/doc/test-cases/ 下无对应 feature-points.md)
66
71
 
67
72
  ## 你的约束
68
73
  - ❌ 不改需求和设计文档
@@ -40,6 +40,9 @@ outputFormat: .harness/doc/test-report/test-report-{task-id}.md
40
40
 
41
41
  responsibilities:
42
42
  - 制定测试策略和计划
43
+ - 验证测试用例文档完整性(.harness/doc/test-cases/ 下有 feature-points.md)
44
+ - 验证前端组件测试覆盖(每个 .vue 有对应 .test.ts/.spec.ts)
45
+ - 验证后端 Service 测试覆盖(每个 Service 有对应 Test 类)
43
46
  - 设计和执行各类测试
44
47
  - 记录和跟踪缺陷
45
48
  - 输出完整的测试报告
@@ -50,7 +50,9 @@ outputFormat: .harness/doc/test-report/test-report-{task-id}.md
50
50
  ## 测试覆盖矩阵
51
51
  | 测试类型 | 执行时机 | 负责人 | 必须? | 引用Skill |
52
52
  |----------|----------|--------|-------|-----------|
53
+ | 测试用例验证 | 测试开始前 | Tester | ✅ 必须 | test-case-designer |
53
54
  | 单元测试 | 开发时 | Developer | ✅ 必须 | test-unit |
55
+ | 前端组件测试 | 开发时 | Developer | ✅ 必须 | test-unit |
54
56
  | 静态检查 | 每次 commit | Dev/CI | ✅ 必须 | lint-check |
55
57
  | API 集成测试 | PR 前 | Tester | ✅ 必须 | test-api |
56
58
  | E2E 关键路径 | 预发布前 | Tester | ✅ 必须 | test-e2e |
@@ -73,6 +75,15 @@ outputFormat: .harness/doc/test-report/test-report-{task-id}.md
73
75
  - 核心 E2E 路径全部通过
74
76
  - API 契约无破坏性变更
75
77
  - 测试覆盖率不低于基线
78
+ - **测试用例文档存在且与验收标准对齐**(.harness/doc/test-cases/ 下有 feature-points.md)
79
+ - **前端组件测试覆盖 ≥80%**(每个 .vue 有对应 .test.ts/.spec.ts)
80
+
81
+ ## 测试执行前置检查
82
+ 在正式执行测试前,必须验证以下前置条件:
83
+ 1. **测试用例文档检查** — 确认 `.harness/doc/test-cases/{需求名称}/feature-points.md` 存在
84
+ 2. **前端测试文件存在性** — 扫描 src/ 下 .vue 文件,确认每个组件有对应测试文件
85
+ 3. **后端测试类存在性** — 扫描 Service/ServiceImpl 类,确认有对应 Test 类
86
+ 4. 任何前置条件不满足 → 记录为 P1 缺陷并要求开发补充
76
87
 
77
88
  ## 你的约束
78
89
  - ❌ 不改被测代码
@@ -134,6 +134,66 @@ async function run(options = {}) {
134
134
  );
135
135
  }
136
136
 
137
+ // === F-B6: 前端单元测试执行 ===
138
+ if (pkgJson?.scripts?.test || pkgJson?.scripts?.['test:unit'] || pkgJson?.scripts?.['test:coverage']) {
139
+ const testCmd = pkgJson.scripts['test:coverage'] || pkgJson.scripts['test:unit'] || pkgJson.scripts.test;
140
+ await runCheck(
141
+ '前端单元测试 (npm run test)',
142
+ `${pm} run test 2>&1`,
143
+ { timeout: 180000, blocking: true, suggestion: '前端项目必须有单元测试且全部通过。使用 Vitest 或 Jest 编写组件测试。参考 skills/test-unit/SKILL.md' }
144
+ );
145
+ } else {
146
+ // 无测试脚本 → 严重问题
147
+ issues.push({
148
+ code: 'F-B6',
149
+ severity: 'error',
150
+ message: '前端项目缺少测试脚本 (package.json 中无 test/test:unit/test:coverage 脚本)',
151
+ suggestion: '必须添加测试脚本。推荐配置: "test": "vitest run", "test:coverage": "vitest run --coverage"'
152
+ });
153
+ checks.push({ name: '前端单元测试', status: 'fail', duration_ms: 0, error: '无测试脚本' });
154
+ }
155
+
156
+ // === F-B7: 前端组件测试文件存在性检查 ===
157
+ const srcDir = path.join(process.cwd(), 'src');
158
+ if (fs.existsSync(srcDir)) {
159
+ const vueFiles = findVueFiles(srcDir);
160
+ const testFileNames = new Set(
161
+ findTestFiles(srcDir).map(f => {
162
+ const base = path.basename(f);
163
+ return base.replace(/\.(test|spec)\.(ts|js)$/, '.vue');
164
+ })
165
+ );
166
+
167
+ const untestedVue = vueFiles.filter(vf => {
168
+ const base = path.basename(vf);
169
+ return !testFileNames.has(base);
170
+ }).map(vf => vf.replace(srcDir + path.sep, ''));
171
+
172
+ if (untestedVue.length > 0 && vueFiles.length > 0) {
173
+ const coveragePercent = ((vueFiles.length - untestedVue.length) / vueFiles.length * 100).toFixed(1);
174
+ if (parseFloat(coveragePercent) < 80) {
175
+ issues.push({
176
+ code: 'F-B7',
177
+ severity: 'error',
178
+ message: `前端组件测试覆盖不足: ${coveragePercent}% — ${untestedVue.length} 个组件缺少测试文件`,
179
+ details: untestedVue.slice(0, 10),
180
+ suggestion: '每个 .vue 组件必须有对应的 .test.ts 或 .spec.ts 文件。这是 Gate 强制检查项。'
181
+ });
182
+ } else {
183
+ issues.push({
184
+ code: 'F-B7',
185
+ severity: 'warning',
186
+ message: `前端组件测试覆盖 ${coveragePercent}%,${untestedVue.length} 个组件缺少测试`,
187
+ details: untestedVue.slice(0, 5),
188
+ suggestion: '建议为所有组件补充测试'
189
+ });
190
+ }
191
+ checks.push({ name: '前端组件测试覆盖', status: parseFloat(coveragePercent) < 80 ? 'fail' : 'pass', duration_ms: 0 });
192
+ } else if (vueFiles.length > 0) {
193
+ checks.push({ name: '前端组件测试覆盖', status: 'pass', duration_ms: 0 });
194
+ }
195
+ }
196
+
137
197
  // 计算结果
138
198
  const passed = checks.filter(c => c.status === 'pass').length;
139
199
  const failed = checks.filter(c => c.status === 'fail').length;
@@ -150,3 +210,37 @@ async function run(options = {}) {
150
210
  }
151
211
 
152
212
  module.exports = run;
213
+
214
+ /**
215
+ * 递归查找 .vue 文件
216
+ */
217
+ function findVueFiles(dir, results = []) {
218
+ if (!fs.existsSync(dir)) return results;
219
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
220
+ for (const entry of entries) {
221
+ const fullPath = path.join(dir, entry.name);
222
+ if (entry.isDirectory() && !['node_modules', '.git', 'dist', 'coverage'].includes(entry.name)) {
223
+ findVueFiles(fullPath, results);
224
+ } else if (entry.name.endsWith('.vue')) {
225
+ results.push(fullPath);
226
+ }
227
+ }
228
+ return results;
229
+ }
230
+
231
+ /**
232
+ * 递归查找测试文件 (.test.ts / .spec.ts / .test.js / .spec.js)
233
+ */
234
+ function findTestFiles(dir, results = []) {
235
+ if (!fs.existsSync(dir)) return results;
236
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
237
+ for (const entry of entries) {
238
+ const fullPath = path.join(dir, entry.name);
239
+ if (entry.isDirectory() && !['node_modules', '.git', 'dist', 'coverage'].includes(entry.name)) {
240
+ findTestFiles(fullPath, results);
241
+ } else if (/\.(test|spec)\.(ts|js)$/.test(entry.name)) {
242
+ results.push(fullPath);
243
+ }
244
+ }
245
+ return results;
246
+ }
@@ -2,13 +2,16 @@
2
2
  * Gate Check Category C: 测试合规 (test-compliance)
3
3
  *
4
4
  * 检查项:
5
- * - 单元测试执行与覆盖率收集
6
- * - E2E 关键路径测试(可选)
7
- * - API 集成测试(可选)
8
- * - 测试数量对比(基线模式)
5
+ * - C1: 单元测试执行与覆盖率收集
6
+ * - C2: 测试数量趋势(如果有基线)
7
+ * - C3: 测试用例文档完整性(.harness/doc/test-cases/)
8
+ * - C4: 前端组件测试覆盖(每个 .vue 有对应 .test.ts/.spec.ts)
9
+ * - C5: 后端 Service 测试覆盖(每个 Service 有对应 Test 类)
9
10
  */
10
11
 
11
12
  const { execSync } = require('child_process');
13
+ const fs = require('fs');
14
+ const path = require('path');
12
15
 
13
16
  async function run(options = {}) {
14
17
  const issues = [];
@@ -100,6 +103,151 @@ async function run(options = {}) {
100
103
  testResults.test_count = testResults.unit.total;
101
104
  }
102
105
 
106
+ // C3: 测试用例文档完整性检查
107
+ const cwd = process.cwd();
108
+ const testCasesDir = path.join(cwd, '.harness', 'doc', 'test-cases');
109
+ let testCaseStatus = { exists: false, files: [] };
110
+
111
+ if (fs.existsSync(testCasesDir)) {
112
+ const entries = fs.readdirSync(testCasesDir, { withFileTypes: true });
113
+ const requirementDirs = entries.filter(e => e.isDirectory());
114
+
115
+ for (const dir of requirementDirs) {
116
+ const dirPath = path.join(testCasesDir, dir.name);
117
+ const files = fs.readdirSync(dirPath);
118
+ const hasFeaturePoints = files.some(f => f === 'feature-points.md');
119
+ const hasCyclePlan = files.some(f => f === 'cycle-plan.md');
120
+ const hasSpecialPlan = files.some(f => f === 'special-plan.md');
121
+
122
+ testCaseStatus.files.push({
123
+ requirement: dir.name,
124
+ feature_points: hasFeaturePoints,
125
+ cycle_plan: hasCyclePlan,
126
+ special_plan: hasSpecialPlan
127
+ });
128
+
129
+ if (!hasFeaturePoints) {
130
+ issues.push({
131
+ code: 'C3',
132
+ severity: 'error',
133
+ message: `需求 "${dir.name}" 缺少功能点测试用例文档 (feature-points.md)`,
134
+ suggestion: '使用 test-case-designer Skill 生成标准化测试用例文档'
135
+ });
136
+ }
137
+ }
138
+
139
+ testCaseStatus.exists = requirementDirs.length > 0;
140
+
141
+ if (requirementDirs.length === 0) {
142
+ issues.push({
143
+ code: 'C3',
144
+ severity: 'warning',
145
+ message: '测试用例文档目录为空,没有找到任何需求的测试用例',
146
+ suggestion: '在开发阶段应使用 test-case-designer Skill 生成测试用例文档'
147
+ });
148
+ }
149
+ } else {
150
+ issues.push({
151
+ code: 'C3',
152
+ severity: 'error',
153
+ message: '测试用例文档目录不存在 (.harness/doc/test-cases/)',
154
+ suggestion: '开发阶段必须使用 test-case-designer Skill 生成测试用例文档。这是 Gate C3 强制检查项。'
155
+ });
156
+ }
157
+ testResults.test_cases = testCaseStatus;
158
+
159
+ // C4: 前端组件测试覆盖检查
160
+ const srcDir = path.join(cwd, 'src');
161
+ if (fs.existsSync(srcDir)) {
162
+ const vueFiles = findFiles(srcDir, '.vue');
163
+ const testFiles = new Set(findFiles(srcDir, '.test.ts')
164
+ .concat(findFiles(srcDir, '.spec.ts'))
165
+ .concat(findFiles(srcDir, '.test.js'))
166
+ .concat(findFiles(srcDir, '.spec.js'))
167
+ .map(f => path.basename(f, path.extname(f)).replace(/\.(test|spec)$/, '')));
168
+
169
+ const untestedVueComponents = [];
170
+ for (const vueFile of vueFiles) {
171
+ const componentName = path.basename(vueFile, '.vue');
172
+ // 检查是否有对应的测试文件(同名或含组件名的测试文件)
173
+ const hasTest = testFiles.has(componentName) ||
174
+ Array.from(testFiles).some(t => t.includes(componentName));
175
+ if (!hasTest) {
176
+ untestedVueComponents.push(vueFile.replace(srcDir + path.sep, ''));
177
+ }
178
+ }
179
+
180
+ testResults.frontend_coverage = {
181
+ total_components: vueFiles.length,
182
+ tested_components: vueFiles.length - untestedVueComponents.length,
183
+ untested: untestedVueComponents
184
+ };
185
+
186
+ if (untestedVueComponents.length > 0 && vueFiles.length > 0) {
187
+ const coveragePercent = ((vueFiles.length - untestedVueComponents.length) / vueFiles.length * 100).toFixed(1);
188
+
189
+ if (parseFloat(coveragePercent) < 80) {
190
+ issues.push({
191
+ code: 'C4',
192
+ severity: 'error',
193
+ message: `前端组件测试覆盖不足: ${coveragePercent}% (${vueFiles.length - untestedVueComponents.length}/${vueFiles.length}) — ${untestedVueComponents.length} 个组件缺少测试`,
194
+ details: untestedVueComponents.slice(0, 10),
195
+ suggestion: '每个 Vue 组件必须有对应的 .test.ts 或 .spec.ts 文件。请使用 Vitest/Jest 编写组件测试。'
196
+ });
197
+ } else {
198
+ issues.push({
199
+ code: 'C4',
200
+ severity: 'warning',
201
+ message: `前端组件测试覆盖 ${coveragePercent}%,${untestedVueComponents.length} 个组件缺少测试`,
202
+ details: untestedVueComponents.slice(0, 5),
203
+ suggestion: '建议为所有组件补充测试文件'
204
+ });
205
+ }
206
+ }
207
+ }
208
+
209
+ // C5: 后端 Service 测试覆盖检查
210
+ const javaSrcDir = path.join(cwd, 'src', 'main', 'java');
211
+ if (fs.existsSync(javaSrcDir)) {
212
+ const serviceFiles = findFiles(javaSrcDir, 'ServiceImpl.java')
213
+ .concat(findFiles(javaSrcDir, 'Service.java').filter(f => !f.includes('Impl')));
214
+ const testSrcDir = path.join(cwd, 'src', 'test', 'java');
215
+
216
+ let testClassNames = new Set();
217
+ if (fs.existsSync(testSrcDir)) {
218
+ const testJavaFiles = findFiles(testSrcDir, 'Test.java')
219
+ .concat(findFiles(testSrcDir, 'Tests.java'));
220
+ testClassNames = new Set(testJavaFiles.map(f => path.basename(f)));
221
+ }
222
+
223
+ const untestedServices = [];
224
+ for (const svcFile of serviceFiles) {
225
+ const svcName = path.basename(svcFile, '.java');
226
+ const expectedTestName = `${svcName}Test.java`;
227
+ const expectedTestNameAlt = `${svcName}Tests.java`;
228
+ const hasTest = testClassNames.has(expectedTestName) || testClassNames.has(expectedTestNameAlt);
229
+ if (!hasTest) {
230
+ untestedServices.push(svcFile.replace(javaSrcDir + path.sep, ''));
231
+ }
232
+ }
233
+
234
+ testResults.backend_coverage = {
235
+ total_services: serviceFiles.length,
236
+ tested_services: serviceFiles.length - untestedServices.length,
237
+ untested: untestedServices
238
+ };
239
+
240
+ if (untestedServices.length > 0 && serviceFiles.length > 0) {
241
+ issues.push({
242
+ code: 'C5',
243
+ severity: 'error',
244
+ message: `后端 Service 测试覆盖不足: ${serviceFiles.length - untestedServices.length}/${serviceFiles.length} — ${untestedServices.length} 个 Service 缺少测试类`,
245
+ details: untestedServices.slice(0, 10),
246
+ suggestion: '每个 Service/ServiceImpl 必须有对应的 Test 类(JUnit5+Mockito)。参考 skills/test-unit/SKILL.md'
247
+ });
248
+ }
249
+ }
250
+
103
251
  // 计算状态
104
252
  const hasError = issues.some(i => i.severity === 'error');
105
253
 
@@ -111,4 +259,22 @@ async function run(options = {}) {
111
259
  };
112
260
  }
113
261
 
262
+ /**
263
+ * 递归查找指定扩展名的文件
264
+ */
265
+ function findFiles(dir, extension, results = []) {
266
+ if (!fs.existsSync(dir)) return results;
267
+
268
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
269
+ for (const entry of entries) {
270
+ const fullPath = path.join(dir, entry.name);
271
+ if (entry.isDirectory() && !['node_modules', '.git', 'dist', 'target', 'coverage'].includes(entry.name)) {
272
+ findFiles(fullPath, extension, results);
273
+ } else if (entry.name.endsWith(extension)) {
274
+ results.push(fullPath);
275
+ }
276
+ }
277
+ return results;
278
+ }
279
+
114
280
  module.exports = run;
@@ -61,6 +61,16 @@ enabled: true
61
61
  | E1 | **单元测试** | 新增/修改代码有对应单测 | ⭐⭐⭐ |
62
62
  | E2 | **覆盖率** | 新代码覆盖率 ≥ 85% | ⭐⭐⭐ |
63
63
  | E3 | **测试质量** | 测试有意义(非假断言)| ⭐⭐ |
64
+ | E4 | **测试用例文档** | `.harness/doc/test-cases/` 下有 feature-points.md | ⭐⭐⭐ |
65
+ | E5 | **前端组件测试** | 每个 .vue 文件有对应 .test.ts/.spec.ts | ⭐⭐⭐ |
66
+ | E5 | **后端 Service 测试** | 每个 Service/ServiceImpl 有对应 Test 类 | ⭐⭐⭐ |
67
+
68
+ ### E 类一票否决条件
69
+
70
+ 以下情况直接 FAIL,无需计算总分:
71
+ - **E4-NO-TEST-CASES**: 缺少测试用例文档(.harness/doc/test-cases/ 下无 feature-points.md)
72
+ - **E5-NO-VUE-TEST**: 新增/修改的 Vue 组件缺少对应测试文件
73
+ - **E5-NO-JAVA-TEST**: 新增/修改的 Service 类缺少对应测试类
64
74
 
65
75
  ---
66
76
 
@@ -291,8 +291,16 @@ workflow:
291
291
  required: true
292
292
  - name: "单元测试"
293
293
  format: "source code (.test.*)"
294
- location: "src/"
294
+ location: "src/ 或 test/"
295
+ required: true
296
+ validation:
297
+ frontend: "每个 Vue 组件必须有对应 .test.ts/.spec.ts 文件(Vitest/Jest)"
298
+ backend: "每个 Service 类必须有对应 Test 类(JUnit5+Mockito),覆盖率 ≥80%"
299
+ - name: "测试用例"
300
+ format: "markdown (.harness/doc/test-cases/{需求名称}/)"
295
301
  required: true
302
+ description: "基于 test-case-designer Skill 生成的标准化测试用例文档"
303
+ validation: "必须包含功能点测试用例(feature-points.md),可选拓展周期计划和专项计划"
296
304
  - name: "Git Commit 历史"
297
305
  source: "git_log"
298
306
  required: true
@@ -315,18 +323,22 @@ workflow:
315
323
  action: "实现核心逻辑"
316
324
  skill: null
317
325
  - step: 3
318
- action: "编写/更新单元测试"
326
+ action: "编写/更新单元测试(前端 Vitest/Jest + 后端 JUnit5/Mockito)"
319
327
  skill: "test-unit"
328
+ gate: "无测试文件 → 阻断提交,必须补充后再继续"
329
+ - step: 3.5
330
+ action: "验证测试用例文档完整性"
331
+ skill: "test-case-designer"
332
+ gate: "缺少测试用例文档 → 阻断提交,必须生成 feature-points.md 后再继续"
320
333
  - step: 4
321
334
  action: "运行构建 Skill(按技术栈选择)"
322
335
  # 条件引用:检测 pom.xml → java-build;检测 package.json → vue-frontend-build
323
- # 详见 skills/build.md(已废弃,保留为路由参考)
324
336
  skill: "java-build | vue-frontend-build"
325
337
  gate: "编译失败 → 返回 Step 2"
326
338
  - step: 5
327
- action: "运行 Test Unit Skill"
339
+ action: "运行 Test Unit Skill(前端 Vitest + 后端 JUnit5)"
328
340
  skill: "test-unit"
329
- gate: "测试失败 → 返回 Step 2/3"
341
+ gate: "测试失败 → 返回 Step 2/3 | 前端无测试 → FAIL | 后端覆盖率 <80% → WARNING"
330
342
  - step: 6
331
343
  action: "运行 Lint Check Skill"
332
344
  skill: "lint-check"
@@ -406,11 +418,11 @@ workflow:
406
418
  review_dimensions:
407
419
  - id: quality
408
420
  name: "代码质量"
409
- weight: 30
421
+ weight: 25
410
422
  checks: ["A1-A6"]
411
423
  - id: compliance
412
424
  name: "规范遵循"
413
- weight: 15
425
+ weight: 10
414
426
  checks: ["B1-B5"]
415
427
  - id: security
416
428
  name: "安全与风险"
@@ -422,8 +434,21 @@ workflow:
422
434
  checks: ["D1-D4"]
423
435
  - id: testing
424
436
  name: "测试覆盖"
425
- weight: 20
426
- checks: ["E1-E3"]
437
+ weight: 30
438
+ checks: ["E1-E5"]
439
+ # E4: 测试用例文档完整性(.harness/doc/test-cases/ 下有对应文件)
440
+ # E5: 前端组件测试存在性(每个 .vue 文件有对应 .test.ts/.spec.ts)
441
+
442
+ fail_fast_conditions:
443
+ - condition: "新增/修改的 Vue 组件缺少对应测试文件"
444
+ action: "FAIL — 前端组件必须有单元测试"
445
+ code: "E5-NO-VUE-TEST"
446
+ - condition: "新增/修改的 Service 类缺少对应测试类"
447
+ action: "FAIL — 后端 Service 必须有单元测试"
448
+ code: "E5-NO-JAVA-TEST"
449
+ - condition: "缺少测试用例文档(.harness/doc/test-cases/)"
450
+ action: "FAIL — 必须有标准化测试用例文档"
451
+ code: "E4-NO-TEST-CASES"
427
452
 
428
453
  - id: testing
429
454
  name: "测试验证"
@@ -448,6 +473,10 @@ workflow:
448
473
  - name: "审查报告"
449
474
  from: "code_review"
450
475
  required: true
476
+ - name: "测试用例文档"
477
+ from: "development"
478
+ required: true
479
+ path: ".harness/doc/test-cases/{需求名称}/"
451
480
  - name: "Skill 测试动作集"
452
481
  source: "skills/test-*.md"
453
482
  required: true
@@ -476,6 +505,26 @@ workflow:
476
505
  required: true
477
506
  # 前端: Vitest/Jest + coverage ≥80% | 后端: JUnit5/Mockito + JaCoCo ≥80%
478
507
  threshold: "coverage >= baseline (前端 Vitest / 后端 JaCoCo)"
508
+ frontend_specific:
509
+ framework: "Vitest 或 Jest"
510
+ component_test: "每个 .vue 组件必须有对应 .test.ts/.spec.ts 文件"
511
+ coverage_threshold: "branches ≥80%, functions ≥80%, lines ≥80%"
512
+ blocking: "无测试文件 → Gate FAIL"
513
+ backend_specific:
514
+ framework: "JUnit5 + Mockito"
515
+ service_test: "每个 Service 类必须有对应 Test 类"
516
+ coverage_threshold: "JaCoCo 行覆盖率 ≥80%, 分支覆盖率 ≥80%"
517
+ blocking: "无测试类 → Gate FAIL"
518
+ - name: "test_case_validation"
519
+ skill: "test-case-designer"
520
+ executor: "tester_agent"
521
+ required: true
522
+ # 验证开发阶段产出的测试用例文档与代码实现的覆盖对齐
523
+ threshold: "功能点测试用例覆盖 ≥ 90% 的验收标准"
524
+ validation_checks:
525
+ - "feature-points.md 文件存在且非空"
526
+ - "功能点测试用例与验收标准对齐"
527
+ - "代码中的单元测试覆盖测试用例中的关键场景"
479
528
  - name: "api_integration"
480
529
  skill: "test-api"
481
530
  executor: "tester_agent"
package/lib/index.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * 多适配器架构:检测 AI 工具 → 转换规则格式 → 注入到对应位置
5
5
  *
6
+ * v1.12.5: 强制测试用例和前端单元测试验证 — workflow definition 增加 step 3.5 测试用例检查、E4/E5 审查项、fail_fast_conditions;Gate C3 测试用例文档检查、C4 前端组件测试覆盖、C5 后端 Service 测试覆盖;build-gates-frontend 增加 F-B6 前端单元测试执行、F-B7 组件测试文件检查
6
7
  * v1.12.4: 修复 Qoder commands 输出路径从扁平结构改为 commands/js/ 二级目录,与 codebuddy/cursor/trae 保持一致
7
8
  * v1.12.3: 新增 test-case-designer Skill(功能点/周期计划/专项计划三种测试用例模板);修改 DDB-02 规则细化测试执行方案与测试用例设计边界;tester Agent 增加设计阶段测试用例编写职责
8
9
  * v1.12.1: 操作手册新增 8 张 Mermaid 可视化图表(三层约束架构图、jsspec三步流程图、标准流程总览图、Agent协作时序图、门禁检查流程图、init注入流程图、Agent调度架构图、交付物流转图)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsharness",
3
- "version": "1.12.4",
3
+ "version": "1.12.5",
4
4
  "description": "Harness Engineering - AI 编程行为工程化管控系统。将 rules/skills/commands/agents 四桶一键注入到 CodeBuddy、Cursor、Copilot、Claude Code 等 AI 工具中。",
5
5
  "main": "lib/index.mjs",
6
6
  "bin": {