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.
@@ -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(`git ${args}`, { encoding: "utf8", stdio: ["pipe","pipe","pipe"] }); }
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({ severity: "warning", message: `模块 ${mod} 有较大代码变更 (${total} 行),但 DESIGN.md 未更新`, related_files: modCode.map(c => c.path) });
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({ severity: "info", message: `模块 ${mod} 新增了文件,建议更新 README.md`, related_files: newFiles.map(c => c.path) });
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) issues.push({ severity: "warning", message: `代码变更 ${total} 行,但没有对应的测试更新`, related_files: code.map(c => c.path) });
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) issues.push({ severity: "info", message: "配置文件有变更,请确认是否需要更新文档", related_files: configs.map(c => c.path) });
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) issues.push({ severity: "info", message: `删除了 ${deleted.length} 个文件,请确认相关引用已清理`, related_files: deleted.map(c => c.path) });
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 L = [];
199
- const sep = "=".repeat(60);
200
- const sep2 = "-".repeat(40);
201
- L.push(sep, "变更分析报告", sep);
202
- L.push(`\n变更文件: ${r.changes.length}`);
203
- L.push(`新增行数: +${r.totalAdd}`);
204
- L.push(`删除行数: -${r.totalDel}`);
205
- L.push(`受影响模块: ${[...r.modules].join(", ") || "无"}`);
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
- L.push("\n" + sep2, "变更文件列表:", sep2);
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
- L.push(` ${icons[c.type] || "📝"} ${c.path}${t} (+${c.additions}/-${c.deletions})`);
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
- L.push("\n" + sep2, "文档同步状态:", sep2);
250
+ const lines = ["\n" + DASH, "文档同步状态:", DASH];
236
251
  for (const [doc, synced] of Object.entries(r.docStatus)) {
237
- L.push(` ${synced ? "✓" : "✗"} ${doc}`);
252
+ lines.push(` ${synced ? "✓" : "✗"} ${doc}`);
238
253
  }
254
+ report += '\n' + lines.join('\n');
239
255
  }
240
256
 
241
- L.push("\n" + sep);
242
- return L.join("\n");
257
+ return report;
243
258
  }
244
259
 
245
260
  // CLI
246
- const args = process.argv.slice(2);
247
- let mode = "working", verbose = false, jsonOut = false;
248
- for (let i = 0; i < args.length; i++) {
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
- const result = analyzeChanges(mode);
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
- if (jsonOut) {
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
- process.exit(result.passed ? 0 : 1);
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(['install.sh', 'uninstall.sh', 'install.ps1', 'uninstall.ps1', 'Dockerfile', 'Makefile']);
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)) { add('error', `路径不存在: ${modulePath}`); return { modulePath, issues, structure: {} }; }
49
- if (!fs.statSync(modulePath).isDirectory()) { add('error', `不是目录: ${modulePath}`); return { modulePath, issues, structure: {} }; }
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 => { try { return fs.statSync(path.join(modulePath, d)).isDirectory(); } catch { return false; } });
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 => { try { const s = fs.statSync(path.join(modulePath, n)); return s.isFile() && CODE_EXTS.has(path.extname(n)); } catch { return false; } });
64
- const rootScript = entries.filter(n => { try { return fs.statSync(path.join(modulePath, n)).isFile() && ROOT_SCRIPT_FILES.has(n); } catch { return false; } });
65
- if (rootCode.length || rootScript.length) { srcFound = true; if (rootCode.length > 5) add('warning', `根目录代码文件过多 (${rootCode.length}个),建议整理到 src/ 目录`); }
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 => { try { return fs.statSync(path.join(modulePath, d)).isDirectory(); } catch { return false; } });
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
- if (!['usage', 'install', '使用', '安装', 'example', '示例'].some(k => c.toLowerCase().includes(k))) add('info', 'README.md 建议添加使用说明或示例', readme);
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
- if (!['决策', 'decision', '选择', 'choice', '权衡', 'trade'].some(k => c.toLowerCase().includes(k))) add('info', 'DESIGN.md 建议记录设计决策和权衡', design);
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 lines = [
105
- '='.repeat(60), '模块完整性扫描报告', '='.repeat(60),
106
- `\n模块路径: ${r.modulePath}`,
107
- `扫描结果: ${passed ? '\u2713 通过' : '\u2717 未通过'}`,
108
- `错误: ${errs} | 警告: ${warns}`
109
- ];
110
- if (r.issues.length) {
111
- lines.push('\n' + '-'.repeat(40), '问题列表:', '-'.repeat(40));
112
- for (const i of r.issues) {
113
- const icon = { error: '\u2717', warning: '\u26A0', info: '\u2139' }[i.severity];
114
- lines.push(` ${icon} [${i.severity.toUpperCase()}] ${i.message}`);
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
- lines.push('\n' + '-'.repeat(40), '目录结构:', '-'.repeat(40), formatStructure(r.structure));
150
+ report += '\n' + '-'.repeat(40) + '\n目录结构:\n' + '-'.repeat(40) + '\n' + formatStructure(r.structure);
120
151
  }
121
- lines.push('\n' + '='.repeat(60));
122
- return lines.join('\n');
152
+ return report;
123
153
  }
124
154
 
125
155
  // CLI
126
- const args = process.argv.slice(2);
127
- const verbose = args.includes('-v') || args.includes('--verbose');
128
- const jsonOut = args.includes('--json');
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 (jsonOut) {
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);