code-abyss 1.6.16 → 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.
Files changed (97) hide show
  1. package/README.md +8 -6
  2. package/bin/install.js +59 -163
  3. package/bin/lib/ccline.js +82 -0
  4. package/bin/lib/utils.js +61 -0
  5. package/package.json +5 -2
  6. package/skills/SKILL.md +24 -16
  7. package/skills/domains/ai/SKILL.md +2 -2
  8. package/skills/domains/ai/prompt-and-eval.md +279 -0
  9. package/skills/domains/architecture/SKILL.md +2 -3
  10. package/skills/domains/architecture/security-arch.md +87 -0
  11. package/skills/domains/data-engineering/SKILL.md +188 -26
  12. package/skills/domains/development/SKILL.md +1 -4
  13. package/skills/domains/devops/SKILL.md +3 -5
  14. package/skills/domains/devops/performance.md +63 -0
  15. package/skills/domains/devops/testing.md +97 -0
  16. package/skills/domains/frontend-design/SKILL.md +12 -3
  17. package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
  18. package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  19. package/skills/domains/frontend-design/engineering.md +287 -0
  20. package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
  21. package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  22. package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
  23. package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  24. package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
  25. package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  26. package/skills/domains/infrastructure/SKILL.md +174 -34
  27. package/skills/domains/mobile/SKILL.md +211 -21
  28. package/skills/domains/orchestration/SKILL.md +1 -0
  29. package/skills/domains/security/SKILL.md +4 -6
  30. package/skills/domains/security/blue-team.md +57 -0
  31. package/skills/domains/security/red-team.md +54 -0
  32. package/skills/domains/security/threat-intel.md +50 -0
  33. package/skills/orchestration/multi-agent/SKILL.md +195 -46
  34. package/skills/run_skill.js +139 -0
  35. package/skills/tools/gen-docs/SKILL.md +6 -4
  36. package/skills/tools/gen-docs/scripts/doc_generator.js +363 -0
  37. package/skills/tools/lib/shared.js +98 -0
  38. package/skills/tools/verify-change/SKILL.md +8 -6
  39. package/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
  40. package/skills/tools/verify-module/SKILL.md +6 -4
  41. package/skills/tools/verify-module/scripts/module_scanner.js +171 -0
  42. package/skills/tools/verify-quality/SKILL.md +5 -3
  43. package/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
  44. package/skills/tools/verify-security/SKILL.md +7 -5
  45. package/skills/tools/verify-security/scripts/security_scanner.js +283 -0
  46. package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
  47. package/skills/domains/COVERAGE_PLAN.md +0 -232
  48. package/skills/domains/ai/model-evaluation.md +0 -790
  49. package/skills/domains/ai/prompt-engineering.md +0 -703
  50. package/skills/domains/architecture/compliance.md +0 -299
  51. package/skills/domains/architecture/data-security.md +0 -184
  52. package/skills/domains/data-engineering/data-pipeline.md +0 -762
  53. package/skills/domains/data-engineering/data-quality.md +0 -894
  54. package/skills/domains/data-engineering/stream-processing.md +0 -791
  55. package/skills/domains/development/dart.md +0 -963
  56. package/skills/domains/development/kotlin.md +0 -834
  57. package/skills/domains/development/php.md +0 -659
  58. package/skills/domains/development/swift.md +0 -755
  59. package/skills/domains/devops/e2e-testing.md +0 -914
  60. package/skills/domains/devops/performance-testing.md +0 -734
  61. package/skills/domains/devops/testing-strategy.md +0 -667
  62. package/skills/domains/frontend-design/build-tools.md +0 -743
  63. package/skills/domains/frontend-design/performance.md +0 -734
  64. package/skills/domains/frontend-design/testing.md +0 -699
  65. package/skills/domains/infrastructure/gitops.md +0 -735
  66. package/skills/domains/infrastructure/iac.md +0 -855
  67. package/skills/domains/infrastructure/kubernetes.md +0 -1018
  68. package/skills/domains/mobile/android-dev.md +0 -979
  69. package/skills/domains/mobile/cross-platform.md +0 -795
  70. package/skills/domains/mobile/ios-dev.md +0 -931
  71. package/skills/domains/security/secrets-management.md +0 -834
  72. package/skills/domains/security/supply-chain.md +0 -931
  73. package/skills/domains/security/threat-modeling.md +0 -828
  74. package/skills/run_skill.py +0 -153
  75. package/skills/tests/README.md +0 -225
  76. package/skills/tests/SUMMARY.md +0 -362
  77. package/skills/tests/__init__.py +0 -3
  78. package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
  79. package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
  80. package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
  81. package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
  82. package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
  83. package/skills/tests/test_change_analyzer.py +0 -558
  84. package/skills/tests/test_doc_generator.py +0 -538
  85. package/skills/tests/test_module_scanner.py +0 -376
  86. package/skills/tests/test_quality_checker.py +0 -516
  87. package/skills/tests/test_security_scanner.py +0 -426
  88. package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
  89. package/skills/tools/gen-docs/scripts/doc_generator.py +0 -520
  90. package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
  91. package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
  92. package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
  93. package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
  94. package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
  95. package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
  96. package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
  97. package/skills/tools/verify-security/scripts/security_scanner.py +0 -374
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { execSync } = require("child_process");
5
+ const path = require("path");
6
+ const fs = require("fs");
7
+ const { parseCliArgs, buildReport, hasFatal, DASH } = require(path.join(__dirname, '..', '..', 'lib', 'shared.js'));
8
+
9
+ const CODE_EXT = new Set([".py",".go",".rs",".ts",".js",".jsx",".tsx",".java",".c",".cpp",".h",".hpp"]);
10
+ const DOC_EXT = new Set([".md",".rst",".txt",".adoc"]);
11
+ const TEST_PATTERNS = ["test_","_test.",".test.","spec_","_spec.","/tests/","/test/","/__tests__/"];
12
+ const CONFIG_FILES = new Set(["package.json","pyproject.toml","go.mod","cargo.toml","pom.xml","makefile","dockerfile"]);
13
+ const CONFIG_EXT = new Set([".yaml",".yml",".json",".toml",".ini"]);
14
+
15
+ function normalizePath(p) {
16
+ let s = p.trim();
17
+ if (s.startsWith('"') && s.endsWith('"') && s.length >= 2) {
18
+ s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
19
+ }
20
+ if (s.startsWith("./")) s = s.slice(2);
21
+ return s;
22
+ }
23
+
24
+ function classifyFile(filePath) {
25
+ const ext = path.extname(filePath).toLowerCase();
26
+ const name = path.basename(filePath).toLowerCase();
27
+ const lower = filePath.toLowerCase();
28
+ return {
29
+ path: filePath, type: "modified", additions: 0, deletions: 0,
30
+ is_code: CODE_EXT.has(ext), is_doc: DOC_EXT.has(ext),
31
+ is_test: TEST_PATTERNS.some(p => lower.includes(p)),
32
+ is_config: CONFIG_FILES.has(name) || CONFIG_EXT.has(ext),
33
+ };
34
+ }
35
+
36
+ function parseNameStatusLine(line) {
37
+ const parts = line.split("\t");
38
+ if (parts.length < 2) return null;
39
+ const status = parts[0][0];
40
+ const p = normalizePath(parts[parts.length - 1]);
41
+ if (!p) return null;
42
+ const c = classifyFile(p);
43
+ const map = { A: "added", M: "modified", D: "deleted", R: "renamed" };
44
+ if (map[status]) c.type = map[status];
45
+ return c;
46
+ }
47
+
48
+ function parsePorcelainLine(line) {
49
+ if (line.length < 3) return null;
50
+ const status = line.slice(0, 2);
51
+ let raw = line.slice(3);
52
+ if (!raw) return null;
53
+ if (raw.includes(" -> ")) raw = raw.split(" -> ")[1];
54
+ const p = normalizePath(raw);
55
+ if (!p) return null;
56
+ const c = classifyFile(p);
57
+ if (status.includes("?") || status.includes("A")) c.type = "added";
58
+ else if (status.includes("R")) c.type = "renamed";
59
+ else if (status.includes("M")) c.type = "modified";
60
+ else if (status.includes("D")) c.type = "deleted";
61
+ return c;
62
+ }
63
+
64
+ function git(args) {
65
+ try { return execSync('git ' + args, { encoding: "utf8", stdio: ["pipe","pipe","pipe"] }); }
66
+ catch { return ""; }
67
+ }
68
+
69
+ function getGitChanges(base = "HEAD~1", target = "HEAD") {
70
+ const changes = [];
71
+ for (const line of git(`diff --name-status ${base} ${target}`).split("\n")) {
72
+ if (!line) continue;
73
+ const c = parseNameStatusLine(line);
74
+ if (c) changes.push(c);
75
+ }
76
+ const statMap = {};
77
+ for (const line of git(`diff --numstat ${base} ${target}`).split("\n")) {
78
+ if (!line) continue;
79
+ const parts = line.split("\t");
80
+ if (parts.length >= 3) {
81
+ statMap[normalizePath(parts[2])] = [
82
+ parts[0] === "-" ? 0 : parseInt(parts[0], 10),
83
+ parts[1] === "-" ? 0 : parseInt(parts[1], 10),
84
+ ];
85
+ }
86
+ }
87
+ for (const c of changes) {
88
+ if (statMap[c.path]) { [c.additions, c.deletions] = statMap[c.path]; }
89
+ }
90
+ return changes;
91
+ }
92
+
93
+ function getStagedChanges() {
94
+ const changes = [];
95
+ for (const line of git("diff --cached --name-status").split("\n")) {
96
+ if (!line) continue;
97
+ const c = parseNameStatusLine(line);
98
+ if (c) changes.push(c);
99
+ }
100
+ return changes;
101
+ }
102
+
103
+ function getWorkingChanges() {
104
+ const changes = [];
105
+ for (const line of git("status --porcelain").split("\n")) {
106
+ if (!line) continue;
107
+ const c = parsePorcelainLine(line);
108
+ if (c) changes.push(c);
109
+ }
110
+ return changes;
111
+ }
112
+
113
+ function isPathInModule(filePath, mod) {
114
+ const np = normalizePath(filePath);
115
+ if (mod === ".") return !np.includes("/");
116
+ return np === mod || np.startsWith(mod + "/");
117
+ }
118
+
119
+ function identifyModules(changes) {
120
+ const modules = new Set();
121
+ for (const c of changes) {
122
+ const np = normalizePath(c.path);
123
+ const parts = np.split("/").filter(Boolean);
124
+ if (parts.length === 1) { modules.add("."); continue; }
125
+ let found = false;
126
+ for (let i = 0; i < parts.length; i++) {
127
+ const mp = parts.slice(0, i + 1).join("/");
128
+ if (fs.existsSync(path.join(mp, "README.md")) || fs.existsSync(path.join(mp, "DESIGN.md"))) {
129
+ modules.add(mp); found = true; break;
130
+ }
131
+ }
132
+ if (!found && parts.length > 1) modules.add(parts[0]);
133
+ }
134
+ return modules;
135
+ }
136
+
137
+ function checkDocSync(changes, modules) {
138
+ const docStatus = {};
139
+ const issues = [];
140
+ const codeChanges = changes.filter(c => c.is_code && c.type !== "deleted");
141
+ const docPaths = new Set(changes.filter(c => c.is_doc).map(c => normalizePath(c.path)));
142
+
143
+ for (const mod of modules) {
144
+ const readme = normalizePath(mod === "." ? "README.md" : `${mod}/README.md`);
145
+ const design = normalizePath(mod === "." ? "DESIGN.md" : `${mod}/DESIGN.md`);
146
+ const modCode = codeChanges.filter(c => isPathInModule(c.path, mod));
147
+ if (!modCode.length) continue;
148
+ const total = modCode.reduce((s, c) => s + c.additions + c.deletions, 0);
149
+ if (total > 50 && !docPaths.has(design)) {
150
+ issues.push({
151
+ severity: "warning",
152
+ message: `模块 ${mod} 有较大代码变更 (${total} 行),但 DESIGN.md 未更新`,
153
+ related_files: modCode.map(c => c.path)
154
+ });
155
+ docStatus[`${mod}/DESIGN.md`] = false;
156
+ } else {
157
+ docStatus[`${mod}/DESIGN.md`] = true;
158
+ }
159
+ const newFiles = modCode.filter(c => c.type === "added");
160
+ if (newFiles.length && !docPaths.has(readme)) {
161
+ issues.push({
162
+ severity: "info",
163
+ message: `模块 ${mod} 新增了文件,建议更新 README.md`,
164
+ related_files: newFiles.map(c => c.path)
165
+ });
166
+ }
167
+ }
168
+ return { docStatus, issues };
169
+ }
170
+
171
+ function analyzeImpact(changes) {
172
+ const issues = [];
173
+ const code = changes.filter(c => c.is_code && !c.is_test);
174
+ const tests = changes.filter(c => c.is_test);
175
+ if (code.length && !tests.length) {
176
+ const total = code.reduce((s, c) => s + c.additions + c.deletions, 0);
177
+ if (total > 30) {
178
+ issues.push({
179
+ severity: "warning",
180
+ message: `代码变更 ${total} 行,但没有对应的测试更新`,
181
+ related_files: code.map(c => c.path)
182
+ });
183
+ }
184
+ }
185
+ const configs = changes.filter(c => c.is_config);
186
+ if (configs.length) {
187
+ issues.push({
188
+ severity: "info",
189
+ message: "配置文件有变更,请确认是否需要更新文档",
190
+ related_files: configs.map(c => c.path)
191
+ });
192
+ }
193
+ const deleted = changes.filter(c => c.type === "deleted");
194
+ if (deleted.length) {
195
+ issues.push({
196
+ severity: "info",
197
+ message: `删除了 ${deleted.length} 个文件,请确认相关引用已清理`,
198
+ related_files: deleted.map(c => c.path)
199
+ });
200
+ }
201
+ return issues;
202
+ }
203
+
204
+ function analyzeChanges(mode = "working") {
205
+ let changes;
206
+ if (mode === "staged") changes = getStagedChanges();
207
+ else if (mode === "committed") changes = getGitChanges();
208
+ else changes = getWorkingChanges();
209
+
210
+ const issues = [];
211
+ let modules = new Set(), docStatus = {};
212
+ if (changes.length) {
213
+ modules = identifyModules(changes);
214
+ const ds = checkDocSync(changes, modules);
215
+ docStatus = ds.docStatus;
216
+ issues.push(...ds.issues, ...analyzeImpact(changes));
217
+ }
218
+ const passed = !issues.some(i => i.severity === "error");
219
+ const totalAdd = changes.reduce((s, c) => s + c.additions, 0);
220
+ const totalDel = changes.reduce((s, c) => s + c.deletions, 0);
221
+ return { changes, issues, modules, docStatus, passed, totalAdd, totalDel };
222
+ }
223
+
224
+ function formatReport(r, verbose) {
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);
233
+
234
+ if (r.changes.length && verbose) {
235
+ const lines = ["\n" + DASH, "变更文件列表:", DASH];
236
+ const icons = { added: "➕", modified: "📝", deleted: "➖", renamed: "📋" };
237
+ for (const c of r.changes) {
238
+ const tags = [];
239
+ if (c.is_code) tags.push("代码");
240
+ if (c.is_doc) tags.push("文档");
241
+ if (c.is_test) tags.push("测试");
242
+ if (c.is_config) tags.push("配置");
243
+ const t = tags.length ? ` [${tags.join(", ")}]` : "";
244
+ lines.push(` ${icons[c.type] || "📝"} ${c.path}${t} (+${c.additions}/-${c.deletions})`);
245
+ }
246
+ report += '\n' + lines.join('\n');
247
+ }
248
+
249
+ if (Object.keys(r.docStatus).length) {
250
+ const lines = ["\n" + DASH, "文档同步状态:", DASH];
251
+ for (const [doc, synced] of Object.entries(r.docStatus)) {
252
+ lines.push(` ${synced ? "✓" : "✗"} ${doc}`);
253
+ }
254
+ report += '\n' + lines.join('\n');
255
+ }
256
+
257
+ return report;
258
+ }
259
+
260
+ // CLI
261
+ if (require.main === module) {
262
+ const opts = parseCliArgs(process.argv);
263
+ const result = analyzeChanges(opts.mode || "working");
264
+
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
+ }
285
+
286
+ process.exit(result.passed ? 0 : 1);
287
+ }
288
+
289
+ module.exports = { normalizePath, classifyFile, parsePorcelainLine, parseNameStatusLine, identifyModules };
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: verify-module
3
3
  description: 模块完整性校验关卡。扫描目录结构、检测缺失文档、验证代码与文档同步。当魔尊提到模块校验、文档检查、结构完整性、README检查、DESIGN检查时使用。在新建模块完成时自动触发。
4
+ license: MIT
5
+ compatibility: node>=18
4
6
  user-invocable: true
5
7
  disable-model-invocation: false
6
8
  allowed-tools: Bash, Read, Glob
@@ -23,9 +25,9 @@ argument-hint: <模块路径>
23
25
 
24
26
  ```bash
25
27
  # 在 verify-module 目录下运行(推荐)
26
- python scripts/module_scanner.py <模块路径>
27
- python scripts/module_scanner.py <模块路径> -v # 详细模式
28
- python scripts/module_scanner.py <模块路径> --json # JSON 输出
28
+ node scripts/module_scanner.js <模块路径>
29
+ node scripts/module_scanner.js <模块路径> -v # 详细模式
30
+ node scripts/module_scanner.js <模块路径> --json # JSON 输出
29
31
  ```
30
32
 
31
33
  ## 校验标准
@@ -84,7 +86,7 @@ module/
84
86
  ## 校验流程
85
87
 
86
88
  ```
87
- 1. 运行 module_scanner.py 自动扫描
89
+ 1. 运行 module_scanner.js 自动扫描
88
90
  2. 检查文件结构是否完整
89
91
  3. 检查 README.md 各项是否齐全
90
92
  4. 检查 DESIGN.md 各项是否齐全
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { parseCliArgs, buildReport, hasFatal } = require(path.join(__dirname, '..', '..', 'lib', 'shared.js'));
7
+
8
+ const REQUIRED_FILES = { 'README.md': '模块说明文档', 'DESIGN.md': '设计决策文档' };
9
+ const ALT_SRC_DIRS = ['src', 'lib', 'pkg', 'internal', 'cmd', 'app'];
10
+ const ALT_TEST_DIRS = ['tests', 'test', '__tests__', 'spec'];
11
+ const ROOT_SCRIPT_FILES = new Set([
12
+ 'install.sh', 'uninstall.sh', 'install.ps1',
13
+ 'uninstall.ps1', 'Dockerfile', 'Makefile'
14
+ ]);
15
+ const CODE_EXTS = new Set(['.py', '.go', '.rs', '.ts', '.js', '.java', '.sh', '.ps1']);
16
+ const TEST_PATTERNS = ['test_', '_test.', '.test.', 'spec_', '_spec.'];
17
+
18
+ function scanStructure(p, depth = 3) {
19
+ const s = { name: path.basename(p), type: 'dir', children: [] };
20
+ if (depth <= 0) return s;
21
+ try {
22
+ for (const name of fs.readdirSync(p).sort()) {
23
+ if (name.startsWith('.')) continue;
24
+ const full = path.join(p, name);
25
+ const stat = fs.statSync(full);
26
+ if (stat.isFile()) s.children.push({ name, type: 'file', size: stat.size });
27
+ else if (stat.isDirectory()) s.children.push(scanStructure(full, depth - 1));
28
+ }
29
+ } catch {}
30
+ return s;
31
+ }
32
+
33
+ function rglob(dir, test) {
34
+ try {
35
+ for (const name of fs.readdirSync(dir)) {
36
+ const full = path.join(dir, name);
37
+ try {
38
+ const stat = fs.statSync(full);
39
+ if (stat.isFile() && test(name)) return true;
40
+ if (stat.isDirectory()) { if (rglob(full, test)) return true; }
41
+ } catch {}
42
+ }
43
+ } catch {}
44
+ return false;
45
+ }
46
+
47
+ function scanModule(target) {
48
+ const modulePath = path.resolve(target);
49
+ const issues = [];
50
+ const add = (severity, message, p) => issues.push({ severity, message, path: p || null });
51
+
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
+ }
60
+
61
+ const structure = scanStructure(modulePath);
62
+
63
+ // required files
64
+ for (const [file, desc] of Object.entries(REQUIRED_FILES)) {
65
+ const fp = path.join(modulePath, file);
66
+ if (!fs.existsSync(fp)) add('error', `缺少必需文档: ${file} (${desc})`, fp);
67
+ else if (fs.statSync(fp).size < 50) add('warning', `文档内容过少: ${file} (< 50 bytes)`, fp);
68
+ }
69
+
70
+ // source dirs
71
+ let srcFound = ALT_SRC_DIRS.some(d => {
72
+ try { return fs.statSync(path.join(modulePath, d)).isDirectory(); }
73
+ catch { return false; }
74
+ });
75
+ const entries = fs.readdirSync(modulePath);
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
+ }
94
+ if (!srcFound) add('warning', '未找到源码目录或代码文件');
95
+
96
+ // test dirs
97
+ let testFound = ALT_TEST_DIRS.some(d => {
98
+ try { return fs.statSync(path.join(modulePath, d)).isDirectory(); }
99
+ catch { return false; }
100
+ });
101
+ if (!testFound) testFound = rglob(modulePath, n => TEST_PATTERNS.some(p => n.includes(p)));
102
+ if (!testFound) add('warning', '未找到测试目录或测试文件');
103
+
104
+ // doc quality
105
+ const readme = path.join(modulePath, 'README.md');
106
+ if (fs.existsSync(readme)) {
107
+ const c = fs.readFileSync(readme, 'utf-8');
108
+ if (!c.includes('#')) add('warning', 'README.md 缺少标题', readme);
109
+ const docKeys = ['usage', 'install', '使用', '安装', 'example', '示例'];
110
+ if (!docKeys.some(k => c.toLowerCase().includes(k)))
111
+ add('info', 'README.md 建议添加使用说明或示例', readme);
112
+ }
113
+ const design = path.join(modulePath, 'DESIGN.md');
114
+ if (fs.existsSync(design)) {
115
+ const c = fs.readFileSync(design, 'utf-8');
116
+ const designKeys = ['决策', 'decision', '选择', 'choice', '权衡', 'trade'];
117
+ if (!designKeys.some(k => c.toLowerCase().includes(k)))
118
+ add('info', 'DESIGN.md 建议记录设计决策和权衡', design);
119
+ }
120
+
121
+ return { modulePath, issues, structure };
122
+ }
123
+
124
+ function formatStructure(s, indent = 0) {
125
+ const pre = ' '.repeat(indent);
126
+ if (s.type === 'dir') {
127
+ const lines = [`${pre}\u{1F4C1} ${s.name}/`];
128
+ for (const ch of (s.children || [])) lines.push(formatStructure(ch, indent + 1));
129
+ return lines.join('\n');
130
+ }
131
+ const sz = (s.size || 0) < 1024 ? `(${s.size} B)` : `(${Math.floor(s.size / 1024)} KB)`;
132
+ return `${pre}\u{1F4C4} ${s.name} ${sz}`;
133
+ }
134
+
135
+ function formatReport(r, verbose) {
136
+ const errs = r.issues.filter(i => i.severity === 'error').length;
137
+ const warns = r.issues.filter(i => i.severity === 'warning').length;
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);
149
+ if (verbose && r.structure.name) {
150
+ report += '\n' + '-'.repeat(40) + '\n目录结构:\n' + '-'.repeat(40) + '\n' + formatStructure(r.structure);
151
+ }
152
+ return report;
153
+ }
154
+
155
+ // CLI
156
+ const opts = parseCliArgs(process.argv);
157
+ const result = scanModule(opts.target);
158
+ const passed = !hasFatal(result.issues);
159
+
160
+ if (opts.json) {
161
+ console.log(JSON.stringify({
162
+ module_path: result.modulePath, passed,
163
+ error_count: result.issues.filter(i => i.severity === 'error').length,
164
+ warning_count: result.issues.filter(i => i.severity === 'warning').length,
165
+ issues: result.issues
166
+ }, null, 2));
167
+ } else {
168
+ console.log(formatReport(result, opts.verbose));
169
+ }
170
+
171
+ process.exit(passed ? 0 : 1);
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: verify-quality
3
3
  description: 代码质量校验关卡。检测复杂度、重复代码、命名规范、函数长度等质量指标。当魔尊提到代码质量、复杂度检查、代码异味、重构建议、lint检查、代码规范时使用。在复杂模块、重构完成时自动触发。
4
+ license: MIT
5
+ compatibility: node>=18
4
6
  user-invocable: true
5
7
  disable-model-invocation: false
6
8
  allowed-tools: Bash, Read, Glob
@@ -24,9 +26,9 @@ argument-hint: <扫描路径>
24
26
 
25
27
  ```bash
26
28
  # 在 skill 目录下运行
27
- python scripts/quality_checker.py <扫描路径>
28
- python scripts/quality_checker.py <扫描路径> -v # 详细模式
29
- python scripts/quality_checker.py <扫描路径> --json # JSON 输出
29
+ node scripts/quality_checker.js <扫描路径>
30
+ node scripts/quality_checker.js <扫描路径> -v # 详细模式
31
+ node scripts/quality_checker.js <扫描路径> --json # JSON 输出
30
32
  ```
31
33
 
32
34
  ## 检测指标