jsharness 1.0.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 (68) hide show
  1. package/.harness/README.md +199 -0
  2. package/.harness/agents/code-reviewer/contract.yaml +64 -0
  3. package/.harness/agents/developer/contract.yaml +72 -0
  4. package/.harness/agents/gate-controller/contract.yaml +64 -0
  5. package/.harness/agents/project-manager/contract.yaml +77 -0
  6. package/.harness/agents/prompt-templates.md +352 -0
  7. package/.harness/agents/requirements-analyst/contract.yaml +64 -0
  8. package/.harness/agents/solution-designer/contract.yaml +75 -0
  9. package/.harness/agents/tester/contract.yaml +92 -0
  10. package/.harness/config/models.yaml +67 -0
  11. package/.harness/dev-map/backend/api-definition.md +131 -0
  12. package/.harness/dev-map/backend/auth-security.md +131 -0
  13. package/.harness/dev-map/backend/conventions-java.md +471 -0
  14. package/.harness/dev-map/backend/conventions.md +192 -0
  15. package/.harness/dev-map/backend/database.md +106 -0
  16. package/.harness/dev-map/backend/structure.md +140 -0
  17. package/.harness/dev-map/decisions.md +275 -0
  18. package/.harness/dev-map/frontend/api-integration.md +139 -0
  19. package/.harness/dev-map/frontend/components.md +178 -0
  20. package/.harness/dev-map/frontend/conventions.md +416 -0
  21. package/.harness/dev-map/frontend/state-management.md +170 -0
  22. package/.harness/dev-map/frontend/structure.md +103 -0
  23. package/.harness/dev-map/overview.md +267 -0
  24. package/.harness/docs/integration-test-plan.md +248 -0
  25. package/.harness/docs/team-guidelines/README.md +161 -0
  26. package/.harness/docs/team-guidelines/arch-team.md +811 -0
  27. package/.harness/docs/team-guidelines/collaboration.md +556 -0
  28. package/.harness/docs/team-guidelines/pm-team.md +337 -0
  29. package/.harness/docs/team-guidelines/qa-team.md +562 -0
  30. package/.harness/docs/team-guidelines/rd-team.md +714 -0
  31. package/.harness/docs/training-materials.md +280 -0
  32. package/.harness/gate/baseline.js +220 -0
  33. package/.harness/gate/checks/build-gates-frontend.js +152 -0
  34. package/.harness/gate/checks/build-gates-java.js +155 -0
  35. package/.harness/gate/checks/build-gates.js +119 -0
  36. package/.harness/gate/checks/engineering-consistency.js +138 -0
  37. package/.harness/gate/checks/security-quality.js +129 -0
  38. package/.harness/gate/checks/static-compliance.js +313 -0
  39. package/.harness/gate/checks/test-compliance.js +114 -0
  40. package/.harness/gate/index.js +315 -0
  41. package/.harness/mcp/config.yaml +435 -0
  42. package/.harness/rules/global/coding-standard.md +232 -0
  43. package/.harness/rules/global/commit-convention.md +165 -0
  44. package/.harness/rules/global/process-discipline.md +192 -0
  45. package/.harness/rules/global/security-baseline.md +306 -0
  46. package/.harness/rules/project/frontend-vue3.md +293 -0
  47. package/.harness/rules/project/java-backend.md +460 -0
  48. package/.harness/rules/project/web-specific.md +231 -0
  49. package/.harness/skills/build.md +192 -0
  50. package/.harness/skills/code-review.md +251 -0
  51. package/.harness/skills/docker-build.md +227 -0
  52. package/.harness/skills/docs-update.md +164 -0
  53. package/.harness/skills/java-build.md +261 -0
  54. package/.harness/skills/lint-check.md +482 -0
  55. package/.harness/skills/task-board-maintenance.md +105 -0
  56. package/.harness/skills/test-api.md +461 -0
  57. package/.harness/skills/test-e2e.md +431 -0
  58. package/.harness/skills/test-unit.md +649 -0
  59. package/.harness/skills/vue-frontend-build.md +344 -0
  60. package/.harness/specs/quality-feedback/implementation-guide.md +350 -0
  61. package/.harness/task-board.md +121 -0
  62. package/.harness/workflow/definition.yaml +504 -0
  63. package/.harness/workflow/validate.js +320 -0
  64. package/.harness/workflow/variants.yaml +253 -0
  65. package/README.md +237 -0
  66. package/bin/jsharness.js +53 -0
  67. package/lib/index.mjs +778 -0
  68. package/package.json +1 -0
@@ -0,0 +1,280 @@
1
+ # Harness Engineering — 团队文档与培训材料
2
+
3
+ > 本目录包含面向不同角色的使用指南和培训材料。
4
+
5
+ ---
6
+
7
+ ## 12.1 快速入门指南(面向开发者)
8
+
9
+ ### 《Harness 快速入门 — 5 分钟上手》
10
+
11
+ #### 你是谁?
12
+ 你是开发者,你已经在写代码,现在团队引入了 Harness 来规范化 AI 辅助开发的流程。
13
+
14
+ #### Harness 对你意味着什么?
15
+
16
+ ```
17
+ 之前:
18
+ 需求 → 自己理解 → 直接写代码 → 提 PR → 等人 Review → 合入
19
+
20
+ 现在(有了 Harness):
21
+ 需求 → PM路由 → 需求分析师拆解 → 方案设计师出方案 → 闸门审批 →
22
+ 你写代码(遵循 Skill)→ 自检三步 → 提 PR → 审查Agent审 → 测试Agent验 → 合入
23
+ ```
24
+
25
+ 听起来步骤多了?但每一步都有明确的输入和产出,**不再模糊**。
26
+
27
+ #### 你的日常工作流
28
+
29
+ ##### Step 1: 接任务
30
+
31
+ 收到闸门放行后的设计文档:
32
+ - `design-TASK-xxx.md` — 技术方案
33
+ - `api-definition.yaml` — 接口定义
34
+ - `dev-map/frontend/conventions.md` — 编码规范
35
+
36
+ ##### Step 2: 写代码(按 Skill 操作)
37
+
38
+ ```bash
39
+ # 1. 先读设计文档和 dev-map
40
+ # (了解要做什么、怎么做、现有约定)
41
+
42
+ # 2. 写核心代码
43
+ # 3. 同时写/更新单测
44
+ npm run test -- --watch # 开发时保持测试通过
45
+
46
+ # 4. 完成后,运行 Build Skill
47
+ npm run build # 编译必须通过
48
+
49
+ # 5. 运行 Test Unit Skill
50
+ npm run test # 全部通过 + 覆盖率不降
51
+
52
+ # 6. 运行 Lint Check Skill
53
+ npx eslint . && npx prettier --check # 零 warning
54
+
55
+ # 7. 更新 dev-map(如果改了结构)
56
+ # 8. 规范 Commit
57
+ git commit -m "feat(user): add avatar upload (#123)"
58
+
59
+ # 9. 创建 PR
60
+ gh pr create --title "feat(user): add avatar upload (#123)" --body "..."
61
+ ```
62
+
63
+ ##### Step 3: 等 Review
64
+
65
+ 审查 Agent 会检查你的代码。可能的结果:
66
+
67
+ | 结果 | 含义 | 你需要做的 |
68
+ |------|------|-----------|
69
+ | **PASS** | 可以合入 | 等测试通过即可 |
70
+ | **CONDITIONAL_PASS** | 基本OK但有小问题 | 修掉必修问题即可 |
71
+ | **FAIL** | 需要修改 | 按审查报告修复,重新提交 |
72
+
73
+ ##### 常见坑 & 避免
74
+
75
+ | 坑 | 怎么避免 |
76
+ |----|----------|
77
+ | 忘记写单测 | 边写代码边写测试,TDD 思路 |
78
+ | console.log 残留 | 提交前 `npx eslint .` 会抓到 |
79
+ | 覆盖率降了 | Gate 会对比基线,新增代码必须有足够测试 |
80
+ | 不知道怎么命名 | 看 `dev-map/frontend/conventions.md` |
81
+ | 不确定某个 API 怎么调 | 看 `dev-map/frontend/api-integration.md` |
82
+
83
+ #### 遇到问题?
84
+
85
+ ```
86
+ 1. 先看 dev-map — 大部分问题答案都在那里
87
+ 2. 再看 Skill 文件 — 标准操作步骤
88
+ 3. 最后问 PM 或 Tech Lead
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 12.2 PM 操作手册
94
+
95
+ ### 《PM 操作指南 — 如何用好 TaskBoard 和路由》
96
+
97
+ #### 你的核心职责
98
+
99
+ 你不是技术决策者,你是**交通指挥官**。
100
+
101
+ #### 日常操作
102
+
103
+ ##### 1. 接收新需求
104
+
105
+ ```
106
+ 来源: 产品经理口头 / Jira Issue / 邮件 / 会议纪要
107
+
108
+ 操作:
109
+ 1. 在 TaskBoard「待分配池」中快速记录
110
+ 2. 判断类型:
111
+ ├── 新功能? → 标记为 standard
112
+ ├── Bug? → 判断严重度 → hotfix(紧急) / bugfix(一般)
113
+ ├── 文档/配置? → micro
114
+ └── 安全问题? → security-response
115
+ 3. 如果是重复需求 → 关联已有任务,不新建
116
+ 4. 分配 Task ID → 移入「待开始」→ 分配给需求分析师
117
+ ```
118
+
119
+ ##### 2. 监控进度(每天至少看一次 TaskBoard)
120
+
121
+ ```
122
+ 关注信号:
123
+ ⚠️ 任何任务在同一阶段停留超过 48 小时 → 询问原因
124
+ ⚠️ 打回次数超过 2 次 → 可能是需求不清或能力不足
125
+ ⚠️ 同一阶段堆积 >3 个任务 → 瓶颈预警,考虑加资源或排优先级
126
+ ✅ 任务正常流动 → 无需干预
127
+ ```
128
+
129
+ ##### 3. 处理冲突和阻塞
130
+
131
+ ```
132
+ 当下游提出变更请求时:
133
+ 1. 阅读变更请求内容
134
+ 2. 判断是否合理
135
+ 3. 合理 → 路由给对应上游角色处理
136
+ 4. 不合理(越界/不合理要求)→ 与下游沟通解释为什么
137
+ 5. 记录决策到 TaskBoard 备注
138
+ ```
139
+
140
+ ##### 4. 周报生成
141
+
142
+ 每周五下午,基于 TaskBoard 度量指标自动生成周报:
143
+ - 本周完成多少个需求
144
+ - 平均交付周期
145
+ - 各阶段瓶颈在哪里
146
+ - 下周重点
147
+
148
+ #### 禁止事项清单
149
+
150
+ ```
151
+ ❌ "这个应该用 Redis" ← 技术判断,不是你的事
152
+ ❌ "跳过方案设计吧,直接做" ← 流程绕过,不允许
153
+ ❌ "代码质量看起来还行" ← 质量评判交给审查 Agent
154
+ ❌ 未经讨论直接修改其他角色的文档 ← 违反下游不得改上游规则
155
+ ❌ 在闸门 BLOCK 时强行推进 ← 安全是红线
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 12.3 QA 协作指南
161
+
162
+ ### 《QA 协作指南 — 如何在 Harness 中发挥最大价值》
163
+
164
+ #### 你的角色变化
165
+
166
+ ```
167
+ 之前:
168
+ 收到构建通知 → 手动测试 → 报告 Bug → 等修复 → 回归
169
+
170
+ 现在(Harness):
171
+ 测试验证 Agent 的核心执行者
172
+ 自动化测试的设计者和维护者
173
+ Gate 结果的最终把关人
174
+ ```
175
+
176
+ #### 你在流程中的位置
177
+
178
+ ```
179
+ ... → 开发 → 代码审查 → 【你在这里】 → 交付
180
+
181
+ 你接收: 设计文档 + 验收标准 + 代码 + 审查报告
182
+ 你产出: 测试报告 + 缺陷列表 + 回归风险评估
183
+ ```
184
+
185
+ #### 测试策略制定
186
+
187
+ 每次接到任务时:
188
+
189
+ ```yaml
190
+ # 1. 读验收标准(来自需求文档)— 这是你的测试依据
191
+ # 2. 读设计文档 — 了解技术实现,帮助定位缺陷
192
+ # 3. 读审查报告 — 了解已经关注的风险点
193
+ # 4. 制定测试计划:
194
+
195
+ test_plan:
196
+ must_have:
197
+ - 单元测试覆盖率检查(开发者负责执行,你审核结果)
198
+ - API 契约测试(接口是否符合定义)
199
+ - E2E 关键路径测试(核心用户流程)
200
+ - 安全基础扫描(npm audit 等)
201
+
202
+ should_have:
203
+ - 性能测试(如果有 NFR 要求)
204
+ - 边界值测试(字段长度限制、并发等)
205
+ - 兼容性测试(浏览器版本)
206
+
207
+ focus_areas_based_on_review:
208
+ - 审查报告中标记的高风险区域
209
+ - 新增或修改的复杂函数
210
+
211
+ regression_scope:
212
+ - 本次修改影响的功能模块
213
+ - 相关联的数据表和 API
214
+ ```
215
+
216
+ #### 缺陷分级速查
217
+
218
+ | 发现 | 级别 | 响应 |
219
+ |------|------|------|
220
+ | 数据库被删 / 支付金额错误 | P0 🔴 | 立即阻断,通知所有人 |
221
+ | 登录不了 / 核心功能挂了 | P1 🟠 | 24h 内必须修 |
222
+ | 显示偏差 / 非关键路径异常 | P2 🟡 | 本迭代修 |
223
+ | UI 小瑕疵 / 文案错误 | P3 🔵 | 下版本 |
224
+
225
+ #### 与开发者协作的最佳实践
226
+
227
+ ```markdown
228
+ ✅ 做:
229
+ - 缺陷描述包含:复现步骤、期望行为、实际行为、截图/日志
230
+ - 使用统一模板(defects-{task-id}.md)
231
+ - 区分"必修"和"建议"
232
+
233
+ ❌ 不做:
234
+ - 只说"有问题"不给复现步骤
235
+ - 把风格偏好当作 Bug 提(那是 ESLint 的事)
236
+ - 修改被测代码来"证明"Bug 存在
237
+ ```
238
+
239
+ ---
240
+
241
+ ## 12.4 培训会组织指南
242
+
243
+ ### 首次培训会议议程(建议 2 小时)
244
+
245
+ ```markdown
246
+ # Harness Engineering 首次培训会
247
+
248
+ ## 时间安排(总计 ~120 分钟)
249
+
250
+ ### Part 1: 为什么需要 Harness?(15 分钟)
251
+ - 当前 AI 辅助的问题(效果不稳定、不可控)
252
+ - Harness 要解决什么
253
+ - 预期收益
254
+
255
+ ### Part 2: 整体架构概览(20 分钟)
256
+ - 七角色介绍(每人 1 分钟讲自己的职责)
257
+ - 工作流演示(白板画一遍完整流程)
258
+ - Rule / Skill / Gate 三层约束体系
259
+
260
+ ### Part 3: 分角色实操(45 分钟,分 3 组并行)
261
+ - **开发组**: 创建一个简单的 Hello World 功能,走完开发→自检→提 PR 全流程
262
+ - **PM组**: 模拟接收 3 个不同类型的需求,练习路由和 TaskBoard 操作
263
+ - **QA组**: 基于一个已有的 PR,编写测试计划和执行测试
264
+
265
+ ### Part 4: Q&A(20 分钟)
266
+ - 收集各组实操中遇到的问题
267
+ - 解答疑问
268
+
269
+ ### Part 5: 下一步(20 分钟)
270
+ - 选定试点需求
271
+ - 确定试点时间表
272
+ - 明确反馈收集方式
273
+ ```
274
+
275
+ ### 培训材料准备清单
276
+
277
+ - [ ] PPT: Harness 架构图 + 角色职责 + 流程图
278
+ - [ ] 每人一份打印版: Rule 摘要 + Skill 清单 + 快速参考卡
279
+ - [ ] 试用环境:预配置好的分支和测试数据
280
+ - [ ] 反馈表(纸质或在线表单)
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Gate Baseline 对比机制 (baseline.js)
3
+ *
4
+ * 功能:
5
+ * - 保存基线 (--save-baseline): 将当前 gate 结果保存为基线
6
+ * - 加载基线并与当前结果对比 (--baseline)
7
+ * - 标记 REGRESSION / IMPROVEMENT / STABLE
8
+ * - 生成对比报告
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const BASELINE_FILE = path.join(process.cwd(), '.gate-baseline.json');
15
+
16
+ /**
17
+ * 保存当前结果为基线
18
+ * @param {Object} currentResult - 当前 Gate Report JSON
19
+ */
20
+ function saveBaseline(currentResult) {
21
+ const baseline = {
22
+ saved_at: new Date().toISOString(),
23
+ version: currentResult.tool || 'harness-gate v1.0.0',
24
+ overall_status: currentResult.overall_status,
25
+ categories: {},
26
+ metadata: {}
27
+ };
28
+
29
+ // 提取各类别的关键指标
30
+ for (const cat of currentResult.categories || []) {
31
+ baseline.categories[cat.category_id] = {
32
+ status: cat.status,
33
+ score: cat.score,
34
+ issues_count: cat.issues_count,
35
+ key_metrics: extractKeyMetrics(cat)
36
+ };
37
+ }
38
+
39
+ // 提取元数据指标
40
+ baseline.metadata = {
41
+ source_files_count: countSourceFiles(),
42
+ test_files_count: countTestFiles(),
43
+ package_version: getPackageVersion()
44
+ };
45
+
46
+ fs.writeFileSync(BASELINE_FILE, JSON.stringify(baseline, null, 2));
47
+ console.log(`💾 基线已保存到 ${BASELINE_FILE}`);
48
+ return baseline;
49
+ }
50
+
51
+ /**
52
+ * 对比当前结果与基线
53
+ * @param {Object} currentResult - 当前 Gate Report JSON
54
+ * @param {Object} baselineData - 历史基线数据
55
+ * @returns {Object} 对比结果
56
+ */
57
+ function compare(currentResult, baselineData) {
58
+ const comparison = {
59
+ baseline_date: baselineData.saved_at,
60
+ regression: false,
61
+ improvement: false,
62
+ stable: true,
63
+ changes: [],
64
+ details: []
65
+ };
66
+
67
+ // 对比每个类别
68
+ for (const cat of currentResult.categories || []) {
69
+ const baselineCat = baselineData.categories?.[cat.category_id];
70
+ if (!baselineCat) continue;
71
+
72
+ const change = compareCategory(cat, baselineCat);
73
+ if (change) comparison.changes.push(change);
74
+
75
+ // 如果任何类别出现回归
76
+ if (change?.regression) {
77
+ comparison.regression = true;
78
+ comparison.stable = false;
79
+ }
80
+ if (change?.improvement) {
81
+ comparison.improvement = true;
82
+ }
83
+ }
84
+
85
+ // 元数据对比
86
+ if (baselineData.metadata) {
87
+ const currentTests = countTestFiles();
88
+ const baselineTests = baselineData.metadata.test_files_count || 0;
89
+ if (currentTests < baselineTests) {
90
+ comparison.changes.push({
91
+ type: 'metadata',
92
+ metric: 'test_file_count',
93
+ from: baselineTests,
94
+ to: currentTests,
95
+ trend: 'decrease',
96
+ regression: true
97
+ });
98
+ comparison.regression = true;
99
+ comparison.details.push(`⚠️ 测试文件数量下降: ${baselineTests} → ${currentTests} (-${baselineTests - currentTests})`);
100
+ } else if (currentTests > baselineTests) {
101
+ comparison.details.push(`✅ 测试文件数量增加: ${baselineTests} → ${currentTests} (+${currentTests - baselineTests})`);
102
+ comparison.improvement = true;
103
+ }
104
+ }
105
+
106
+ // 生成汇总
107
+ if (comparison.regression) {
108
+ comparison.details.unshift('🔴 检测到 REGRESSION:部分指标相比基线下降!');
109
+ } else if (comparison.improvement) {
110
+ comparison.details.unshift('📈 检测到 IMPROVEMENT:部分指标相比基线改善!');
111
+ } else {
112
+ comparison.details.unshift('✅ STABLE:所有指标与基线持平或改善');
113
+ }
114
+
115
+ return comparison;
116
+ }
117
+
118
+ /**
119
+ * 对比单个类别
120
+ */
121
+ function compareCategory(current, baseline) {
122
+ const changes = [];
123
+ let hasRegression = false;
124
+ let hasImprovement = false;
125
+
126
+ // 状态退化
127
+ if (baseline.status === 'pass' && (current.status === 'fail' || current.status === 'warning')) {
128
+ hasRegression = true;
129
+ changes.push(`[${current.category}] 状态退化: ${baseline.status} → ${current.status}`);
130
+ }
131
+
132
+ // 状态改善
133
+ if ((baseline.status === 'fail' || baseline.status === 'warning') && current.status === 'pass') {
134
+ hasImprovement = true;
135
+ changes.push(`[${current.category}] 状态改善: ${baseline.status} → ${current.status}`);
136
+ }
137
+
138
+ // 问题数量增加(回归信号)
139
+ if (current.issues_count > (baseline.issues_count || 0) + 2) {
140
+ hasRegression = true;
141
+ changes.push(`[${current.category}] 问题数增加: ${(baseline.issues_count || 0)} → ${current.issues_count}`);
142
+ }
143
+ // 问题数量减少(改善信号)
144
+ if ((current.issues_count || 0) < (baseline.issues_count || 0) - 2) {
145
+ hasImprovement = true;
146
+ changes.push(`[${current.category}] 问题数减少: ${(baseline.issues_count || 0)} → ${current.issues_count}`);
147
+ }
148
+
149
+ if (changes.length === 0) return null;
150
+
151
+ return {
152
+ category: current.category_id,
153
+ regression: hasRegression,
154
+ improvement: hasImprovement,
155
+ changes
156
+ };
157
+ }
158
+
159
+ /**
160
+ * 从类别结果中提取关键指标
161
+ */
162
+ function extractKeyMetrics(cat) {
163
+ const metrics = {};
164
+
165
+ // 从 score 中提取百分比
166
+ if (cat.score && typeof cat.score === 'string') {
167
+ const pctMatch = cat.score.match(/(\d+)%/);
168
+ if (pctMatch) metrics.percentage = parseInt(pctMatch[1]);
169
+ }
170
+
171
+ // 从 details 中提取覆盖率等
172
+ if (cat.category_id === 'C' && cat.details?.unit?.coverage) {
173
+ metrics.coverage_avg = cat.details.unit.coverage;
174
+ }
175
+
176
+ return metrics;
177
+ }
178
+
179
+ // --- 辅助函数 ---
180
+
181
+ function countSourceFiles() {
182
+ try {
183
+ const srcDir = path.join(process.cwd(), 'src');
184
+ if (!fs.existsSync(srcDir)) return 0;
185
+ let count = 0;
186
+ function walk(dir) {
187
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
188
+ if (entry.isDirectory()) walk(path.join(dir, entry.name));
189
+ else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) count++;
190
+ }
191
+ }
192
+ walk(srcDir);
193
+ return count;
194
+ } catch { return 0; }
195
+ }
196
+
197
+ function countTestFiles() {
198
+ try {
199
+ const srcDir = path.join(process.cwd(), 'src');
200
+ if (!fs.existsSync(srcDir)) return 0;
201
+ let count = 0;
202
+ function walk(dir) {
203
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
204
+ if (entry.isDirectory()) walk(path.join(dir, entry.name));
205
+ else if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(entry.name)) count++;
206
+ }
207
+ }
208
+ walk(srcDir);
209
+ return count;
210
+ } catch { return 0; }
211
+ }
212
+
213
+ function getPackageVersion() {
214
+ try {
215
+ const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
216
+ return pkg.version || 'unknown';
217
+ } catch { return 'unknown'; }
218
+ }
219
+
220
+ module.exports = { saveBaseline, compare, BASELINE_FILE };
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Gate Check Category B (Frontend): 前端构建门槛
3
+ *
4
+ * 从原 build-gates.js 提取的前端专用逻辑
5
+ * 适用于 package.json 存在的项目(Vue3/Vite 等)
6
+ *
7
+ * 检查项:
8
+ * - F-B1 TypeScript 类型检查(tsc --noEmit)
9
+ * - F-B2 依赖完整性检查(npm/pnpm ls)
10
+ * - F-B3 前端构建(npm run build)
11
+ * - F-B4 ESLint 快速检查
12
+ * - F-B5(可选) npm 安全审计
13
+ *
14
+ * > 来源: harness-java-fullchain change | 创建日期: 2026-05-21
15
+ */
16
+
17
+ const { execSync } = require('child_process');
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ async function run(options = {}) {
22
+ const issues = [];
23
+ const checks = [];
24
+
25
+ // 辅助函数:执行命令并记录结果
26
+ function runCheck(name, cmd, opts = {}) {
27
+ const start = Date.now();
28
+ try {
29
+ execSync(cmd, {
30
+ encoding: 'utf-8',
31
+ timeout: opts.timeout || 120000,
32
+ stdio: 'pipe',
33
+ cwd: process.cwd(),
34
+ ...opts.env ? { env: { ...process.env, ...opts.env } } : {}
35
+ });
36
+ checks.push({ name, status: 'pass', duration_ms: Date.now() - start });
37
+ return true;
38
+ } catch (e) {
39
+ const output = (e.stderr?.toString() || e.stdout?.toString() || e.message).slice(0, 500);
40
+ checks.push({ name, status: 'fail', duration_ms: Date.now() - start, error: output });
41
+ issues.push({
42
+ code: `F-B${checks.length}`,
43
+ severity: opts.blocking ? 'error' : (opts.warningOnly ? 'warning' : 'error'),
44
+ message: `${name} 失败`,
45
+ details: output.split('\n').slice(0, 5),
46
+ suggestion: opts.suggestion
47
+ });
48
+ return false;
49
+ }
50
+ }
51
+
52
+ function getPackageJson() {
53
+ try {
54
+ return JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
55
+ } catch { return null; }
56
+ }
57
+
58
+ const pkgJson = getPackageJson();
59
+
60
+ if (!pkgJson) {
61
+ return {
62
+ status: 'warning',
63
+ score: 'N/A',
64
+ issues: [{ code: 'F-B0', severity: 'warning', message: '未找到 package.json,跳过前端构建门禁', suggestion: '此模块仅适用于前端项目' }],
65
+ summary: { checks: [], passed: 0, failed: 0 }
66
+ };
67
+ }
68
+
69
+ // === F-B1: TypeScript 类型检查 ===
70
+ // 检测是否有 tsconfig.json,有才运行 tsc
71
+ const hasTsConfig = fs.existsSync(path.join(process.cwd(), 'tsconfig.json'));
72
+ if (hasTsConfig) {
73
+ await runCheck(
74
+ 'TypeScript 类型检查 (npx tsc --noEmit)',
75
+ 'npx tsc --noEmit 2>&1',
76
+ { blocking: true, suggestion: '修复类型错误后再提交。运行 npx tsc --noEmit 查看具体错误' }
77
+ );
78
+ } else {
79
+ checks.push({ name: 'TypeScript 类型检查', status: 'skipped', duration_ms: 0, reason: '无 tsconfig.json' });
80
+ }
81
+
82
+ // === F-B2: 依赖完整性检查 ===
83
+ const pm = fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml')) ? 'pnpm' : 'npm';
84
+ await runCheck(
85
+ `依赖完整性检查 (${pm} ls)`,
86
+ `${pm} ls --depth=0 2>&1 | head -20`,
87
+ { timeout: 30000, blocking: true, suggestion: `运行 ${pm} install 安装缺失依赖` }
88
+ );
89
+
90
+ // === F-B3: 前端生产构建 ===
91
+ if (pkgJson?.scripts?.build) {
92
+ await runCheck(
93
+ '前端生产构建 (npm/pnpm run build)',
94
+ `${pm} run build 2>&1`,
95
+ { timeout: 180000, blocking: true, suggestion: '检查构建日志定位错误原因。常见问题:类型错误/环境变量缺失/动态import路径' }
96
+ );
97
+ } else {
98
+ checks.push({ name: '前端生产构建', status: 'skipped', duration_ms: 0, reason: 'package.json 中无 build 脚本' });
99
+ }
100
+
101
+ // === F-B4: ESLint 快速检查 ===
102
+ const hasEslintConfig =
103
+ fs.existsSync(path.join(process.cwd(), 'eslint.config.js')) ||
104
+ fs.existsSync(path.join(process.cwd(), '.eslintrc.js')) ||
105
+ fs.existsSync(path.join(process.cwd(), '.eslintrc.json')) ||
106
+ (pkgJson?.devDependencies && (
107
+ Object.keys(pkgJson.devDependencies).some(d => d.includes('eslint')) ||
108
+ Object.keys(pkgJson.dependencies || {}).some(d => d.includes('eslint'))
109
+ ));
110
+
111
+ if (hasEslintConfig) {
112
+ await runCheck(
113
+ 'ESLint 静态检查',
114
+ `npx eslint . --ext .ts,.tsx,.js,.jsx,.vue --max-warnings 0 --format compact 2>&1 | tail -10`,
115
+ { timeout: 60000, blocking: false, warningOnly: true, suggestion: '运行 npx eslint . --fix 自动修复可修复的问题' }
116
+ );
117
+ } else {
118
+ checks.push({ name: 'ESLint 静态检查', status: 'skipped', duration_ms: 0, reason: '未检测到 ESLint 配置' });
119
+ }
120
+
121
+ // === F-B5(可选): npm 安全审计 ===
122
+ if (pm === 'npm') {
123
+ await runCheck(
124
+ 'npm 安全审计 (npm audit)',
125
+ 'npm audit --audit-level=high 2>&1',
126
+ { timeout: 30000, blocking: false, warningOnly: true, suggestion: '运行 npm audit fix 修复可自动修复的漏洞;HIGH/CRITICAL 需要手动升级依赖版本' }
127
+ );
128
+ } else {
129
+ // pnpm 使用 pnpm audit
130
+ await runCheck(
131
+ 'pnpm 安全审计 (pnpm audit)',
132
+ 'pnpm audit --audit-level high 2>&1',
133
+ { timeout: 30000, blocking: false, warningOnly: true, suggestion: '运行 pnpm audit fix 修复漏洞' }
134
+ );
135
+ }
136
+
137
+ // 计算结果
138
+ const passed = checks.filter(c => c.status === 'pass').length;
139
+ const failed = checks.filter(c => c.status === 'fail').length;
140
+ const hasBlockingFailure = issues.some(i => i.severity === 'error');
141
+
142
+ return {
143
+ project_type: 'frontend',
144
+ package_manager: pm,
145
+ status: hasBlockingFailure ? 'fail' : (failed > 0 ? 'warning' : 'pass'),
146
+ score: `${checks.length > 0 ? Math.round((passed / checks.filter(c => c.status !== 'skipped').length) * 100) : 100}% (${passed}/${checks.filter(c => c.status !== 'skipped').length})`,
147
+ issues,
148
+ summary: { checks, passed, failed, skipped: checks.filter(c => c.status === 'skipped').length }
149
+ };
150
+ }
151
+
152
+ module.exports = run;