code-abyss 1.7.0 → 1.7.2
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/domains/ai/SKILL.md +2 -0
- package/skills/domains/architecture/SKILL.md +2 -0
- package/skills/domains/data-engineering/SKILL.md +2 -0
- package/skills/domains/development/SKILL.md +2 -0
- package/skills/domains/devops/SKILL.md +2 -0
- package/skills/domains/frontend-design/SKILL.md +1 -0
- package/skills/domains/frontend-design/claymorphism/SKILL.md +2 -0
- package/skills/domains/frontend-design/glassmorphism/SKILL.md +2 -0
- package/skills/domains/frontend-design/liquid-glass/SKILL.md +2 -0
- package/skills/domains/frontend-design/neubrutalism/SKILL.md +2 -0
- package/skills/domains/infrastructure/SKILL.md +2 -0
- package/skills/domains/mobile/SKILL.md +2 -0
- package/skills/domains/orchestration/SKILL.md +2 -1
- package/skills/domains/security/SKILL.md +2 -0
- package/skills/orchestration/multi-agent/SKILL.md +2 -0
- 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
|
@@ -3,6 +3,7 @@
|
|
|
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
|
// 质量规则配置
|
|
8
9
|
const MAX_LINE_LENGTH = 120;
|
|
@@ -15,44 +16,79 @@ const MIN_FUNCTION_NAME_LENGTH = 2;
|
|
|
15
16
|
const EXCLUDE_DIRS = new Set(['.git', 'node_modules', '__pycache__', '.venv', 'venv', 'dist', 'build', '.tox']);
|
|
16
17
|
const CODE_EXTENSIONS = new Set(['.py', '.js', '.ts', '.go', '.java', '.rs', '.c', '.cpp']);
|
|
17
18
|
|
|
18
|
-
const COMMENT_PREFIXES = {
|
|
19
|
+
const COMMENT_PREFIXES = {
|
|
20
|
+
'.js': '//', '.ts': '//', '.go': '//', '.java': '//',
|
|
21
|
+
'.c': '//', '.cpp': '//', '.rs': '//',
|
|
22
|
+
};
|
|
19
23
|
|
|
20
24
|
// --- Analysis ---
|
|
21
25
|
|
|
22
26
|
function analyzeGenericFile(filePath) {
|
|
23
|
-
const metrics = {
|
|
27
|
+
const metrics = {
|
|
28
|
+
path: filePath, lines: 0, code_lines: 0, comment_lines: 0,
|
|
29
|
+
blank_lines: 0, functions: 0, classes: 0,
|
|
30
|
+
max_complexity: 0, avg_function_length: 0,
|
|
31
|
+
};
|
|
24
32
|
const issues = [];
|
|
25
33
|
let content;
|
|
26
|
-
try {
|
|
34
|
+
try {
|
|
35
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
36
|
+
} catch { return { metrics, issues }; }
|
|
27
37
|
|
|
28
38
|
const lines = content.split('\n');
|
|
29
39
|
metrics.lines = lines.length;
|
|
30
|
-
const prefix = COMMENT_PREFIXES[
|
|
40
|
+
const prefix = COMMENT_PREFIXES[
|
|
41
|
+
path.extname(filePath).toLowerCase()
|
|
42
|
+
] || '//';
|
|
31
43
|
|
|
32
44
|
for (let i = 0; i < lines.length; i++) {
|
|
33
45
|
const stripped = lines[i].trim();
|
|
34
46
|
if (!stripped) metrics.blank_lines++;
|
|
35
|
-
else if (
|
|
47
|
+
else if (
|
|
48
|
+
stripped.startsWith(prefix) ||
|
|
49
|
+
stripped.startsWith('/*') ||
|
|
50
|
+
stripped.startsWith('*')
|
|
51
|
+
) metrics.comment_lines++;
|
|
36
52
|
else metrics.code_lines++;
|
|
37
53
|
|
|
38
54
|
if (lines[i].length > MAX_LINE_LENGTH) {
|
|
39
|
-
issues.push({
|
|
55
|
+
issues.push({
|
|
56
|
+
severity: 'info', category: '格式',
|
|
57
|
+
message: `行过长 (${lines[i].length} > ${MAX_LINE_LENGTH})`,
|
|
58
|
+
file_path: filePath, line_number: i + 1,
|
|
59
|
+
suggestion: null,
|
|
60
|
+
});
|
|
40
61
|
}
|
|
41
62
|
}
|
|
42
63
|
|
|
43
64
|
if (metrics.code_lines > MAX_FILE_LENGTH) {
|
|
44
|
-
issues.push({
|
|
65
|
+
issues.push({
|
|
66
|
+
severity: 'warning', category: '复杂度',
|
|
67
|
+
message: `文件过长 (${metrics.code_lines} 行代码 > ${MAX_FILE_LENGTH})`,
|
|
68
|
+
file_path: filePath, suggestion: '考虑拆分为多个模块',
|
|
69
|
+
line_number: null,
|
|
70
|
+
});
|
|
45
71
|
}
|
|
46
72
|
|
|
47
73
|
return { metrics, issues };
|
|
48
74
|
}
|
|
49
75
|
|
|
50
76
|
function analyzePythonFile(filePath) {
|
|
51
|
-
const metrics = {
|
|
77
|
+
const metrics = {
|
|
78
|
+
path: filePath, lines: 0, code_lines: 0, comment_lines: 0,
|
|
79
|
+
blank_lines: 0, functions: 0, classes: 0,
|
|
80
|
+
max_complexity: 0, avg_function_length: 0,
|
|
81
|
+
};
|
|
52
82
|
const issues = [];
|
|
53
83
|
let content;
|
|
54
|
-
try {
|
|
55
|
-
|
|
84
|
+
try {
|
|
85
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
86
|
+
} catch (e) {
|
|
87
|
+
issues.push({
|
|
88
|
+
severity: 'error', category: '文件',
|
|
89
|
+
message: `无法读取文件: ${e.message}`,
|
|
90
|
+
file_path: filePath, line_number: null, suggestion: null,
|
|
91
|
+
});
|
|
56
92
|
return { metrics, issues };
|
|
57
93
|
}
|
|
58
94
|
|
|
@@ -73,12 +109,22 @@ function analyzePythonFile(filePath) {
|
|
|
73
109
|
else { metrics.code_lines++; }
|
|
74
110
|
|
|
75
111
|
if (lines[i].length > MAX_LINE_LENGTH) {
|
|
76
|
-
issues.push({
|
|
112
|
+
issues.push({
|
|
113
|
+
severity: 'info', category: '格式',
|
|
114
|
+
message: `行过长 (${lines[i].length} > ${MAX_LINE_LENGTH})`,
|
|
115
|
+
file_path: filePath, line_number: i + 1,
|
|
116
|
+
suggestion: null,
|
|
117
|
+
});
|
|
77
118
|
}
|
|
78
119
|
}
|
|
79
120
|
|
|
80
121
|
if (metrics.code_lines > MAX_FILE_LENGTH) {
|
|
81
|
-
issues.push({
|
|
122
|
+
issues.push({
|
|
123
|
+
severity: 'warning', category: '复杂度',
|
|
124
|
+
message: `文件过长 (${metrics.code_lines} 行代码 > ${MAX_FILE_LENGTH})`,
|
|
125
|
+
file_path: filePath, suggestion: '考虑拆分为多个模块',
|
|
126
|
+
line_number: null,
|
|
127
|
+
});
|
|
82
128
|
}
|
|
83
129
|
|
|
84
130
|
// Regex-based Python analysis (no AST available in Node)
|
|
@@ -91,7 +137,10 @@ function analyzePythonFile(filePath) {
|
|
|
91
137
|
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
92
138
|
const name = match[2];
|
|
93
139
|
const indent = match[1].length;
|
|
94
|
-
const params = match[3].trim()
|
|
140
|
+
const params = match[3].trim()
|
|
141
|
+
? match[3].split(',').map(p => p.trim())
|
|
142
|
+
.filter(p => p && p !== 'self' && p !== 'cls')
|
|
143
|
+
: [];
|
|
95
144
|
|
|
96
145
|
// Calculate function length by finding next line at same or lesser indent
|
|
97
146
|
const funcLines = lines.slice(lineNum); // lines after def
|
|
@@ -120,25 +169,53 @@ function analyzePythonFile(filePath) {
|
|
|
120
169
|
|
|
121
170
|
// Check function length
|
|
122
171
|
if (length > MAX_FUNCTION_LENGTH) {
|
|
123
|
-
issues.push({
|
|
172
|
+
issues.push({
|
|
173
|
+
severity: 'warning', category: '复杂度',
|
|
174
|
+
message: `函数 '${name}' 过长 (${length} 行 > ${MAX_FUNCTION_LENGTH})`,
|
|
175
|
+
file_path: filePath, line_number: lineNum,
|
|
176
|
+
suggestion: '考虑拆分为多个小函数',
|
|
177
|
+
});
|
|
124
178
|
}
|
|
125
179
|
// Check complexity
|
|
126
180
|
if (complexity > MAX_COMPLEXITY) {
|
|
127
|
-
issues.push({
|
|
181
|
+
issues.push({
|
|
182
|
+
severity: 'warning', category: '复杂度',
|
|
183
|
+
message: `函数 '${name}' 圈复杂度过高 (${complexity} > ${MAX_COMPLEXITY})`,
|
|
184
|
+
file_path: filePath, line_number: lineNum,
|
|
185
|
+
suggestion: '减少嵌套层级,提取子函数',
|
|
186
|
+
});
|
|
128
187
|
}
|
|
129
188
|
// Check parameter count
|
|
130
189
|
if (params.length > MAX_PARAMETERS) {
|
|
131
|
-
issues.push({
|
|
190
|
+
issues.push({
|
|
191
|
+
severity: 'warning', category: '设计',
|
|
192
|
+
message: `函数 '${name}' 参数过多 (${params.length} > ${MAX_PARAMETERS})`,
|
|
193
|
+
file_path: filePath, line_number: lineNum,
|
|
194
|
+
suggestion: '考虑使用配置对象或数据类封装参数',
|
|
195
|
+
});
|
|
132
196
|
}
|
|
133
197
|
// Check naming
|
|
134
|
-
const SPECIAL = new Set([
|
|
198
|
+
const SPECIAL = new Set([
|
|
199
|
+
'setUp', 'tearDown', 'setUpClass',
|
|
200
|
+
'tearDownClass', 'setUpModule', 'tearDownModule',
|
|
201
|
+
]);
|
|
135
202
|
if (!name.startsWith('_') && !SPECIAL.has(name) && !name.startsWith('visit_')) {
|
|
136
203
|
if (!/^[a-z][a-z0-9_]*$/.test(name)) {
|
|
137
|
-
issues.push({
|
|
204
|
+
issues.push({
|
|
205
|
+
severity: 'info', category: '命名',
|
|
206
|
+
message: `函数名 '${name}' 不符合 snake_case 规范`,
|
|
207
|
+
file_path: filePath, line_number: lineNum,
|
|
208
|
+
suggestion: '函数名应使用 snake_case',
|
|
209
|
+
});
|
|
138
210
|
}
|
|
139
211
|
}
|
|
140
212
|
if (name.length < MIN_FUNCTION_NAME_LENGTH) {
|
|
141
|
-
issues.push({
|
|
213
|
+
issues.push({
|
|
214
|
+
severity: 'warning', category: '命名',
|
|
215
|
+
message: `函数名 '${name}' 过短`,
|
|
216
|
+
file_path: filePath, line_number: lineNum,
|
|
217
|
+
suggestion: '使用更具描述性的函数名',
|
|
218
|
+
});
|
|
142
219
|
}
|
|
143
220
|
}
|
|
144
221
|
|
|
@@ -147,7 +224,12 @@ function analyzePythonFile(filePath) {
|
|
|
147
224
|
const name = match[2];
|
|
148
225
|
metrics.classes++;
|
|
149
226
|
if (!/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
|
|
150
|
-
issues.push({
|
|
227
|
+
issues.push({
|
|
228
|
+
severity: 'warning', category: '命名',
|
|
229
|
+
message: `类名 '${name}' 不符合 PascalCase 规范`,
|
|
230
|
+
file_path: filePath, line_number: lineNum,
|
|
231
|
+
suggestion: '类名应使用 PascalCase,如 MyClassName',
|
|
232
|
+
});
|
|
151
233
|
}
|
|
152
234
|
}
|
|
153
235
|
|
|
@@ -164,7 +246,11 @@ function analyzePythonFile(filePath) {
|
|
|
164
246
|
function scanDirectory(scanPath, excludeDirs) {
|
|
165
247
|
const resolved = path.resolve(scanPath);
|
|
166
248
|
const exclude = excludeDirs || EXCLUDE_DIRS;
|
|
167
|
-
const result = {
|
|
249
|
+
const result = {
|
|
250
|
+
scan_path: resolved, files_scanned: 0,
|
|
251
|
+
total_lines: 0, total_code_lines: 0,
|
|
252
|
+
issues: [], file_metrics: [],
|
|
253
|
+
};
|
|
168
254
|
|
|
169
255
|
function walk(dir) {
|
|
170
256
|
let entries;
|
|
@@ -191,83 +277,58 @@ function scanDirectory(scanPath, excludeDirs) {
|
|
|
191
277
|
|
|
192
278
|
// --- Reporting ---
|
|
193
279
|
|
|
194
|
-
function
|
|
195
|
-
function warningCount(result) { return result.issues.filter(i => i.severity === 'warning').length; }
|
|
196
|
-
function passed(result) { return !result.issues.some(i => i.severity === 'error'); }
|
|
280
|
+
function passed(result) { return !hasFatal(result.issues); }
|
|
197
281
|
|
|
198
282
|
function formatReport(result, verbose) {
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const byCategory = {};
|
|
213
|
-
for (const issue of result.issues) {
|
|
214
|
-
(byCategory[issue.category] || (byCategory[issue.category] = [])).push(issue);
|
|
215
|
-
}
|
|
216
|
-
const icons = { error: '✗', warning: '⚠', info: 'ℹ' };
|
|
217
|
-
for (const cat of Object.keys(byCategory).sort()) {
|
|
218
|
-
const catIssues = byCategory[cat];
|
|
219
|
-
lines.push(`\n【${cat}】(${catIssues.length} 个)`);
|
|
220
|
-
for (const issue of catIssues.slice(0, 10)) {
|
|
221
|
-
const icon = icons[issue.severity];
|
|
222
|
-
const loc = issue.line_number ? `:${issue.line_number}` : '';
|
|
223
|
-
lines.push(` ${icon} ${issue.file_path}${loc}`);
|
|
224
|
-
lines.push(` ${issue.message}`);
|
|
225
|
-
if (verbose && issue.suggestion) lines.push(` 💡 ${issue.suggestion}`);
|
|
226
|
-
}
|
|
227
|
-
if (catIssues.length > 10) lines.push(` ... 及其他 ${catIssues.length - 10} 个问题`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
283
|
+
const errs = result.issues.filter(i => i.severity === 'error').length;
|
|
284
|
+
const warns = result.issues.filter(i => i.severity === 'warning').length;
|
|
285
|
+
const fields = {
|
|
286
|
+
'扫描路径': result.scan_path,
|
|
287
|
+
'扫描文件': result.files_scanned,
|
|
288
|
+
'总行数': result.total_lines,
|
|
289
|
+
'代码行数': result.total_code_lines,
|
|
290
|
+
'检查结果': passed(result) ? '✓ 通过' : '✗ 需要关注',
|
|
291
|
+
'统计': `错误: ${errs} | 警告: ${warns}`,
|
|
292
|
+
};
|
|
293
|
+
let report = buildReport(
|
|
294
|
+
'代码质量检查报告', fields, result.issues, verbose, 'category'
|
|
295
|
+
);
|
|
230
296
|
|
|
231
297
|
if (verbose && result.file_metrics.length) {
|
|
232
|
-
const complex = result.file_metrics
|
|
298
|
+
const complex = result.file_metrics
|
|
299
|
+
.filter(m => m.max_complexity > 0)
|
|
300
|
+
.sort((a, b) => b.max_complexity - a.max_complexity)
|
|
301
|
+
.slice(0, 5);
|
|
233
302
|
if (complex.length) {
|
|
234
|
-
lines
|
|
303
|
+
const lines = ['\n' + '-'.repeat(40), '复杂度最高的文件:', '-'.repeat(40)];
|
|
235
304
|
for (const m of complex) lines.push(` ${m.path}: 复杂度 ${m.max_complexity}, ${m.functions} 个函数`);
|
|
305
|
+
report += '\n' + lines.join('\n');
|
|
236
306
|
}
|
|
237
307
|
}
|
|
238
|
-
|
|
239
|
-
lines.push('\n' + sep);
|
|
240
|
-
return lines.join('\n');
|
|
308
|
+
return report;
|
|
241
309
|
}
|
|
242
310
|
|
|
243
311
|
// --- CLI ---
|
|
244
312
|
|
|
245
313
|
function main() {
|
|
246
|
-
const
|
|
247
|
-
let scanPath = '.', verbose = false, jsonOutput = false;
|
|
248
|
-
|
|
249
|
-
for (let i = 0; i < args.length; i++) {
|
|
250
|
-
if (args[i] === '-v' || args[i] === '--verbose') verbose = true;
|
|
251
|
-
else if (args[i] === '--json') jsonOutput = true;
|
|
252
|
-
else if (!args[i].startsWith('-')) scanPath = args[i];
|
|
253
|
-
}
|
|
314
|
+
const opts = parseCliArgs(process.argv);
|
|
254
315
|
|
|
255
|
-
const result = scanDirectory(
|
|
316
|
+
const result = scanDirectory(opts.target);
|
|
256
317
|
|
|
257
|
-
if (
|
|
318
|
+
if (opts.json) {
|
|
258
319
|
const output = {
|
|
259
320
|
scan_path: result.scan_path,
|
|
260
321
|
files_scanned: result.files_scanned,
|
|
261
322
|
total_lines: result.total_lines,
|
|
262
323
|
total_code_lines: result.total_code_lines,
|
|
263
324
|
passed: passed(result),
|
|
264
|
-
error_count:
|
|
265
|
-
warning_count:
|
|
325
|
+
error_count: result.issues.filter(i => i.severity === 'error').length,
|
|
326
|
+
warning_count: result.issues.filter(i => i.severity === 'warning').length,
|
|
266
327
|
issues: result.issues
|
|
267
328
|
};
|
|
268
329
|
console.log(JSON.stringify(output, null, 2));
|
|
269
330
|
} else {
|
|
270
|
-
console.log(formatReport(result, verbose));
|
|
331
|
+
console.log(formatReport(result, opts.verbose));
|
|
271
332
|
}
|
|
272
333
|
|
|
273
334
|
process.exit(passed(result) ? 0 : 1);
|