code-abyss 1.6.16 → 1.7.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.
Files changed (92) hide show
  1. package/package.json +2 -2
  2. package/skills/SKILL.md +24 -16
  3. package/skills/domains/ai/SKILL.md +2 -2
  4. package/skills/domains/ai/prompt-and-eval.md +279 -0
  5. package/skills/domains/architecture/SKILL.md +2 -3
  6. package/skills/domains/architecture/security-arch.md +87 -0
  7. package/skills/domains/data-engineering/SKILL.md +188 -26
  8. package/skills/domains/development/SKILL.md +1 -4
  9. package/skills/domains/devops/SKILL.md +3 -5
  10. package/skills/domains/devops/performance.md +63 -0
  11. package/skills/domains/devops/testing.md +97 -0
  12. package/skills/domains/frontend-design/SKILL.md +12 -3
  13. package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
  14. package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  15. package/skills/domains/frontend-design/engineering.md +287 -0
  16. package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
  17. package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  18. package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
  19. package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  20. package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
  21. package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  22. package/skills/domains/infrastructure/SKILL.md +174 -34
  23. package/skills/domains/mobile/SKILL.md +211 -21
  24. package/skills/domains/orchestration/SKILL.md +1 -0
  25. package/skills/domains/security/SKILL.md +4 -6
  26. package/skills/domains/security/blue-team.md +57 -0
  27. package/skills/domains/security/red-team.md +54 -0
  28. package/skills/domains/security/threat-intel.md +50 -0
  29. package/skills/orchestration/multi-agent/SKILL.md +195 -46
  30. package/skills/run_skill.js +134 -0
  31. package/skills/tools/gen-docs/SKILL.md +6 -4
  32. package/skills/tools/gen-docs/scripts/doc_generator.js +349 -0
  33. package/skills/tools/verify-change/SKILL.md +8 -6
  34. package/skills/tools/verify-change/scripts/change_analyzer.js +270 -0
  35. package/skills/tools/verify-module/SKILL.md +6 -4
  36. package/skills/tools/verify-module/scripts/module_scanner.js +145 -0
  37. package/skills/tools/verify-quality/SKILL.md +5 -3
  38. package/skills/tools/verify-quality/scripts/quality_checker.js +276 -0
  39. package/skills/tools/verify-security/SKILL.md +7 -5
  40. package/skills/tools/verify-security/scripts/security_scanner.js +133 -0
  41. package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
  42. package/skills/domains/COVERAGE_PLAN.md +0 -232
  43. package/skills/domains/ai/model-evaluation.md +0 -790
  44. package/skills/domains/ai/prompt-engineering.md +0 -703
  45. package/skills/domains/architecture/compliance.md +0 -299
  46. package/skills/domains/architecture/data-security.md +0 -184
  47. package/skills/domains/data-engineering/data-pipeline.md +0 -762
  48. package/skills/domains/data-engineering/data-quality.md +0 -894
  49. package/skills/domains/data-engineering/stream-processing.md +0 -791
  50. package/skills/domains/development/dart.md +0 -963
  51. package/skills/domains/development/kotlin.md +0 -834
  52. package/skills/domains/development/php.md +0 -659
  53. package/skills/domains/development/swift.md +0 -755
  54. package/skills/domains/devops/e2e-testing.md +0 -914
  55. package/skills/domains/devops/performance-testing.md +0 -734
  56. package/skills/domains/devops/testing-strategy.md +0 -667
  57. package/skills/domains/frontend-design/build-tools.md +0 -743
  58. package/skills/domains/frontend-design/performance.md +0 -734
  59. package/skills/domains/frontend-design/testing.md +0 -699
  60. package/skills/domains/infrastructure/gitops.md +0 -735
  61. package/skills/domains/infrastructure/iac.md +0 -855
  62. package/skills/domains/infrastructure/kubernetes.md +0 -1018
  63. package/skills/domains/mobile/android-dev.md +0 -979
  64. package/skills/domains/mobile/cross-platform.md +0 -795
  65. package/skills/domains/mobile/ios-dev.md +0 -931
  66. package/skills/domains/security/secrets-management.md +0 -834
  67. package/skills/domains/security/supply-chain.md +0 -931
  68. package/skills/domains/security/threat-modeling.md +0 -828
  69. package/skills/run_skill.py +0 -153
  70. package/skills/tests/README.md +0 -225
  71. package/skills/tests/SUMMARY.md +0 -362
  72. package/skills/tests/__init__.py +0 -3
  73. package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
  74. package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
  75. package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
  76. package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
  77. package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
  78. package/skills/tests/test_change_analyzer.py +0 -558
  79. package/skills/tests/test_doc_generator.py +0 -538
  80. package/skills/tests/test_module_scanner.py +0 -376
  81. package/skills/tests/test_quality_checker.py +0 -516
  82. package/skills/tests/test_security_scanner.py +0 -426
  83. package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
  84. package/skills/tools/gen-docs/scripts/doc_generator.py +0 -520
  85. package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
  86. package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
  87. package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
  88. package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
  89. package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
  90. package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
  91. package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
  92. package/skills/tools/verify-security/scripts/security_scanner.py +0 -374
@@ -0,0 +1,349 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 文档生成器
4
+ * 自动生成/更新 README.md 和 DESIGN.md 骨架
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // --- Utilities ---
11
+
12
+ function rglob(dir, filter) {
13
+ const results = [];
14
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
15
+ const full = path.join(dir, entry.name);
16
+ if (entry.isDirectory()) {
17
+ results.push(...rglob(full, filter));
18
+ } else if (!filter || filter(entry.name, full)) {
19
+ results.push(full);
20
+ }
21
+ }
22
+ return results;
23
+ }
24
+
25
+ // --- Language Detection ---
26
+
27
+ const LANG_MAP = {
28
+ '.py': 'Python', '.go': 'Go', '.rs': 'Rust', '.ts': 'TypeScript',
29
+ '.js': 'JavaScript', '.java': 'Java', '.c': 'C', '.cpp': 'C++',
30
+ };
31
+
32
+ function detectLanguage(modPath) {
33
+ const exts = {};
34
+ try {
35
+ for (const f of rglob(modPath)) {
36
+ const ext = path.extname(f).toLowerCase();
37
+ if (ext) exts[ext] = (exts[ext] || 0) + 1;
38
+ }
39
+ } catch { return 'Unknown'; }
40
+ const codeExts = Object.entries(exts).filter(([k]) => k in LANG_MAP);
41
+ if (codeExts.length) {
42
+ const best = codeExts.reduce((a, b) => b[1] > a[1] ? b : a);
43
+ return LANG_MAP[best[0]] || 'Unknown';
44
+ }
45
+ return 'Unknown';
46
+ }
47
+
48
+ // --- Python AST-lite extraction via regex ---
49
+
50
+ function analyzePythonModule(modPath) {
51
+ const info = makeInfo(modPath, 'Python');
52
+ const pyFiles = rglob(modPath, (name) => name.endsWith('.py'));
53
+ info.files = pyFiles.map(f => path.relative(modPath, f));
54
+
55
+ for (const pyFile of pyFiles) {
56
+ const basename = path.basename(pyFile);
57
+ if (basename.startsWith('test_') || basename.includes('_test')) continue;
58
+ let content;
59
+ try { content = fs.readFileSync(pyFile, 'utf-8'); } catch { continue; }
60
+
61
+ // Module docstring (triple-quoted at top)
62
+ if (!info.description) {
63
+ const docM = content.match(/^(?:#[^\n]*\n)*\s*(?:"""([\s\S]*?)"""|'''([\s\S]*?)''')/);
64
+ if (docM) info.description = (docM[1] || docM[2]).split('\n')[0].trim();
65
+ }
66
+
67
+ const rel = path.relative(modPath, pyFile);
68
+
69
+ // Functions
70
+ for (const m of content.matchAll(/^def\s+([A-Za-z]\w*)\s*\(/gm)) {
71
+ info.functions.push({ name: m[1], file: rel, doc: '' });
72
+ }
73
+ // Classes
74
+ for (const m of content.matchAll(/^class\s+([A-Za-z]\w*)\s*[:(]/gm)) {
75
+ info.classes.push({ name: m[1], file: rel, doc: '' });
76
+ }
77
+
78
+ // Entry points
79
+ if (['main.py', '__main__.py', 'cli.py', 'app.py'].includes(basename)) {
80
+ info.entry_points.push(rel);
81
+ }
82
+ }
83
+
84
+ // Dependencies
85
+ const reqPath = path.join(modPath, 'requirements.txt');
86
+ try {
87
+ const content = fs.readFileSync(reqPath, 'utf-8');
88
+ for (const line of content.split('\n')) {
89
+ const trimmed = line.trim();
90
+ if (trimmed && !trimmed.startsWith('#')) {
91
+ info.dependencies.push(trimmed.split(/[=><]/)[0]);
92
+ }
93
+ }
94
+ } catch {}
95
+
96
+ return info;
97
+ }
98
+
99
+ // --- Generic analysis (regex fallback) ---
100
+
101
+ const LANG_PATTERNS = {
102
+ 'Go': [/^\s*func\s+(\w+)/, /^\s*type\s+(\w+)\s+struct\b/],
103
+ 'Rust': [/^\s*(?:pub\s+)?fn\s+(\w+)/, /^\s*(?:pub\s+)?struct\s+(\w+)/],
104
+ 'TypeScript': [/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, /^\s*(?:export\s+)?class\s+(\w+)/],
105
+ 'JavaScript': [/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, /^\s*(?:export\s+)?class\s+(\w+)/],
106
+ 'Java': [/^\s*(?:public|private|protected)?\s*(?:static\s+)?\w+\s+(\w+)\s*\(/, /^\s*(?:public\s+)?class\s+(\w+)/],
107
+ 'C++': [/^\s*(?:\w+\s+)+(\w+)\s*\([^;]*$/, /^\s*class\s+(\w+)/],
108
+ 'C': [/^\s*(?:\w+\s+)+(\w+)\s*\([^;]*$/, null],
109
+ };
110
+
111
+ const CODE_EXTS = new Set(['.py', '.go', '.rs', '.ts', '.js', '.java', '.c', '.cpp']);
112
+
113
+ function analyzeModule(modPath) {
114
+ const language = detectLanguage(modPath);
115
+ if (language === 'Python') return analyzePythonModule(modPath);
116
+
117
+ const info = makeInfo(modPath, language);
118
+ const [funcPat, clsPat] = LANG_PATTERNS[language] || [null, null];
119
+
120
+ try {
121
+ for (const f of rglob(modPath)) {
122
+ if (!CODE_EXTS.has(path.extname(f).toLowerCase())) continue;
123
+ const rel = path.relative(modPath, f);
124
+ info.files.push(rel);
125
+
126
+ if (!funcPat && !clsPat) continue;
127
+ let content;
128
+ try { content = fs.readFileSync(f, 'utf-8'); } catch { continue; }
129
+ for (const line of content.split('\n')) {
130
+ if (funcPat) {
131
+ const m = line.match(funcPat);
132
+ if (m && !m[1].startsWith('_')) info.functions.push({ name: m[1], file: rel, doc: '' });
133
+ }
134
+ if (clsPat) {
135
+ const m = line.match(clsPat);
136
+ if (m && !m[1].startsWith('_')) info.classes.push({ name: m[1], file: rel, doc: '' });
137
+ }
138
+ }
139
+ }
140
+ } catch {}
141
+
142
+ return info;
143
+ }
144
+
145
+ function makeInfo(modPath, language) {
146
+ return {
147
+ name: path.basename(modPath), path: modPath, description: '', language,
148
+ files: [], functions: [], classes: [], dependencies: [], entry_points: [],
149
+ };
150
+ }
151
+
152
+ // --- README Generation ---
153
+
154
+ function generateReadme(info) {
155
+ const L = [];
156
+ L.push(`# ${info.name}`, '');
157
+ if (info.description) {
158
+ L.push(info.description);
159
+ } else {
160
+ L.push('> 请在此描述模块的核心功能、解决的问题和主要用途。');
161
+ L.push('> 例如:本模块提供 X 功能,用于解决 Y 问题。');
162
+ }
163
+ L.push('', '## 概述', '', '<!-- 描述这个模块是什么,解决什么问题 -->', '');
164
+ L.push('## 特性', '', '<!-- 列出模块的主要特性,每项应包含简短描述 -->', '');
165
+ L.push('- **特性1**: 请描述第一个主要特性');
166
+ L.push('- **特性2**: 请描述第二个主要特性');
167
+ L.push('- **特性3**: 请描述第三个主要特性', '');
168
+
169
+ if (info.dependencies.length) {
170
+ L.push('## 依赖', '', '```');
171
+ info.dependencies.slice(0, 10).forEach(d => L.push(d));
172
+ if (info.dependencies.length > 10) L.push(`# ... 及其他 ${info.dependencies.length - 10} 个依赖`);
173
+ L.push('```', '');
174
+ }
175
+
176
+ L.push('## 使用方法', '');
177
+ if (info.entry_points.length) {
178
+ L.push('### 运行', '', '```bash');
179
+ const cmds = { Python: `python -m ${info.name}`, Go: 'go run ./cmd/main.go', Rust: 'cargo run', TypeScript: 'npm start', JavaScript: 'npm start' };
180
+ L.push(cmds[info.language] || `# 请根据 ${info.language} 项目结构添加运行命令`);
181
+ L.push('```', '');
182
+ }
183
+
184
+ L.push('### 示例', '');
185
+ const EXAMPLES = {
186
+ Python: `from ${info.name.toLowerCase()} import main\n\n# 初始化\nobj = main()\n\n# 执行操作\nresult = obj.process()\nprint(result)`,
187
+ Go: `package main\n\nimport "${info.name.toLowerCase()}"\n\nfunc main() {\n // 初始化\n obj := ${info.name.toLowerCase()}.New()\n\n // 执行操作\n result := obj.Process()\n println(result)\n}`,
188
+ Rust: `use ${info.name.toLowerCase()}::*;\n\nfn main() {\n // 初始化\n let obj = Object::new();\n\n // 执行操作\n let result = obj.process();\n println!("{}", result);\n}`,
189
+ TypeScript: `import { main } from "./${info.name.toLowerCase()}";\n\n// 初始化\nconst obj = new main();\n\n// 执行操作\nconst result = obj.process();\nconsole.log(result);`,
190
+ JavaScript: `const { main } = require("./${info.name.toLowerCase()}");\n\n// 初始化\nconst obj = new main();\n\n// 执行操作\nconst result = obj.process();\nconsole.log(result);`,
191
+ };
192
+ if (EXAMPLES[info.language]) {
193
+ L.push('```' + info.language.toLowerCase(), EXAMPLES[info.language], '```');
194
+ } else {
195
+ L.push('```' + info.language.toLowerCase());
196
+ L.push(`<!-- 请根据 ${info.language} 语言特性提供使用示例 -->`);
197
+ L.push(`<!-- 示例应包含:初始化、基本操作、结果处理 -->`);
198
+ L.push('```');
199
+ }
200
+ L.push('');
201
+
202
+ if (info.classes.length || info.functions.length) {
203
+ L.push('## API 概览', '');
204
+ if (info.classes.length) {
205
+ L.push('### 类', '', '| 类名 | 描述 |', '|------|------|');
206
+ info.classes.slice(0, 10).forEach(c => L.push(`| \`${c.name}\` | ${c.doc || '请补充此类的功能描述'} |`));
207
+ L.push('');
208
+ }
209
+ if (info.functions.length) {
210
+ L.push('### 函数', '', '| 函数 | 描述 |', '|------|------|');
211
+ info.functions.slice(0, 10).forEach(f => L.push(`| \`${f.name}()\` | ${f.doc || '请补充此函数的功能描述'} |`));
212
+ L.push('');
213
+ }
214
+ }
215
+
216
+ L.push('## 目录结构', '', '```', `${info.name}/`);
217
+ info.files.sort().slice(0, 15).forEach(f => L.push(`├── ${f}`));
218
+ if (info.files.length > 15) L.push(`└── ... (${info.files.length - 15} more files)`);
219
+ L.push('```', '');
220
+ L.push('## 相关文档', '', '- [设计文档](DESIGN.md)', '');
221
+ return L.join('\n');
222
+ }
223
+
224
+ // --- DESIGN Generation ---
225
+
226
+ function generateDesign(info) {
227
+ const today = new Date().toISOString().slice(0, 10);
228
+ const L = [];
229
+ L.push(`# ${info.name} 设计文档`, '');
230
+ L.push('## 设计概述', '', '### 目标', '', '<!-- 这个模块要解决什么问题? -->', '');
231
+ L.push('### 非目标', '', '<!-- 这个模块明确不做什么? -->', '');
232
+ L.push('## 架构设计', '', '### 整体架构', '', '```');
233
+ L.push('┌─────────────────────────────────────┐');
234
+ L.push('│ 请在此绘制模块的整体架构图 │');
235
+ L.push('│ 包括主要组件、数据流、依赖关系 │');
236
+ L.push('│ 可使用 ASCII 图或 Mermaid 图表 │');
237
+ L.push('└─────────────────────────────────────┘');
238
+ L.push('```', '');
239
+ L.push('### 核心组件', '');
240
+ if (info.classes.length) {
241
+ info.classes.slice(0, 5).forEach(c => L.push(`- **${c.name}**: ${c.doc || '请描述此组件的职责和功能'}`));
242
+ } else {
243
+ L.push('<!-- 列出模块的核心组件及其职责 -->');
244
+ L.push('- **组件1**: 请描述第一个核心组件的职责');
245
+ L.push('- **组件2**: 请描述第二个核心组件的职责');
246
+ L.push('- **组件3**: 请描述第三个核心组件的职责');
247
+ }
248
+ L.push('');
249
+ L.push('## 设计决策', '', '### 决策记录', '');
250
+ L.push('| 日期 | 决策 | 理由 | 影响 |', '|------|------|------|------|');
251
+ L.push(`| ${today} | 初始设计 | - | - |`, '');
252
+ L.push('### 技术选型', '', `- **语言**: ${info.language}`);
253
+ if (info.dependencies.length) L.push(`- **主要依赖**: ${info.dependencies.slice(0, 5).join(', ')}`);
254
+ L.push('- **理由**: <!-- 请说明为什么选择这些技术栈,包括性能、可维护性、生态等考量 -->', '');
255
+ L.push('## 权衡取舍', '', '### 已知限制', '');
256
+ L.push('<!-- 列出模块的已知限制和约束条件 -->');
257
+ L.push('- **限制1**: 请描述第一个已知限制及其原因');
258
+ L.push('- **限制2**: 请描述第二个已知限制及其原因', '');
259
+ L.push('### 技术债务', '');
260
+ L.push('<!-- 记录有意引入的技术债务、临时方案及其原因 -->');
261
+ L.push('- **债务1**: 描述 | 原因:性能优先 | 计划偿还时间:v2.0', '');
262
+ L.push('## 安全考量', '', '### 威胁模型', '');
263
+ L.push('<!-- 识别潜在的安全威胁,如认证、授权、数据泄露等 -->');
264
+ L.push('- **威胁1**: 请描述潜在威胁及其影响');
265
+ L.push('- **威胁2**: 请描述潜在威胁及其影响', '');
266
+ L.push('### 安全措施', '');
267
+ L.push('<!-- 列出已实施的安全措施,如输入验证、加密、访问控制等 -->');
268
+ L.push('- **措施1**: 请描述已实施的安全措施');
269
+ L.push('- **措施2**: 请描述已实施的安全措施', '');
270
+ L.push('## 变更历史', '', `### ${today} - 初始版本`, '');
271
+ L.push('**变更内容**: 创建模块', '', '**变更理由**: 初始开发', '');
272
+ return L.join('\n');
273
+ }
274
+
275
+ // --- Core: generate_docs ---
276
+
277
+ function generateDocs(targetPath, force) {
278
+ const modPath = path.resolve(targetPath);
279
+ const result = { readme: null, design: null, status: 'success', messages: [] };
280
+
281
+ if (!fs.existsSync(modPath)) {
282
+ result.status = 'error';
283
+ result.messages.push(`路径不存在: ${modPath}`);
284
+ return result;
285
+ }
286
+
287
+ const info = analyzeModule(modPath);
288
+
289
+ const readmePath = path.join(modPath, 'README.md');
290
+ if (fs.existsSync(readmePath) && !force) {
291
+ result.messages.push('README.md 已存在,跳过(使用 --force 覆盖)');
292
+ } else {
293
+ fs.writeFileSync(readmePath, generateReadme(info));
294
+ result.readme = readmePath;
295
+ result.messages.push('已生成 README.md');
296
+ }
297
+
298
+ const designPath = path.join(modPath, 'DESIGN.md');
299
+ if (fs.existsSync(designPath) && !force) {
300
+ result.messages.push('DESIGN.md 已存在,跳过(使用 --force 覆盖)');
301
+ } else {
302
+ fs.writeFileSync(designPath, generateDesign(info));
303
+ result.design = designPath;
304
+ result.messages.push('已生成 DESIGN.md');
305
+ }
306
+
307
+ return result;
308
+ }
309
+
310
+ // --- CLI ---
311
+
312
+ function parseArgs(argv) {
313
+ const args = { path: '.', force: false, json: false, readmeOnly: false, designOnly: false };
314
+ const rest = argv.slice(2);
315
+ const positional = [];
316
+ for (const a of rest) {
317
+ if (a === '-f' || a === '--force') args.force = true;
318
+ else if (a === '--json') args.json = true;
319
+ else if (a === '--readme-only') args.readmeOnly = true;
320
+ else if (a === '--design-only') args.designOnly = true;
321
+ else if (a === '-h' || a === '--help') {
322
+ console.log('Usage: doc_generator.js [path] [-f|--force] [--json] [--readme-only] [--design-only]');
323
+ process.exit(0);
324
+ } else positional.push(a);
325
+ }
326
+ if (positional.length) args.path = positional[0];
327
+ return args;
328
+ }
329
+
330
+ function main() {
331
+ const args = parseArgs(process.argv);
332
+ const result = generateDocs(args.path, args.force);
333
+
334
+ if (args.json) {
335
+ console.log(JSON.stringify(result, null, 2));
336
+ } else {
337
+ console.log('='.repeat(50));
338
+ console.log('文档生成报告');
339
+ console.log('='.repeat(50));
340
+ for (const msg of result.messages) {
341
+ console.log(` \u2022 ${msg}`);
342
+ }
343
+ console.log('='.repeat(50));
344
+ }
345
+
346
+ process.exit(result.status === 'success' ? 0 : 1);
347
+ }
348
+
349
+ main();
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: verify-change
3
3
  description: 变更校验关卡。分析代码变更,检测文档同步状态,评估变更影响范围。当魔尊提到变更检查、文档同步、代码审查、提交前检查、diff分析时使用。在设计级变更、重构完成时自动触发。
4
+ license: MIT
5
+ compatibility: node>=18
4
6
  user-invocable: true
5
7
  disable-model-invocation: false
6
8
  allowed-tools: Bash, Read, Grep
@@ -24,11 +26,11 @@ argument-hint: [--mode working|staged|committed]
24
26
 
25
27
  ```bash
26
28
  # 在 skill 目录下运行
27
- python scripts/change_analyzer.py # 分析工作区变更(默认)
28
- python scripts/change_analyzer.py --mode staged # 分析暂存区变更
29
- python scripts/change_analyzer.py --mode committed # 分析已提交变更
30
- python scripts/change_analyzer.py -v # 详细模式
31
- python scripts/change_analyzer.py --json # JSON 输出
29
+ node scripts/change_analyzer.js # 分析工作区变更(默认)
30
+ node scripts/change_analyzer.js --mode staged # 分析暂存区变更
31
+ node scripts/change_analyzer.js --mode committed # 分析已提交变更
32
+ node scripts/change_analyzer.js -v # 详细模式
33
+ node scripts/change_analyzer.js --json # JSON 输出
32
34
  ```
33
35
 
34
36
  ## 检测能力
@@ -107,7 +109,7 @@ python scripts/change_analyzer.py --json # JSON 输出
107
109
  ## 校验流程
108
110
 
109
111
  ```
110
- 1. 运行 change_analyzer.py 自动分析
112
+ 1. 运行 change_analyzer.js 自动分析
111
113
  2. 识别变更文件和受影响模块
112
114
  3. 检查文档同步状态
113
115
  4. 评估变更影响
@@ -0,0 +1,270 @@
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
+
8
+ const CODE_EXT = new Set([".py",".go",".rs",".ts",".js",".jsx",".tsx",".java",".c",".cpp",".h",".hpp"]);
9
+ const DOC_EXT = new Set([".md",".rst",".txt",".adoc"]);
10
+ const TEST_PATTERNS = ["test_","_test.",".test.","spec_","_spec.","/tests/","/test/","/__tests__/"];
11
+ const CONFIG_FILES = new Set(["package.json","pyproject.toml","go.mod","cargo.toml","pom.xml","makefile","dockerfile"]);
12
+ const CONFIG_EXT = new Set([".yaml",".yml",".json",".toml",".ini"]);
13
+
14
+ function normalizePath(p) {
15
+ let s = p.trim();
16
+ if (s.startsWith('"') && s.endsWith('"') && s.length >= 2) {
17
+ s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
18
+ }
19
+ if (s.startsWith("./")) s = s.slice(2);
20
+ return s;
21
+ }
22
+
23
+ function classifyFile(filePath) {
24
+ const ext = path.extname(filePath).toLowerCase();
25
+ const name = path.basename(filePath).toLowerCase();
26
+ const lower = filePath.toLowerCase();
27
+ return {
28
+ path: filePath, type: "modified", additions: 0, deletions: 0,
29
+ is_code: CODE_EXT.has(ext), is_doc: DOC_EXT.has(ext),
30
+ is_test: TEST_PATTERNS.some(p => lower.includes(p)),
31
+ is_config: CONFIG_FILES.has(name) || CONFIG_EXT.has(ext),
32
+ };
33
+ }
34
+
35
+ function parseNameStatusLine(line) {
36
+ const parts = line.split("\t");
37
+ if (parts.length < 2) return null;
38
+ const status = parts[0][0];
39
+ const p = normalizePath(parts[parts.length - 1]);
40
+ if (!p) return null;
41
+ const c = classifyFile(p);
42
+ const map = { A: "added", M: "modified", D: "deleted", R: "renamed" };
43
+ if (map[status]) c.type = map[status];
44
+ return c;
45
+ }
46
+
47
+ function parsePorcelainLine(line) {
48
+ if (line.length < 3) return null;
49
+ const status = line.slice(0, 2);
50
+ let raw = line.slice(3);
51
+ if (!raw) return null;
52
+ if (raw.includes(" -> ")) raw = raw.split(" -> ")[1];
53
+ const p = normalizePath(raw);
54
+ if (!p) return null;
55
+ const c = classifyFile(p);
56
+ if (status.includes("?") || status.includes("A")) c.type = "added";
57
+ else if (status.includes("R")) c.type = "renamed";
58
+ else if (status.includes("M")) c.type = "modified";
59
+ else if (status.includes("D")) c.type = "deleted";
60
+ return c;
61
+ }
62
+
63
+ function git(args) {
64
+ try { return execSync(`git ${args}`, { encoding: "utf8", stdio: ["pipe","pipe","pipe"] }); }
65
+ catch { return ""; }
66
+ }
67
+
68
+ function getGitChanges(base = "HEAD~1", target = "HEAD") {
69
+ const changes = [];
70
+ for (const line of git(`diff --name-status ${base} ${target}`).split("\n")) {
71
+ if (!line) continue;
72
+ const c = parseNameStatusLine(line);
73
+ if (c) changes.push(c);
74
+ }
75
+ const statMap = {};
76
+ for (const line of git(`diff --numstat ${base} ${target}`).split("\n")) {
77
+ if (!line) continue;
78
+ const parts = line.split("\t");
79
+ if (parts.length >= 3) {
80
+ statMap[normalizePath(parts[2])] = [
81
+ parts[0] === "-" ? 0 : parseInt(parts[0], 10),
82
+ parts[1] === "-" ? 0 : parseInt(parts[1], 10),
83
+ ];
84
+ }
85
+ }
86
+ for (const c of changes) {
87
+ if (statMap[c.path]) { [c.additions, c.deletions] = statMap[c.path]; }
88
+ }
89
+ return changes;
90
+ }
91
+
92
+ function getStagedChanges() {
93
+ const changes = [];
94
+ for (const line of git("diff --cached --name-status").split("\n")) {
95
+ if (!line) continue;
96
+ const c = parseNameStatusLine(line);
97
+ if (c) changes.push(c);
98
+ }
99
+ return changes;
100
+ }
101
+
102
+ function getWorkingChanges() {
103
+ const changes = [];
104
+ for (const line of git("status --porcelain").split("\n")) {
105
+ if (!line) continue;
106
+ const c = parsePorcelainLine(line);
107
+ if (c) changes.push(c);
108
+ }
109
+ return changes;
110
+ }
111
+
112
+ function isPathInModule(filePath, mod) {
113
+ const np = normalizePath(filePath);
114
+ if (mod === ".") return !np.includes("/");
115
+ return np === mod || np.startsWith(mod + "/");
116
+ }
117
+
118
+ function identifyModules(changes) {
119
+ const modules = new Set();
120
+ for (const c of changes) {
121
+ const np = normalizePath(c.path);
122
+ const parts = np.split("/").filter(Boolean);
123
+ if (parts.length === 1) { modules.add("."); continue; }
124
+ let found = false;
125
+ for (let i = 0; i < parts.length; i++) {
126
+ const mp = parts.slice(0, i + 1).join("/");
127
+ if (fs.existsSync(path.join(mp, "README.md")) || fs.existsSync(path.join(mp, "DESIGN.md"))) {
128
+ modules.add(mp); found = true; break;
129
+ }
130
+ }
131
+ if (!found && parts.length > 1) modules.add(parts[0]);
132
+ }
133
+ return modules;
134
+ }
135
+
136
+ function checkDocSync(changes, modules) {
137
+ const docStatus = {};
138
+ const issues = [];
139
+ const codeChanges = changes.filter(c => c.is_code && c.type !== "deleted");
140
+ const docPaths = new Set(changes.filter(c => c.is_doc).map(c => normalizePath(c.path)));
141
+
142
+ for (const mod of modules) {
143
+ const readme = normalizePath(mod === "." ? "README.md" : `${mod}/README.md`);
144
+ const design = normalizePath(mod === "." ? "DESIGN.md" : `${mod}/DESIGN.md`);
145
+ const modCode = codeChanges.filter(c => isPathInModule(c.path, mod));
146
+ if (!modCode.length) continue;
147
+ const total = modCode.reduce((s, c) => s + c.additions + c.deletions, 0);
148
+ if (total > 50 && !docPaths.has(design)) {
149
+ issues.push({ severity: "warning", message: `模块 ${mod} 有较大代码变更 (${total} 行),但 DESIGN.md 未更新`, related_files: modCode.map(c => c.path) });
150
+ docStatus[`${mod}/DESIGN.md`] = false;
151
+ } else {
152
+ docStatus[`${mod}/DESIGN.md`] = true;
153
+ }
154
+ const newFiles = modCode.filter(c => c.type === "added");
155
+ if (newFiles.length && !docPaths.has(readme)) {
156
+ issues.push({ severity: "info", message: `模块 ${mod} 新增了文件,建议更新 README.md`, related_files: newFiles.map(c => c.path) });
157
+ }
158
+ }
159
+ return { docStatus, issues };
160
+ }
161
+
162
+ function analyzeImpact(changes) {
163
+ const issues = [];
164
+ const code = changes.filter(c => c.is_code && !c.is_test);
165
+ const tests = changes.filter(c => c.is_test);
166
+ if (code.length && !tests.length) {
167
+ 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) });
169
+ }
170
+ const configs = changes.filter(c => c.is_config);
171
+ if (configs.length) issues.push({ severity: "info", message: "配置文件有变更,请确认是否需要更新文档", related_files: configs.map(c => c.path) });
172
+ 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) });
174
+ return issues;
175
+ }
176
+
177
+ function analyzeChanges(mode = "working") {
178
+ let changes;
179
+ if (mode === "staged") changes = getStagedChanges();
180
+ else if (mode === "committed") changes = getGitChanges();
181
+ else changes = getWorkingChanges();
182
+
183
+ const issues = [];
184
+ let modules = new Set(), docStatus = {};
185
+ if (changes.length) {
186
+ modules = identifyModules(changes);
187
+ const ds = checkDocSync(changes, modules);
188
+ docStatus = ds.docStatus;
189
+ issues.push(...ds.issues, ...analyzeImpact(changes));
190
+ }
191
+ const passed = !issues.some(i => i.severity === "error");
192
+ const totalAdd = changes.reduce((s, c) => s + c.additions, 0);
193
+ const totalDel = changes.reduce((s, c) => s + c.deletions, 0);
194
+ return { changes, issues, modules, docStatus, passed, totalAdd, totalDel };
195
+ }
196
+
197
+ 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 ? "✓ 通过" : "✗ 需要关注"}`);
207
+
208
+ if (r.changes.length && verbose) {
209
+ L.push("\n" + sep2, "变更文件列表:", sep2);
210
+ const icons = { added: "➕", modified: "📝", deleted: "➖", renamed: "📋" };
211
+ for (const c of r.changes) {
212
+ const tags = [];
213
+ if (c.is_code) tags.push("代码");
214
+ if (c.is_doc) tags.push("文档");
215
+ if (c.is_test) tags.push("测试");
216
+ if (c.is_config) tags.push("配置");
217
+ 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
+ }
231
+ }
232
+ }
233
+
234
+ if (Object.keys(r.docStatus).length) {
235
+ L.push("\n" + sep2, "文档同步状态:", sep2);
236
+ for (const [doc, synced] of Object.entries(r.docStatus)) {
237
+ L.push(` ${synced ? "✓" : "✗"} ${doc}`);
238
+ }
239
+ }
240
+
241
+ L.push("\n" + sep);
242
+ return L.join("\n");
243
+ }
244
+
245
+ // 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
+ }
253
+
254
+ const result = analyzeChanges(mode);
255
+
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));
268
+ }
269
+
270
+ process.exit(result.passed ? 0 : 1);
@@ -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 各项是否齐全