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.
- package/.harness/README.md +199 -0
- package/.harness/agents/code-reviewer/contract.yaml +64 -0
- package/.harness/agents/developer/contract.yaml +72 -0
- package/.harness/agents/gate-controller/contract.yaml +64 -0
- package/.harness/agents/project-manager/contract.yaml +77 -0
- package/.harness/agents/prompt-templates.md +352 -0
- package/.harness/agents/requirements-analyst/contract.yaml +64 -0
- package/.harness/agents/solution-designer/contract.yaml +75 -0
- package/.harness/agents/tester/contract.yaml +92 -0
- package/.harness/config/models.yaml +67 -0
- package/.harness/dev-map/backend/api-definition.md +131 -0
- package/.harness/dev-map/backend/auth-security.md +131 -0
- package/.harness/dev-map/backend/conventions-java.md +471 -0
- package/.harness/dev-map/backend/conventions.md +192 -0
- package/.harness/dev-map/backend/database.md +106 -0
- package/.harness/dev-map/backend/structure.md +140 -0
- package/.harness/dev-map/decisions.md +275 -0
- package/.harness/dev-map/frontend/api-integration.md +139 -0
- package/.harness/dev-map/frontend/components.md +178 -0
- package/.harness/dev-map/frontend/conventions.md +416 -0
- package/.harness/dev-map/frontend/state-management.md +170 -0
- package/.harness/dev-map/frontend/structure.md +103 -0
- package/.harness/dev-map/overview.md +267 -0
- package/.harness/docs/integration-test-plan.md +248 -0
- package/.harness/docs/team-guidelines/README.md +161 -0
- package/.harness/docs/team-guidelines/arch-team.md +811 -0
- package/.harness/docs/team-guidelines/collaboration.md +556 -0
- package/.harness/docs/team-guidelines/pm-team.md +337 -0
- package/.harness/docs/team-guidelines/qa-team.md +562 -0
- package/.harness/docs/team-guidelines/rd-team.md +714 -0
- package/.harness/docs/training-materials.md +280 -0
- package/.harness/gate/baseline.js +220 -0
- package/.harness/gate/checks/build-gates-frontend.js +152 -0
- package/.harness/gate/checks/build-gates-java.js +155 -0
- package/.harness/gate/checks/build-gates.js +119 -0
- package/.harness/gate/checks/engineering-consistency.js +138 -0
- package/.harness/gate/checks/security-quality.js +129 -0
- package/.harness/gate/checks/static-compliance.js +313 -0
- package/.harness/gate/checks/test-compliance.js +114 -0
- package/.harness/gate/index.js +315 -0
- package/.harness/mcp/config.yaml +435 -0
- package/.harness/rules/global/coding-standard.md +232 -0
- package/.harness/rules/global/commit-convention.md +165 -0
- package/.harness/rules/global/process-discipline.md +192 -0
- package/.harness/rules/global/security-baseline.md +306 -0
- package/.harness/rules/project/frontend-vue3.md +293 -0
- package/.harness/rules/project/java-backend.md +460 -0
- package/.harness/rules/project/web-specific.md +231 -0
- package/.harness/skills/build.md +192 -0
- package/.harness/skills/code-review.md +251 -0
- package/.harness/skills/docker-build.md +227 -0
- package/.harness/skills/docs-update.md +164 -0
- package/.harness/skills/java-build.md +261 -0
- package/.harness/skills/lint-check.md +482 -0
- package/.harness/skills/task-board-maintenance.md +105 -0
- package/.harness/skills/test-api.md +461 -0
- package/.harness/skills/test-e2e.md +431 -0
- package/.harness/skills/test-unit.md +649 -0
- package/.harness/skills/vue-frontend-build.md +344 -0
- package/.harness/specs/quality-feedback/implementation-guide.md +350 -0
- package/.harness/task-board.md +121 -0
- package/.harness/workflow/definition.yaml +504 -0
- package/.harness/workflow/validate.js +320 -0
- package/.harness/workflow/variants.yaml +253 -0
- package/README.md +237 -0
- package/bin/jsharness.js +53 -0
- package/lib/index.mjs +778 -0
- package/package.json +1 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate Check Category B (Java): Java 后端构建门槛
|
|
3
|
+
*
|
|
4
|
+
* 适用于 pom.xml 存在的 Spring Boot / Maven 项目
|
|
5
|
+
* 参考: rules/project/java-backend.md (JDK21 + Spring Boot)
|
|
6
|
+
*
|
|
7
|
+
* 检查项:
|
|
8
|
+
* - J-B1 Maven 编译检查 (mvn clean compile)
|
|
9
|
+
* - J-B2 单元测试快速验证 (mvn test)
|
|
10
|
+
* - J-B3 Checkstyle 代码风格检查 (mvn checkstyle:check)
|
|
11
|
+
* - J-B4 依赖树分析 (mvn dependency:tree, 非阻塞)
|
|
12
|
+
* - J-B5(可选) SpotBugs Bug 扫描 (mvn spotbugs:check)
|
|
13
|
+
* - J-B6(可选) JaCoCo 覆盖率门禁 (mvn jacoco:check)
|
|
14
|
+
*
|
|
15
|
+
* > 来源: harness-java-fullchain change | 创建日期: 2026-05-21
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { execSync } = require('child_process');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
async function run(options = {}) {
|
|
23
|
+
const issues = [];
|
|
24
|
+
const checks = [];
|
|
25
|
+
|
|
26
|
+
// 辅助函数:执行命令并记录结果
|
|
27
|
+
function runCheck(name, cmd, opts = {}) {
|
|
28
|
+
const start = Date.now();
|
|
29
|
+
try {
|
|
30
|
+
execSync(cmd, {
|
|
31
|
+
encoding: 'utf-8',
|
|
32
|
+
timeout: opts.timeout || 180000,
|
|
33
|
+
stdio: 'pipe',
|
|
34
|
+
cwd: process.cwd(),
|
|
35
|
+
...opts.env ? { env: { ...process.env, ...opts.env } } : {}
|
|
36
|
+
});
|
|
37
|
+
checks.push({ name, status: 'pass', duration_ms: Date.now() - start });
|
|
38
|
+
return true;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
const output = (e.stderr?.toString() || e.stdout?.toString() || e.message).slice(0, 500);
|
|
41
|
+
checks.push({ name, status: 'fail', duration_ms: Date.now() - start, error: output });
|
|
42
|
+
issues.push({
|
|
43
|
+
code: `J-B${checks.length}`,
|
|
44
|
+
severity: opts.blocking ? 'error' : (opts.warningOnly ? 'warning' : (opts.infoOnly ? 'info' : 'error')),
|
|
45
|
+
message: `${name} 失败`,
|
|
46
|
+
details: output.split('\n').slice(0, 5),
|
|
47
|
+
suggestion: opts.suggestion
|
|
48
|
+
});
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 检测是否为 Maven 项目(pom.xml 存在)
|
|
55
|
+
*/
|
|
56
|
+
function isMavenProject() {
|
|
57
|
+
return fs.existsSync(path.join(process.cwd(), 'pom.xml'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 检测 pom.xml 是否包含指定插件(简单文本检测)
|
|
62
|
+
*/
|
|
63
|
+
function hasPlugin(pluginArtifactId) {
|
|
64
|
+
try {
|
|
65
|
+
const pomContent = fs.readFileSync(path.join(process.cwd(), 'pom.xml'), 'utf-8');
|
|
66
|
+
// 简单匹配 artifactId 标签
|
|
67
|
+
return new RegExp(`<artifactId>${pluginArtifactId}</artifactId>`, 'i').test(pomContent);
|
|
68
|
+
} catch { return false; }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!isMavenProject()) {
|
|
72
|
+
return {
|
|
73
|
+
status: 'warning',
|
|
74
|
+
score: 'N/A',
|
|
75
|
+
issues: [{ code: 'J-B0', severity: 'warning', message: '未找到 pom.xml,跳过 Java 后端构建门禁', suggestion: '此模块仅适用于 Maven 项目' }],
|
|
76
|
+
summary: { checks: [], passed: 0, failed: 0 }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// === J-B1: Maven 编译检查 ===
|
|
81
|
+
await runCheck(
|
|
82
|
+
'Maven 编译 (mvn clean compile)',
|
|
83
|
+
'mvn clean compile -q -DskipTests 2>&1',
|
|
84
|
+
{ timeout: 180000, blocking: true, suggestion: '检查编译错误。常见原因:语法错误/缺少依赖/import 缺失。运行 mvn clean compile -e 查看完整错误堆栈' }
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// === J-B2: 单元测试快速验证 ===
|
|
88
|
+
await runCheck(
|
|
89
|
+
'单元测试快速验证 (mvn test)',
|
|
90
|
+
'mvn test -q -DfailIfNoTests=false 2>&1',
|
|
91
|
+
{ timeout: 300000, blocking: false, warningOnly: true, suggestion: '运行 mvn test 查看具体失败用例。检查 Surefire 报告: target/surefire-reports/' }
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// === J-B3: Checkstyle 代码风格检查 ===
|
|
95
|
+
if (hasPlugin('maven-checkstyle-plugin')) {
|
|
96
|
+
await runCheck(
|
|
97
|
+
'Checkstyle 代码风格检查 (mvn checkstyle:check)',
|
|
98
|
+
'mvn checkstyle:check -q 2>&1',
|
|
99
|
+
{ timeout: 120000, blocking: false, warningOnly: true, suggestion: '运行 mvn checkstyle:checkstyle 查看详细违规报告。参考 rules/project/java-backend.md §命名规范' }
|
|
100
|
+
);
|
|
101
|
+
} else {
|
|
102
|
+
checks.push({ name: 'Checkstyle 代码风格检查', status: 'skipped', duration_ms: 0, reason: 'pom.xml 中未配置 maven-checkstyle-plugin' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// === J-B4: 依赖树分析(非阻塞,仅记录)===
|
|
106
|
+
await runCheck(
|
|
107
|
+
'Maven 依赖树分析 (mvn dependency:tree)',
|
|
108
|
+
'mvn dependency:tree -q 2>&1 | tail -20',
|
|
109
|
+
{ timeout: 60000, infoOnly: true, suggestion: '仅信息性输出,用于排查依赖冲突' }
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// === J-B5(可选): SpotBugs Bug 扫描 ===
|
|
113
|
+
if (hasPlugin('spotbugs-maven-plugin')) {
|
|
114
|
+
await runCheck(
|
|
115
|
+
'SpotBugs Bug 扫描 (mvn spotbugs:check)',
|
|
116
|
+
'mvn spotbugs:check -q 2>&1',
|
|
117
|
+
{ timeout: 180000, blocking: false, warningOnly: true, suggestion: '查看 SpotBugs 报告: target/spotbugsXml.xml。重点关注 NullPointer / 资源泄漏' }
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
checks.push({ name: 'SpotBugs Bug 扫描', status: 'skipped', duration_ms: 0, reason: 'pom.xml 中未配置 spotbugs-maven-plugin(推荐添加)' });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// === J-B6(可选): JaCoCo 覆盖率门禁 ===
|
|
124
|
+
if (hasPlugin('jacoco-maven-plugin')) {
|
|
125
|
+
await runCheck(
|
|
126
|
+
'JaCoCo 覆盖率检查 (mvn jacoco:check)',
|
|
127
|
+
'mvn jacoco:check -q 2>&1',
|
|
128
|
+
{ timeout: 120000, blocking: false, warningOnly: true, suggestion: '覆盖率不达标时,需补充测试用例。目标: 分支≥80%, 行≥80%。报告: target/site/jacoco/' }
|
|
129
|
+
);
|
|
130
|
+
} else {
|
|
131
|
+
checks.push({ name: 'JaCoCo 覆盖率检查', status: 'skipped', duration_ms: 0, reason: 'pom.xml 中未配置 jacoco-maven-plugin(强烈推荐添加)' });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 计算结果
|
|
135
|
+
const passed = checks.filter(c => c.status === 'pass').length;
|
|
136
|
+
const failed = checks.filter(c => c.status === 'fail').length;
|
|
137
|
+
const executedChecks = checks.filter(c => c.status !== 'skipped');
|
|
138
|
+
const hasBlockingFailure = issues.some(i => i.severity === 'error');
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
project_type: 'java',
|
|
142
|
+
build_tool: 'maven',
|
|
143
|
+
status: hasBlockingFailure ? 'fail' : (failed > 0 ? 'warning' : 'pass'),
|
|
144
|
+
score: `${executedChecks.length > 0 ? Math.round((passed / executedChecks.length) * 100) : 100}% (${passed}/${executedChecks.length})`,
|
|
145
|
+
issues,
|
|
146
|
+
summary: { checks, passed, failed, skipped: checks.filter(c => c.status === 'skipped').length },
|
|
147
|
+
plugin_status: {
|
|
148
|
+
checkstyle: hasPlugin('maven-checkstyle-plugin'),
|
|
149
|
+
spotbugs: hasPlugin('spotbugs-maven-plugin'),
|
|
150
|
+
jacoco: hasPlugin('jacoco-maven-plugin')
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = run;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate Check Category B: 构建门槛 (路由器)
|
|
3
|
+
*
|
|
4
|
+
* 自动检测项目类型并分发到对应的门禁检查模块:
|
|
5
|
+
* - 检测到 pom.xml → 调用 build-gates-java.js (Maven/Java)
|
|
6
|
+
* - 检测到 package.json → 调用 build-gates-frontend.js (Node/前端)
|
|
7
|
+
* - 都没有 → 返回 warning
|
|
8
|
+
*
|
|
9
|
+
* > 来源: harness-java-fullchain change | 改造日期: 2026-05-21
|
|
10
|
+
* > 原版逻辑已提取至 build-gates-frontend.js
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
// ============================================================
|
|
17
|
+
// 项目类型检测
|
|
18
|
+
// ============================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 检测当前工作目录的项目类型
|
|
22
|
+
*
|
|
23
|
+
* 优先级(从高到低):
|
|
24
|
+
* 1. pom.xml → java (Maven)
|
|
25
|
+
* 2. build.gradle / build.gradle.kts → java (Gradle) — 未来支持
|
|
26
|
+
* 3. package.json → frontend (Node/Vue3)
|
|
27
|
+
* 4. go.mod → golang — 未来支持
|
|
28
|
+
* 5. unknown
|
|
29
|
+
*/
|
|
30
|
+
function detectProjectType() {
|
|
31
|
+
const cwd = process.cwd();
|
|
32
|
+
|
|
33
|
+
if (fs.existsSync(path.join(cwd, 'pom'))) {
|
|
34
|
+
// 检查是文件还是目录(Maven 多模块项目有 pom 目录)
|
|
35
|
+
const stat = fs.statSync(path.join(cwd, 'pom'));
|
|
36
|
+
if (!stat.isDirectory()) return { type: 'java', tool: 'maven' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (fs.existsSync(path.join(cwd, 'pom.xml'))) {
|
|
40
|
+
return { type: 'java', tool: 'maven' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (fs.existsSync(path.join(cwd, 'build.gradle')) ||
|
|
44
|
+
fs.existsSync(path.join(cwd, 'build.gradle.kts'))) {
|
|
45
|
+
return { type: 'java', tool: 'gradle' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (fs.existsSync(path.join(cwd, 'package.json'))) {
|
|
49
|
+
// 进一步区分纯后端 Node.js 和前端项目
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
|
|
52
|
+
const hasDeps = Object.keys(pkg.dependencies || {}).length > 0;
|
|
53
|
+
const hasDevDeps = Object.keys(pkg.devDependencies || {}).length > 0;
|
|
54
|
+
|
|
55
|
+
// 检测是否为前端框架项目
|
|
56
|
+
const frontendDeps = ['vue', 'react', 'next', 'vite', 'webpack', '@angular/core'];
|
|
57
|
+
const hasFrontendDeps = [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {})]
|
|
58
|
+
.some(d => frontendDeps.some(fd => d === fd || d.startsWith(fd + '/') || d.startsWith('@' + fd)));
|
|
59
|
+
|
|
60
|
+
if (hasFrontendDeps || hasDevDeps) {
|
|
61
|
+
return { type: 'frontend', tool: 'node' };
|
|
62
|
+
}
|
|
63
|
+
return { type: 'backend-node', tool: 'node' };
|
|
64
|
+
} catch {
|
|
65
|
+
return { type: 'frontend', tool: 'node' }; // 默认按前端处理
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { type: 'unknown', tool: null };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================
|
|
73
|
+
// 主执行函数
|
|
74
|
+
// ============================================================
|
|
75
|
+
|
|
76
|
+
async function run(options = {}) {
|
|
77
|
+
const detection = detectProjectType();
|
|
78
|
+
const checksDir = __dirname;
|
|
79
|
+
|
|
80
|
+
console.log(` 📋 项目类型检测结果: ${detection.type}${detection.tool ? ` (${detection.tool})` : ''}`);
|
|
81
|
+
|
|
82
|
+
let result;
|
|
83
|
+
|
|
84
|
+
switch (detection.type) {
|
|
85
|
+
case 'java': {
|
|
86
|
+
const javaModule = require(path.join(checksDir, 'build-gates-java'));
|
|
87
|
+
result = await (typeof javaModule === 'function' ? javaModule(options) : javaModule.run(options));
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
case 'frontend':
|
|
92
|
+
case 'backend-node': {
|
|
93
|
+
const frontendModule = require(path.join(checksDir, 'build-gates-frontend'));
|
|
94
|
+
result = await (typeof frontendModule === 'function' ? frontendModule(options) : frontendModule.run(options));
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
default:
|
|
99
|
+
result = {
|
|
100
|
+
status: 'warning',
|
|
101
|
+
score: 'N/A',
|
|
102
|
+
issues: [{
|
|
103
|
+
code: 'B-UNKNOWN',
|
|
104
|
+
severity: 'warning',
|
|
105
|
+
message: `无法识别的项目类型。未找到 pom.xml 或 package.json`,
|
|
106
|
+
suggestion: '请确保在正确的项目根目录中运行,或手动指定 --check 参数'
|
|
107
|
+
}],
|
|
108
|
+
summary: { checks: [], passed: 0, failed: 0 },
|
|
109
|
+
project_type: 'unknown'
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 注入检测信息
|
|
114
|
+
result.detection = detection;
|
|
115
|
+
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = run;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate Check Category E: 工程一致性 (engineering-consistency)
|
|
3
|
+
*
|
|
4
|
+
* 检查项:
|
|
5
|
+
* - CHANGELOG 更新检查
|
|
6
|
+
* - dev-map 一致性检查
|
|
7
|
+
* - 配置同步检查
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
async function run(options = {}) {
|
|
14
|
+
const issues = [];
|
|
15
|
+
let checksPassed = 0;
|
|
16
|
+
let totalChecks = 0;
|
|
17
|
+
|
|
18
|
+
// E1: CHANGELOG 更新检查
|
|
19
|
+
totalChecks++;
|
|
20
|
+
try {
|
|
21
|
+
const changelogPath = path.join(process.cwd(), 'CHANGELOG.md');
|
|
22
|
+
if (fs.existsSync(changelogPath)) {
|
|
23
|
+
const changelog = fs.readFileSync(changelogPath, 'utf-8');
|
|
24
|
+
const hasUnreleased = changelog.toLowerCase().includes('unreleased') ||
|
|
25
|
+
changelog.includes('[Unreleased]');
|
|
26
|
+
|
|
27
|
+
// 检查 Unreleased 区域是否有内容(排除纯标题行)
|
|
28
|
+
if (hasUnreleased) {
|
|
29
|
+
const unreleasedMatch = changelog.match(/##\s*\[Unreleased\][\s\S]*?(?=##\s*\[|$)/i);
|
|
30
|
+
if (unreleasedMatch && unreleasedMatch[0].split('\n').length > 5) {
|
|
31
|
+
checksPassed++; // 有未发布的内容,说明有更新
|
|
32
|
+
} else {
|
|
33
|
+
issues.push({
|
|
34
|
+
code: 'E1',
|
|
35
|
+
severity: 'warning',
|
|
36
|
+
message: 'CHANGELOG.md 存在但 Unreleased 区域可能缺少本次变更条目',
|
|
37
|
+
suggestion: '确保用户可见的功能变更已记录到 CHANGELOG [Unreleased] 区域'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
issues.push({
|
|
42
|
+
code: 'E1',
|
|
43
|
+
severity: 'warning',
|
|
44
|
+
message: 'CHANGELOG.md 缺少 [Unreleased] 区域或格式不规范',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
// 没有 CHANGELOG 文件,不算错但建议创建
|
|
49
|
+
issues.push({
|
|
50
|
+
code: 'E1',
|
|
51
|
+
severity: 'info',
|
|
52
|
+
message: '项目未找到 CHANGELOG.md 文件(建议创建)'
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
issues.push({ code: 'E1', severity: 'warning', message: `CHANGELOG 检查异常: ${e.message}` });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// E2: dev-map 一致性检查
|
|
60
|
+
totalChecks++;
|
|
61
|
+
try {
|
|
62
|
+
const devMapDir = path.join(process.cwd(), '.harness', 'dev-map');
|
|
63
|
+
if (fs.existsSync(devMapDir)) {
|
|
64
|
+
const overviewPath = path.join(devMapDir, 'overview.md');
|
|
65
|
+
if (fs.existsSync(overviewPath)) {
|
|
66
|
+
checksPassed++;
|
|
67
|
+
} else {
|
|
68
|
+
issues.push({
|
|
69
|
+
code: 'E2',
|
|
70
|
+
severity: 'warning',
|
|
71
|
+
message: 'dev-map 目录存在但缺少 overview.md 总览文件'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch (e) {
|
|
76
|
+
issues.push({ code: 'E2', severity: 'info', message: `dev-map 检查跳过: ${e.message}` });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// E3: package.json 版本一致性
|
|
80
|
+
totalChecks++;
|
|
81
|
+
try {
|
|
82
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
83
|
+
if (fs.existsSync(pkgPath)) {
|
|
84
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
85
|
+
|
|
86
|
+
if (!pkg.version) {
|
|
87
|
+
issues.push({
|
|
88
|
+
code: 'E3',
|
|
89
|
+
severity: 'warning',
|
|
90
|
+
message: 'package.json 缺少 version 字段'
|
|
91
|
+
});
|
|
92
|
+
} else if (pkg.version === '0.0.1' || pkg.version === '1.0.0') {
|
|
93
|
+
// 只是提醒
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
checksPassed++;
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
issues.push({ code: 'E3', severity: 'warning', message: '无法读取 package.json' });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// E4: .gitignore 包含必要排除项
|
|
103
|
+
totalChecks++;
|
|
104
|
+
try {
|
|
105
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
106
|
+
if (fs.existsSync(gitignorePath)) {
|
|
107
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
108
|
+
const requiredEntries = ['node_modules', '.env', 'dist', '.DS_Store'];
|
|
109
|
+
const missing = requiredEntries.filter(e => !gitignore.includes(e));
|
|
110
|
+
|
|
111
|
+
if (missing.length === 0) {
|
|
112
|
+
checksPassed++;
|
|
113
|
+
} else {
|
|
114
|
+
issues.push({
|
|
115
|
+
code: 'E4',
|
|
116
|
+
severity: 'info',
|
|
117
|
+
message: `.gitignore 可能缺少常见排除项: ${missing.join(', ')}`
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// 可选检查
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 计算结果
|
|
126
|
+
const score = totalChecks > 0 ? Math.round((checksPassed / totalChecks) * 100) : 100;
|
|
127
|
+
// E 类通常不会导致门禁 FAIL(除非是严重不一致)
|
|
128
|
+
const status = 'pass'; // E 类作为 warning 级别
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
status: issues.some(i => i.severity === 'error') ? 'fail' : status,
|
|
132
|
+
score: `${score}% (${checksPassed}/${totalChecks})`,
|
|
133
|
+
issues,
|
|
134
|
+
summary: { passed: checksPassed, total: totalChecks }
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = run;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate Check Category D: 安全与质量 (security-quality)
|
|
3
|
+
*
|
|
4
|
+
* 检查项:
|
|
5
|
+
* - CVE 依赖审计(npm audit)
|
|
6
|
+
* - Code Review Approval 状态检查(通过 Git 信息判断)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
async function run(options = {}) {
|
|
12
|
+
const issues = [];
|
|
13
|
+
let securityScore = 100;
|
|
14
|
+
|
|
15
|
+
// D1: npm audit 安全审计
|
|
16
|
+
try {
|
|
17
|
+
const auditOutput = execSync('npm audit --json 2>&1', {
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
timeout: 60000,
|
|
20
|
+
cwd: process.cwd()
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
let auditData;
|
|
24
|
+
try {
|
|
25
|
+
auditData = JSON.parse(auditOutput);
|
|
26
|
+
} catch {
|
|
27
|
+
// npm audit 可能输出非 JSON(如无问题时的警告)
|
|
28
|
+
auditData = {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const vulnerabilities = auditData.vulnerabilities || {};
|
|
32
|
+
const summary = auditData.metadata?.vulnerabilities || {};
|
|
33
|
+
|
|
34
|
+
const criticalCount = summary.critical || Object.values(vulnerabilities).filter(v => v.severity === 'critical').length;
|
|
35
|
+
const highCount = summary.high || Object.values(vulnerabilities).filter(v => v.severity === 'high').length;
|
|
36
|
+
const moderateCount = summary.moderate || Object.values(vulnerabilities).filter(v => v.severity === 'moderate').length;
|
|
37
|
+
|
|
38
|
+
if (criticalCount > 0) {
|
|
39
|
+
securityScore -= criticalCount * 30;
|
|
40
|
+
issues.push({
|
|
41
|
+
code: 'D1',
|
|
42
|
+
severity: 'error',
|
|
43
|
+
message: `🔴 发现 ${criticalCount} 个 CRITICAL 安全漏洞!必须立即修复!`,
|
|
44
|
+
suggestion: '运行 `npm audit fix` 或手动升级受影响的包。CRITICAL 级别漏洞阻断合并。'
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (highCount > 0) {
|
|
49
|
+
securityScore -= highCount * 15;
|
|
50
|
+
issues.push({
|
|
51
|
+
code: 'D1-h',
|
|
52
|
+
severity: 'error',
|
|
53
|
+
message: `发现 ${highCount} 个 HIGH 安全漏洞`,
|
|
54
|
+
suggestion: 'HIGH 级别漏洞必须在合入前修复或提供明确的缓解方案'
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (moderateCount > 5) {
|
|
59
|
+
securityScore -= moderateCount * 2;
|
|
60
|
+
issues.push({
|
|
61
|
+
code: 'D1-m',
|
|
62
|
+
severity: 'warning',
|
|
63
|
+
message: `发现 ${moderateCount} 个 MODERATE 安全建议`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (criticalCount === 0 && highCount === 0 && moderateCount === 0) {
|
|
68
|
+
// 无漏洞
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// npm audit 在某些情况下会以非零退出码退出但仍然输出有效 JSON
|
|
73
|
+
// 这不是真正的错误,忽略
|
|
74
|
+
const output = e.stdout?.toString() || '';
|
|
75
|
+
if (output.includes('"vulnerabilities"')) {
|
|
76
|
+
// 已在上面处理了
|
|
77
|
+
} else {
|
|
78
|
+
issues.push({
|
|
79
|
+
code: 'D1',
|
|
80
|
+
severity: 'warning',
|
|
81
|
+
message: `npm audit 执行异常`,
|
|
82
|
+
details: e.message.slice(0, 200)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// D2: 检查 package.json 中是否有已知的不安全依赖
|
|
88
|
+
try {
|
|
89
|
+
const pkg = require(path.join(process.cwd(), 'package.json'));
|
|
90
|
+
const riskyDeps = [];
|
|
91
|
+
const knownRisky = ['lodash/<4', 'request', 'node-sass', 'uglify-js<3'];
|
|
92
|
+
|
|
93
|
+
// 简单版本检查(实际应使用更精确的方法)
|
|
94
|
+
for (const [dep, version] of Object.entries({...pkg.dependencies, ...pkg.devDependencies || {}})) {
|
|
95
|
+
for (const risky of knownRisky) {
|
|
96
|
+
if (dep.startsWith(risky.split('/')[0]) || dep === risky.split('<')[0]) {
|
|
97
|
+
riskyDeps.push(`${dep}@${version}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (riskyDeps.length > 0) {
|
|
103
|
+
issues.push({
|
|
104
|
+
code: 'D2',
|
|
105
|
+
severity: 'warning',
|
|
106
|
+
message: `发现可能含有已知风险的依赖: ${riskyDeps.join(', ')}`,
|
|
107
|
+
suggestion: '评估替换为更安全的替代品'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// 可选检查
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 计算最终状态
|
|
115
|
+
const finalScore = Math.max(0, securityScore);
|
|
116
|
+
const hasCritical = issues.some(i => i.code === 'D1' && i.severity === 'error');
|
|
117
|
+
const hasHigh = issues.some(i => i.code === 'D1-h' && i.severity === 'error');
|
|
118
|
+
const status = hasCritical || hasHigh ? 'fail' : (issues.length > 0 ? 'warning' : 'pass');
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
status,
|
|
122
|
+
score: `${finalScore}/100`,
|
|
123
|
+
issues,
|
|
124
|
+
summary: { security_score: finalScore, vulnerability_counts: {} }
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const path = require('path');
|
|
129
|
+
module.exports = run;
|