code-abyss 1.7.0 → 1.7.1
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/README.md +8 -6
- package/bin/install.js +59 -163
- package/bin/lib/ccline.js +82 -0
- package/bin/lib/utils.js +61 -0
- package/package.json +5 -2
- package/skills/run_skill.js +14 -9
- package/skills/tools/gen-docs/scripts/doc_generator.js +21 -7
- package/skills/tools/lib/shared.js +98 -0
- package/skills/tools/verify-change/scripts/change_analyzer.js +73 -54
- package/skills/tools/verify-module/scripts/module_scanner.js +63 -37
- package/skills/tools/verify-quality/scripts/quality_checker.js +134 -73
- package/skills/tools/verify-security/scripts/security_scanner.js +212 -62
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 验证工具共享库
|
|
5
|
+
* 消灭 verify-* 脚本间的重复代码
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// --- CLI 参数解析 ---
|
|
9
|
+
|
|
10
|
+
function parseCliArgs(argv, extraFlags) {
|
|
11
|
+
const args = argv.slice(2);
|
|
12
|
+
const result = { target: '.', verbose: false, json: false };
|
|
13
|
+
if (extraFlags) Object.assign(result, extraFlags);
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < args.length; i++) {
|
|
16
|
+
if (args[i] === '-v' || args[i] === '--verbose') result.verbose = true;
|
|
17
|
+
else if (args[i] === '--json') result.json = true;
|
|
18
|
+
else if (args[i] === '-h' || args[i] === '--help') { result.help = true; }
|
|
19
|
+
else if (args[i] === '--mode' && args[i + 1]) { result.mode = args[++i]; }
|
|
20
|
+
else if (args[i] === '--exclude') {
|
|
21
|
+
result.exclude = result.exclude || [];
|
|
22
|
+
while (i + 1 < args.length && !args[i + 1].startsWith('-')) result.exclude.push(args[++i]);
|
|
23
|
+
}
|
|
24
|
+
else if (!args[i].startsWith('-')) result.target = args[i];
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// --- 报告格式化 ---
|
|
30
|
+
|
|
31
|
+
const SEP = '='.repeat(60);
|
|
32
|
+
const DASH = '-'.repeat(40);
|
|
33
|
+
const ICONS = {
|
|
34
|
+
error: '\u2717', warning: '\u26A0', info: '\u2139',
|
|
35
|
+
critical: '\u{1F534}', high: '\u{1F7E0}', medium: '\u{1F7E1}', low: '\u{1F535}'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function reportHeader(title, fields) {
|
|
39
|
+
const lines = [SEP, title, SEP];
|
|
40
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
41
|
+
lines.push(`\n${k}: ${v}`);
|
|
42
|
+
}
|
|
43
|
+
return lines;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function reportIssues(issues, verbose, groupBy) {
|
|
47
|
+
if (!issues.length) return [];
|
|
48
|
+
const lines = ['\n' + DASH, '问题列表:', DASH];
|
|
49
|
+
|
|
50
|
+
if (groupBy) {
|
|
51
|
+
const groups = {};
|
|
52
|
+
for (const i of issues) (groups[i[groupBy]] || (groups[i[groupBy]] = [])).push(i);
|
|
53
|
+
for (const cat of Object.keys(groups).sort()) {
|
|
54
|
+
const items = groups[cat];
|
|
55
|
+
lines.push(`\n【${cat}】(${items.length} 个)`);
|
|
56
|
+
for (const i of items.slice(0, 10)) {
|
|
57
|
+
lines.push(` ${ICONS[i.severity] || '\u2139'} ` +
|
|
58
|
+
`${i.file_path || ''}${i.line_number ? ':' + i.line_number : ''}`);
|
|
59
|
+
lines.push(` ${i.message}`);
|
|
60
|
+
if (verbose && i.suggestion) lines.push(` \u{1F4A1} ${i.suggestion}`);
|
|
61
|
+
if (verbose && i.recommendation) lines.push(` \u{1F4A1} ${i.recommendation}`);
|
|
62
|
+
}
|
|
63
|
+
if (items.length > 10) lines.push(` ... 及其他 ${items.length - 10} 个问题`);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
for (const i of issues) {
|
|
67
|
+
const icon = ICONS[i.severity] || '\u2139';
|
|
68
|
+
lines.push(` ${icon} [${i.severity.toUpperCase()}] ${i.message}`);
|
|
69
|
+
if (i.path && verbose) lines.push(` 路径: ${i.path}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return lines;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function reportFooter() { return ['\n' + SEP]; }
|
|
76
|
+
|
|
77
|
+
function buildReport(title, fields, issues, verbose, groupBy) {
|
|
78
|
+
return [...reportHeader(title, fields), ...reportIssues(issues, verbose, groupBy), ...reportFooter()].join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- 通用计数 ---
|
|
82
|
+
|
|
83
|
+
function countBySeverity(issues, field) {
|
|
84
|
+
field = field || 'severity';
|
|
85
|
+
const counts = {};
|
|
86
|
+
for (const i of issues) counts[i[field]] = (counts[i[field]] || 0) + 1;
|
|
87
|
+
return counts;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function hasFatal(issues, fatalLevels) {
|
|
91
|
+
fatalLevels = fatalLevels || ['error'];
|
|
92
|
+
return issues.some(i => fatalLevels.includes(i.severity));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
parseCliArgs, buildReport, reportHeader, reportIssues,
|
|
97
|
+
reportFooter, countBySeverity, hasFatal, SEP, DASH, ICONS
|
|
98
|
+
};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const { execSync } = require("child_process");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const fs = require("fs");
|
|
7
|
+
const { parseCliArgs, buildReport, hasFatal, DASH } = require(path.join(__dirname, '..', '..', 'lib', 'shared.js'));
|
|
7
8
|
|
|
8
9
|
const CODE_EXT = new Set([".py",".go",".rs",".ts",".js",".jsx",".tsx",".java",".c",".cpp",".h",".hpp"]);
|
|
9
10
|
const DOC_EXT = new Set([".md",".rst",".txt",".adoc"]);
|
|
@@ -61,7 +62,7 @@ function parsePorcelainLine(line) {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
function git(args) {
|
|
64
|
-
try { return execSync(
|
|
65
|
+
try { return execSync('git ' + args, { encoding: "utf8", stdio: ["pipe","pipe","pipe"] }); }
|
|
65
66
|
catch { return ""; }
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -146,14 +147,22 @@ function checkDocSync(changes, modules) {
|
|
|
146
147
|
if (!modCode.length) continue;
|
|
147
148
|
const total = modCode.reduce((s, c) => s + c.additions + c.deletions, 0);
|
|
148
149
|
if (total > 50 && !docPaths.has(design)) {
|
|
149
|
-
issues.push({
|
|
150
|
+
issues.push({
|
|
151
|
+
severity: "warning",
|
|
152
|
+
message: `模块 ${mod} 有较大代码变更 (${total} 行),但 DESIGN.md 未更新`,
|
|
153
|
+
related_files: modCode.map(c => c.path)
|
|
154
|
+
});
|
|
150
155
|
docStatus[`${mod}/DESIGN.md`] = false;
|
|
151
156
|
} else {
|
|
152
157
|
docStatus[`${mod}/DESIGN.md`] = true;
|
|
153
158
|
}
|
|
154
159
|
const newFiles = modCode.filter(c => c.type === "added");
|
|
155
160
|
if (newFiles.length && !docPaths.has(readme)) {
|
|
156
|
-
issues.push({
|
|
161
|
+
issues.push({
|
|
162
|
+
severity: "info",
|
|
163
|
+
message: `模块 ${mod} 新增了文件,建议更新 README.md`,
|
|
164
|
+
related_files: newFiles.map(c => c.path)
|
|
165
|
+
});
|
|
157
166
|
}
|
|
158
167
|
}
|
|
159
168
|
return { docStatus, issues };
|
|
@@ -165,12 +174,30 @@ function analyzeImpact(changes) {
|
|
|
165
174
|
const tests = changes.filter(c => c.is_test);
|
|
166
175
|
if (code.length && !tests.length) {
|
|
167
176
|
const total = code.reduce((s, c) => s + c.additions + c.deletions, 0);
|
|
168
|
-
if (total > 30)
|
|
177
|
+
if (total > 30) {
|
|
178
|
+
issues.push({
|
|
179
|
+
severity: "warning",
|
|
180
|
+
message: `代码变更 ${total} 行,但没有对应的测试更新`,
|
|
181
|
+
related_files: code.map(c => c.path)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
169
184
|
}
|
|
170
185
|
const configs = changes.filter(c => c.is_config);
|
|
171
|
-
if (configs.length)
|
|
186
|
+
if (configs.length) {
|
|
187
|
+
issues.push({
|
|
188
|
+
severity: "info",
|
|
189
|
+
message: "配置文件有变更,请确认是否需要更新文档",
|
|
190
|
+
related_files: configs.map(c => c.path)
|
|
191
|
+
});
|
|
192
|
+
}
|
|
172
193
|
const deleted = changes.filter(c => c.type === "deleted");
|
|
173
|
-
if (deleted.length)
|
|
194
|
+
if (deleted.length) {
|
|
195
|
+
issues.push({
|
|
196
|
+
severity: "info",
|
|
197
|
+
message: `删除了 ${deleted.length} 个文件,请确认相关引用已清理`,
|
|
198
|
+
related_files: deleted.map(c => c.path)
|
|
199
|
+
});
|
|
200
|
+
}
|
|
174
201
|
return issues;
|
|
175
202
|
}
|
|
176
203
|
|
|
@@ -195,18 +222,17 @@ function analyzeChanges(mode = "working") {
|
|
|
195
222
|
}
|
|
196
223
|
|
|
197
224
|
function formatReport(r, verbose) {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
L.push(`分析结果: ${r.passed ? "✓ 通过" : "✗ 需要关注"}`);
|
|
225
|
+
const fields = {
|
|
226
|
+
'变更文件': r.changes.length,
|
|
227
|
+
'新增行数': `+${r.totalAdd}`,
|
|
228
|
+
'删除行数': `-${r.totalDel}`,
|
|
229
|
+
'受影响模块': [...r.modules].join(", ") || "无",
|
|
230
|
+
'分析结果': r.passed ? "✓ 通过" : "✗ 需要关注",
|
|
231
|
+
};
|
|
232
|
+
let report = buildReport('变更分析报告', fields, r.issues, verbose);
|
|
207
233
|
|
|
208
234
|
if (r.changes.length && verbose) {
|
|
209
|
-
|
|
235
|
+
const lines = ["\n" + DASH, "变更文件列表:", DASH];
|
|
210
236
|
const icons = { added: "➕", modified: "📝", deleted: "➖", renamed: "📋" };
|
|
211
237
|
for (const c of r.changes) {
|
|
212
238
|
const tags = [];
|
|
@@ -215,56 +241,49 @@ function formatReport(r, verbose) {
|
|
|
215
241
|
if (c.is_test) tags.push("测试");
|
|
216
242
|
if (c.is_config) tags.push("配置");
|
|
217
243
|
const t = tags.length ? ` [${tags.join(", ")}]` : "";
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (r.issues.length) {
|
|
223
|
-
L.push("\n" + sep2, "问题与建议:", sep2);
|
|
224
|
-
const si = { error: "✗", warning: "⚠", info: "ℹ" };
|
|
225
|
-
for (const i of r.issues) {
|
|
226
|
-
L.push(`\n ${si[i.severity] || "ℹ"} [${i.severity.toUpperCase()}] ${i.message}`);
|
|
227
|
-
if (verbose && i.related_files.length) {
|
|
228
|
-
for (const f of i.related_files.slice(0, 5)) L.push(` - ${f}`);
|
|
229
|
-
if (i.related_files.length > 5) L.push(` ... 及其他 ${i.related_files.length - 5} 个文件`);
|
|
230
|
-
}
|
|
244
|
+
lines.push(` ${icons[c.type] || "📝"} ${c.path}${t} (+${c.additions}/-${c.deletions})`);
|
|
231
245
|
}
|
|
246
|
+
report += '\n' + lines.join('\n');
|
|
232
247
|
}
|
|
233
248
|
|
|
234
249
|
if (Object.keys(r.docStatus).length) {
|
|
235
|
-
|
|
250
|
+
const lines = ["\n" + DASH, "文档同步状态:", DASH];
|
|
236
251
|
for (const [doc, synced] of Object.entries(r.docStatus)) {
|
|
237
|
-
|
|
252
|
+
lines.push(` ${synced ? "✓" : "✗"} ${doc}`);
|
|
238
253
|
}
|
|
254
|
+
report += '\n' + lines.join('\n');
|
|
239
255
|
}
|
|
240
256
|
|
|
241
|
-
|
|
242
|
-
return L.join("\n");
|
|
257
|
+
return report;
|
|
243
258
|
}
|
|
244
259
|
|
|
245
260
|
// CLI
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (args[i] === "--mode" && args[i + 1]) { mode = args[++i]; }
|
|
250
|
-
else if (args[i] === "-v" || args[i] === "--verbose") { verbose = true; }
|
|
251
|
-
else if (args[i] === "--json") { jsonOut = true; }
|
|
252
|
-
}
|
|
261
|
+
if (require.main === module) {
|
|
262
|
+
const opts = parseCliArgs(process.argv);
|
|
263
|
+
const result = analyzeChanges(opts.mode || "working");
|
|
253
264
|
|
|
254
|
-
|
|
265
|
+
if (opts.json) {
|
|
266
|
+
console.log(JSON.stringify({
|
|
267
|
+
passed: result.passed,
|
|
268
|
+
total_additions: result.totalAdd,
|
|
269
|
+
total_deletions: result.totalDel,
|
|
270
|
+
affected_modules: [...result.modules],
|
|
271
|
+
changes: result.changes.map(c => ({
|
|
272
|
+
path: c.path, type: c.type, additions: c.additions,
|
|
273
|
+
deletions: c.deletions, is_code: c.is_code,
|
|
274
|
+
is_doc: c.is_doc, is_test: c.is_test
|
|
275
|
+
})),
|
|
276
|
+
issues: result.issues.map(i => ({
|
|
277
|
+
severity: i.severity, message: i.message,
|
|
278
|
+
related_files: i.related_files,
|
|
279
|
+
})),
|
|
280
|
+
doc_sync_status: result.docStatus,
|
|
281
|
+
}, null, 2));
|
|
282
|
+
} else {
|
|
283
|
+
console.log(formatReport(result, opts.verbose));
|
|
284
|
+
}
|
|
255
285
|
|
|
256
|
-
|
|
257
|
-
console.log(JSON.stringify({
|
|
258
|
-
passed: result.passed,
|
|
259
|
-
total_additions: result.totalAdd,
|
|
260
|
-
total_deletions: result.totalDel,
|
|
261
|
-
affected_modules: [...result.modules],
|
|
262
|
-
changes: result.changes.map(c => ({ path: c.path, type: c.type, additions: c.additions, deletions: c.deletions, is_code: c.is_code, is_doc: c.is_doc, is_test: c.is_test })),
|
|
263
|
-
issues: result.issues.map(i => ({ severity: i.severity, message: i.message, related_files: i.related_files })),
|
|
264
|
-
doc_sync_status: result.docStatus,
|
|
265
|
-
}, null, 2));
|
|
266
|
-
} else {
|
|
267
|
-
console.log(formatReport(result, verbose));
|
|
286
|
+
process.exit(result.passed ? 0 : 1);
|
|
268
287
|
}
|
|
269
288
|
|
|
270
|
-
|
|
289
|
+
module.exports = { normalizePath, classifyFile, parsePorcelainLine, parseNameStatusLine, identifyModules };
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { parseCliArgs, buildReport, hasFatal } = require(path.join(__dirname, '..', '..', 'lib', 'shared.js'));
|
|
6
7
|
|
|
7
8
|
const REQUIRED_FILES = { 'README.md': '模块说明文档', 'DESIGN.md': '设计决策文档' };
|
|
8
9
|
const ALT_SRC_DIRS = ['src', 'lib', 'pkg', 'internal', 'cmd', 'app'];
|
|
9
10
|
const ALT_TEST_DIRS = ['tests', 'test', '__tests__', 'spec'];
|
|
10
|
-
const ROOT_SCRIPT_FILES = new Set([
|
|
11
|
+
const ROOT_SCRIPT_FILES = new Set([
|
|
12
|
+
'install.sh', 'uninstall.sh', 'install.ps1',
|
|
13
|
+
'uninstall.ps1', 'Dockerfile', 'Makefile'
|
|
14
|
+
]);
|
|
11
15
|
const CODE_EXTS = new Set(['.py', '.go', '.rs', '.ts', '.js', '.java', '.sh', '.ps1']);
|
|
12
16
|
const TEST_PATTERNS = ['test_', '_test.', '.test.', 'spec_', '_spec.'];
|
|
13
17
|
|
|
@@ -45,8 +49,14 @@ function scanModule(target) {
|
|
|
45
49
|
const issues = [];
|
|
46
50
|
const add = (severity, message, p) => issues.push({ severity, message, path: p || null });
|
|
47
51
|
|
|
48
|
-
if (!fs.existsSync(modulePath)) {
|
|
49
|
-
|
|
52
|
+
if (!fs.existsSync(modulePath)) {
|
|
53
|
+
add('error', `路径不存在: ${modulePath}`);
|
|
54
|
+
return { modulePath, issues, structure: {} };
|
|
55
|
+
}
|
|
56
|
+
if (!fs.statSync(modulePath).isDirectory()) {
|
|
57
|
+
add('error', `不是目录: ${modulePath}`);
|
|
58
|
+
return { modulePath, issues, structure: {} };
|
|
59
|
+
}
|
|
50
60
|
|
|
51
61
|
const structure = scanStructure(modulePath);
|
|
52
62
|
|
|
@@ -58,15 +68,36 @@ function scanModule(target) {
|
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
// source dirs
|
|
61
|
-
let srcFound = ALT_SRC_DIRS.some(d => {
|
|
71
|
+
let srcFound = ALT_SRC_DIRS.some(d => {
|
|
72
|
+
try { return fs.statSync(path.join(modulePath, d)).isDirectory(); }
|
|
73
|
+
catch { return false; }
|
|
74
|
+
});
|
|
62
75
|
const entries = fs.readdirSync(modulePath);
|
|
63
|
-
const rootCode = entries.filter(n => {
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
const rootCode = entries.filter(n => {
|
|
77
|
+
try {
|
|
78
|
+
const s = fs.statSync(path.join(modulePath, n));
|
|
79
|
+
return s.isFile() && CODE_EXTS.has(path.extname(n));
|
|
80
|
+
} catch { return false; }
|
|
81
|
+
});
|
|
82
|
+
const rootScript = entries.filter(n => {
|
|
83
|
+
try {
|
|
84
|
+
return fs.statSync(path.join(modulePath, n)).isFile()
|
|
85
|
+
&& ROOT_SCRIPT_FILES.has(n);
|
|
86
|
+
} catch { return false; }
|
|
87
|
+
});
|
|
88
|
+
if (rootCode.length || rootScript.length) {
|
|
89
|
+
srcFound = true;
|
|
90
|
+
if (rootCode.length > 5) {
|
|
91
|
+
add('warning', `根目录代码文件过多 (${rootCode.length}个),建议整理到 src/ 目录`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
66
94
|
if (!srcFound) add('warning', '未找到源码目录或代码文件');
|
|
67
95
|
|
|
68
96
|
// test dirs
|
|
69
|
-
let testFound = ALT_TEST_DIRS.some(d => {
|
|
97
|
+
let testFound = ALT_TEST_DIRS.some(d => {
|
|
98
|
+
try { return fs.statSync(path.join(modulePath, d)).isDirectory(); }
|
|
99
|
+
catch { return false; }
|
|
100
|
+
});
|
|
70
101
|
if (!testFound) testFound = rglob(modulePath, n => TEST_PATTERNS.some(p => n.includes(p)));
|
|
71
102
|
if (!testFound) add('warning', '未找到测试目录或测试文件');
|
|
72
103
|
|
|
@@ -75,12 +106,16 @@ function scanModule(target) {
|
|
|
75
106
|
if (fs.existsSync(readme)) {
|
|
76
107
|
const c = fs.readFileSync(readme, 'utf-8');
|
|
77
108
|
if (!c.includes('#')) add('warning', 'README.md 缺少标题', readme);
|
|
78
|
-
|
|
109
|
+
const docKeys = ['usage', 'install', '使用', '安装', 'example', '示例'];
|
|
110
|
+
if (!docKeys.some(k => c.toLowerCase().includes(k)))
|
|
111
|
+
add('info', 'README.md 建议添加使用说明或示例', readme);
|
|
79
112
|
}
|
|
80
113
|
const design = path.join(modulePath, 'DESIGN.md');
|
|
81
114
|
if (fs.existsSync(design)) {
|
|
82
115
|
const c = fs.readFileSync(design, 'utf-8');
|
|
83
|
-
|
|
116
|
+
const designKeys = ['决策', 'decision', '选择', 'choice', '权衡', 'trade'];
|
|
117
|
+
if (!designKeys.some(k => c.toLowerCase().includes(k)))
|
|
118
|
+
add('info', 'DESIGN.md 建议记录设计决策和权衡', design);
|
|
84
119
|
}
|
|
85
120
|
|
|
86
121
|
return { modulePath, issues, structure };
|
|
@@ -98,40 +133,31 @@ function formatStructure(s, indent = 0) {
|
|
|
98
133
|
}
|
|
99
134
|
|
|
100
135
|
function formatReport(r, verbose) {
|
|
101
|
-
const passed = !r.issues.some(i => i.severity === 'error');
|
|
102
136
|
const errs = r.issues.filter(i => i.severity === 'error').length;
|
|
103
137
|
const warns = r.issues.filter(i => i.severity === 'warning').length;
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
`错误: ${errs} | 警告: ${warns}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (i.path && verbose) lines.push(` 路径: ${i.path}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
138
|
+
const passed = !hasFatal(r.issues);
|
|
139
|
+
const fields = {
|
|
140
|
+
'模块路径': r.modulePath,
|
|
141
|
+
'扫描结果': passed ? '\u2713 通过' : '\u2717 未通过',
|
|
142
|
+
'统计': `错误: ${errs} | 警告: ${warns}`,
|
|
143
|
+
};
|
|
144
|
+
const issues = r.issues.map(i => ({
|
|
145
|
+
severity: i.severity, message: i.message, path: i.path,
|
|
146
|
+
file_path: i.path || '', line_number: null,
|
|
147
|
+
}));
|
|
148
|
+
let report = buildReport('模块完整性扫描报告', fields, issues, verbose);
|
|
118
149
|
if (verbose && r.structure.name) {
|
|
119
|
-
|
|
150
|
+
report += '\n' + '-'.repeat(40) + '\n目录结构:\n' + '-'.repeat(40) + '\n' + formatStructure(r.structure);
|
|
120
151
|
}
|
|
121
|
-
|
|
122
|
-
return lines.join('\n');
|
|
152
|
+
return report;
|
|
123
153
|
}
|
|
124
154
|
|
|
125
155
|
// CLI
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const target = args.find(a => !a.startsWith('-')) || '.';
|
|
130
|
-
|
|
131
|
-
const result = scanModule(target);
|
|
132
|
-
const passed = !result.issues.some(i => i.severity === 'error');
|
|
156
|
+
const opts = parseCliArgs(process.argv);
|
|
157
|
+
const result = scanModule(opts.target);
|
|
158
|
+
const passed = !hasFatal(result.issues);
|
|
133
159
|
|
|
134
|
-
if (
|
|
160
|
+
if (opts.json) {
|
|
135
161
|
console.log(JSON.stringify({
|
|
136
162
|
module_path: result.modulePath, passed,
|
|
137
163
|
error_count: result.issues.filter(i => i.severity === 'error').length,
|
|
@@ -139,7 +165,7 @@ if (jsonOut) {
|
|
|
139
165
|
issues: result.issues
|
|
140
166
|
}, null, 2));
|
|
141
167
|
} else {
|
|
142
|
-
console.log(formatReport(result, verbose));
|
|
168
|
+
console.log(formatReport(result, opts.verbose));
|
|
143
169
|
}
|
|
144
170
|
|
|
145
171
|
process.exit(passed ? 0 : 1);
|