code-abyss 1.5.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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +197 -0
  3. package/bin/install.js +193 -0
  4. package/bin/uninstall.js +42 -0
  5. package/config/AGENTS.md +247 -0
  6. package/config/CLAUDE.md +207 -0
  7. package/config/settings.example.json +27 -0
  8. package/output-styles/abyss-cultivator.md +399 -0
  9. package/package.json +41 -0
  10. package/skills/SKILL.md +115 -0
  11. package/skills/ai/SKILL.md +29 -0
  12. package/skills/ai/agent-dev.md +242 -0
  13. package/skills/ai/llm-security.md +288 -0
  14. package/skills/architecture/SKILL.md +41 -0
  15. package/skills/architecture/api-design.md +225 -0
  16. package/skills/architecture/caching.md +299 -0
  17. package/skills/architecture/cloud-native.md +285 -0
  18. package/skills/architecture/compliance.md +299 -0
  19. package/skills/architecture/data-security.md +184 -0
  20. package/skills/architecture/message-queue.md +329 -0
  21. package/skills/architecture/security-arch.md +210 -0
  22. package/skills/development/SKILL.md +43 -0
  23. package/skills/development/cpp.md +246 -0
  24. package/skills/development/go.md +323 -0
  25. package/skills/development/java.md +277 -0
  26. package/skills/development/python.md +288 -0
  27. package/skills/development/rust.md +313 -0
  28. package/skills/development/shell.md +313 -0
  29. package/skills/development/typescript.md +277 -0
  30. package/skills/devops/SKILL.md +36 -0
  31. package/skills/devops/cost-optimization.md +272 -0
  32. package/skills/devops/database.md +217 -0
  33. package/skills/devops/devsecops.md +198 -0
  34. package/skills/devops/git-workflow.md +181 -0
  35. package/skills/devops/observability.md +280 -0
  36. package/skills/devops/performance.md +273 -0
  37. package/skills/devops/testing.md +186 -0
  38. package/skills/gen-docs/SKILL.md +114 -0
  39. package/skills/gen-docs/scripts/doc_generator.py +491 -0
  40. package/skills/multi-agent/SKILL.md +268 -0
  41. package/skills/run_skill.py +88 -0
  42. package/skills/security/SKILL.md +51 -0
  43. package/skills/security/blue-team.md +379 -0
  44. package/skills/security/code-audit.md +265 -0
  45. package/skills/security/pentest.md +226 -0
  46. package/skills/security/red-team.md +321 -0
  47. package/skills/security/threat-intel.md +322 -0
  48. package/skills/security/vuln-research.md +369 -0
  49. package/skills/tests/README.md +225 -0
  50. package/skills/tests/SUMMARY.md +362 -0
  51. package/skills/tests/__init__.py +3 -0
  52. package/skills/tests/test_change_analyzer.py +558 -0
  53. package/skills/tests/test_doc_generator.py +538 -0
  54. package/skills/tests/test_module_scanner.py +376 -0
  55. package/skills/tests/test_quality_checker.py +516 -0
  56. package/skills/tests/test_security_scanner.py +426 -0
  57. package/skills/verify-change/SKILL.md +138 -0
  58. package/skills/verify-change/scripts/change_analyzer.py +529 -0
  59. package/skills/verify-module/SKILL.md +125 -0
  60. package/skills/verify-module/scripts/module_scanner.py +321 -0
  61. package/skills/verify-quality/SKILL.md +158 -0
  62. package/skills/verify-quality/scripts/quality_checker.py +481 -0
  63. package/skills/verify-security/SKILL.md +141 -0
  64. package/skills/verify-security/scripts/security_scanner.py +368 -0
@@ -0,0 +1,426 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ verify-security 单元测试
4
+ 测试代码安全扫描器功能
5
+ """
6
+
7
+ import unittest
8
+ import tempfile
9
+ import sys
10
+ from pathlib import Path
11
+ from unittest.mock import patch, MagicMock
12
+
13
+ # 添加 skills 目录到 Python 路径
14
+ sys.path.insert(0, str(Path(__file__).parent.parent / "verify-security" / "scripts"))
15
+
16
+ from security_scanner import (
17
+ Severity, Finding, ScanResult, scan_file, scan_directory,
18
+ format_report, SECURITY_RULES
19
+ )
20
+
21
+
22
+ class TestSecurityScanner(unittest.TestCase):
23
+ """安全扫描器测试"""
24
+
25
+ def setUp(self):
26
+ """测试前准备"""
27
+ self.temp_dir = tempfile.TemporaryDirectory()
28
+ self.temp_path = Path(self.temp_dir.name)
29
+
30
+ def tearDown(self):
31
+ """测试后清理"""
32
+ self.temp_dir.cleanup()
33
+
34
+ def test_scan_file_with_sql_injection(self):
35
+ """测试 SQL 注入检测"""
36
+ test_file = self.temp_path / "test.py"
37
+ test_file.write_text('cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")')
38
+
39
+ findings = scan_file(test_file, SECURITY_RULES)
40
+
41
+ self.assertTrue(len(findings) > 0)
42
+ self.assertTrue(any(f.severity == Severity.CRITICAL for f in findings))
43
+ self.assertTrue(any("SQL" in f.message for f in findings))
44
+
45
+ def test_scan_file_without_sql_context_not_flagged(self):
46
+ """测试非 SQL 场景不应误报 SQL 注入"""
47
+ test_file = self.temp_path / "non_sql.py"
48
+ test_file.write_text('long_line = "x = \"" + "a" * 10000 + "\""')
49
+
50
+ findings = scan_file(test_file, SECURITY_RULES)
51
+
52
+ self.assertFalse(any("SQL" in f.message for f in findings))
53
+
54
+ def test_scan_file_with_hardcoded_secret(self):
55
+ """测试硬编码密钥检测"""
56
+ test_file = self.temp_path / "config.py"
57
+ test_file.write_text('password = "super_secret_password_12345"')
58
+
59
+ findings = scan_file(test_file, SECURITY_RULES)
60
+
61
+ self.assertTrue(len(findings) > 0)
62
+ self.assertTrue(any(f.severity == Severity.HIGH for f in findings))
63
+
64
+ def test_scan_file_with_command_injection(self):
65
+ """测试命令注入检测"""
66
+ test_file = self.temp_path / "shell.py"
67
+ test_file.write_text('os.system(cmd, shell=True)')
68
+
69
+ findings = scan_file(test_file, SECURITY_RULES)
70
+
71
+ self.assertTrue(len(findings) > 0)
72
+ self.assertTrue(any("shell=True" in f.message for f in findings))
73
+
74
+ def test_scan_file_with_xss_vulnerability(self):
75
+ """测试 XSS 漏洞检测"""
76
+ test_file = self.temp_path / "app.js"
77
+ test_file.write_text('element.innerHTML = userInput;')
78
+
79
+ findings = scan_file(test_file, SECURITY_RULES)
80
+
81
+ self.assertTrue(len(findings) > 0)
82
+ self.assertTrue(any("innerHTML" in f.message for f in findings))
83
+
84
+ def test_scan_file_with_unsafe_pickle(self):
85
+ """测试不安全反序列化检测"""
86
+ test_file = self.temp_path / "data.py"
87
+ test_file.write_text('data = pickle.loads(user_data)')
88
+
89
+ findings = scan_file(test_file, SECURITY_RULES)
90
+
91
+ self.assertTrue(len(findings) > 0)
92
+ self.assertTrue(any("反序列化" in f.message for f in findings))
93
+
94
+ def test_scan_file_with_weak_crypto(self):
95
+ """测试弱加密算法检测"""
96
+ test_file = self.temp_path / "crypto.py"
97
+ test_file.write_text('hash_obj = hashlib.md5(password)')
98
+
99
+ findings = scan_file(test_file, SECURITY_RULES)
100
+
101
+ self.assertTrue(len(findings) > 0)
102
+ self.assertTrue(any("MD5" in f.message for f in findings))
103
+
104
+ def test_scan_file_with_debug_code(self):
105
+ """测试调试代码检测"""
106
+ test_file = self.temp_path / "main.py"
107
+ test_file.write_text('print("debug info")\npdb.set_trace()')
108
+
109
+ findings = scan_file(test_file, SECURITY_RULES)
110
+
111
+ self.assertTrue(len(findings) > 0)
112
+ self.assertTrue(any("调试" in f.message for f in findings))
113
+
114
+ def test_scan_file_print_not_marked_debug(self):
115
+ """测试普通 print 不应被判定调试代码"""
116
+ test_file = self.temp_path / "main.py"
117
+ test_file.write_text('print("normal output")')
118
+
119
+ findings = scan_file(test_file, SECURITY_RULES)
120
+
121
+ self.assertFalse(any("调试" in f.message for f in findings))
122
+
123
+ def test_scan_file_ignores_comments(self):
124
+ """测试注释行被忽略"""
125
+ test_file = self.temp_path / "test.py"
126
+ test_file.write_text('# cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")')
127
+
128
+ findings = scan_file(test_file, SECURITY_RULES)
129
+
130
+ # 注释行应该被忽略
131
+ self.assertEqual(len(findings), 0)
132
+
133
+ def test_scan_file_empty_file(self):
134
+ """测试空文件扫描"""
135
+ test_file = self.temp_path / "empty.py"
136
+ test_file.write_text('')
137
+
138
+ findings = scan_file(test_file, SECURITY_RULES)
139
+
140
+ self.assertEqual(len(findings), 0)
141
+
142
+ def test_scan_file_nonexistent(self):
143
+ """测试不存在的文件"""
144
+ nonexistent = self.temp_path / "nonexistent.py"
145
+
146
+ findings = scan_file(nonexistent, SECURITY_RULES)
147
+
148
+ self.assertEqual(len(findings), 0)
149
+
150
+ def test_scan_directory_basic(self):
151
+ """测试目录扫描基本功能"""
152
+ # 创建测试文件
153
+ (self.temp_path / "safe.py").write_text('x = 1')
154
+ (self.temp_path / "unsafe.py").write_text('password = "secret123456"')
155
+
156
+ result = scan_directory(str(self.temp_path))
157
+
158
+ self.assertIsInstance(result, ScanResult)
159
+ self.assertEqual(result.files_scanned, 2)
160
+ self.assertTrue(len(result.findings) > 0)
161
+
162
+ def test_scan_directory_empty(self):
163
+ """测试空目录扫描"""
164
+ result = scan_directory(str(self.temp_path))
165
+
166
+ self.assertEqual(result.files_scanned, 0)
167
+ self.assertEqual(len(result.findings), 0)
168
+
169
+ def test_scan_directory_nonexistent(self):
170
+ """测试不存在的目录"""
171
+ nonexistent = self.temp_path / "nonexistent"
172
+
173
+ result = scan_directory(str(nonexistent))
174
+
175
+ self.assertEqual(result.files_scanned, 0)
176
+
177
+ def test_scan_directory_excludes_dirs(self):
178
+ """测试目录排除功能"""
179
+ # 创建被排除的目录
180
+ node_modules = self.temp_path / "node_modules"
181
+ node_modules.mkdir()
182
+ (node_modules / "unsafe.py").write_text('password = "secret123456"')
183
+
184
+ # 创建不被排除的文件
185
+ (self.temp_path / "safe.py").write_text('x = 1')
186
+
187
+ result = scan_directory(str(self.temp_path), exclude_dirs=['node_modules'])
188
+
189
+ self.assertEqual(result.files_scanned, 1)
190
+
191
+ def test_scan_directory_default_excludes_tests(self):
192
+ """测试默认排除测试目录"""
193
+ tests_dir = self.temp_path / "tests"
194
+ tests_dir.mkdir()
195
+ (tests_dir / "test_unsafe.py").write_text('password = "secret123456"')
196
+ (self.temp_path / "app.py").write_text('x = 1')
197
+
198
+ result = scan_directory(str(self.temp_path))
199
+
200
+ self.assertEqual(result.files_scanned, 1)
201
+ self.assertFalse(any("test_unsafe.py" in f.file_path for f in result.findings))
202
+
203
+ def test_scan_result_passed_property(self):
204
+ """测试 ScanResult.passed 属性"""
205
+ result = ScanResult(scan_path="/test")
206
+
207
+ # 没有发现时应该通过
208
+ self.assertTrue(result.passed)
209
+
210
+ # 添加低危发现
211
+ result.findings.append(Finding(
212
+ severity=Severity.LOW,
213
+ category="test",
214
+ message="test",
215
+ file_path="test.py",
216
+ line_number=1,
217
+ line_content="test",
218
+ recommendation="test"
219
+ ))
220
+ self.assertTrue(result.passed)
221
+
222
+ # 添加高危发现
223
+ result.findings.append(Finding(
224
+ severity=Severity.HIGH,
225
+ category="test",
226
+ message="test",
227
+ file_path="test.py",
228
+ line_number=2,
229
+ line_content="test",
230
+ recommendation="test"
231
+ ))
232
+ self.assertFalse(result.passed)
233
+
234
+ def test_scan_result_count_by_severity(self):
235
+ """测试按严重程度计数"""
236
+ result = ScanResult(scan_path="/test")
237
+
238
+ result.findings.append(Finding(
239
+ severity=Severity.CRITICAL,
240
+ category="test",
241
+ message="test",
242
+ file_path="test.py",
243
+ line_number=1,
244
+ line_content="test",
245
+ recommendation="test"
246
+ ))
247
+ result.findings.append(Finding(
248
+ severity=Severity.HIGH,
249
+ category="test",
250
+ message="test",
251
+ file_path="test.py",
252
+ line_number=2,
253
+ line_content="test",
254
+ recommendation="test"
255
+ ))
256
+
257
+ counts = result.count_by_severity()
258
+
259
+ self.assertEqual(counts['critical'], 1)
260
+ self.assertEqual(counts['high'], 1)
261
+ self.assertEqual(counts['medium'], 0)
262
+
263
+ def test_format_report_basic(self):
264
+ """测试报告格式化"""
265
+ result = ScanResult(scan_path="/test", files_scanned=5)
266
+
267
+ report = format_report(result)
268
+
269
+ self.assertIn("代码安全扫描报告", report)
270
+ self.assertIn("/test", report)
271
+ self.assertIn("5", report)
272
+
273
+ def test_format_report_with_findings(self):
274
+ """测试包含发现的报告格式化"""
275
+ result = ScanResult(scan_path="/test", files_scanned=1)
276
+ result.findings.append(Finding(
277
+ severity=Severity.CRITICAL,
278
+ category="注入",
279
+ message="SQL 注入风险",
280
+ file_path="test.py",
281
+ line_number=10,
282
+ line_content="cursor.execute(f'...')",
283
+ recommendation="使用参数化查询"
284
+ ))
285
+
286
+ report = format_report(result, verbose=True)
287
+
288
+ self.assertIn("SQL 注入风险", report)
289
+ self.assertIn("test.py:10", report)
290
+ self.assertIn("使用参数化查询", report)
291
+
292
+ def test_finding_dataclass(self):
293
+ """测试 Finding 数据类"""
294
+ finding = Finding(
295
+ severity=Severity.HIGH,
296
+ category="敏感信息",
297
+ message="硬编码密钥",
298
+ file_path="config.py",
299
+ line_number=5,
300
+ line_content='password = "secret"',
301
+ recommendation="使用环境变量"
302
+ )
303
+
304
+ self.assertEqual(finding.severity, Severity.HIGH)
305
+ self.assertEqual(finding.category, "敏感信息")
306
+ self.assertEqual(finding.line_number, 5)
307
+
308
+ def test_severity_enum(self):
309
+ """测试 Severity 枚举"""
310
+ self.assertEqual(Severity.CRITICAL.value, "critical")
311
+ self.assertEqual(Severity.HIGH.value, "high")
312
+ self.assertEqual(Severity.MEDIUM.value, "medium")
313
+ self.assertEqual(Severity.LOW.value, "low")
314
+ self.assertEqual(Severity.INFO.value, "info")
315
+
316
+ def test_scan_file_with_multiple_issues(self):
317
+ """测试单个文件中的多个问题"""
318
+ test_file = self.temp_path / "multi.py"
319
+ test_file.write_text('''
320
+ password = "secret123456"
321
+ cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
322
+ os.system(cmd, shell=True)
323
+ ''')
324
+
325
+ findings = scan_file(test_file, SECURITY_RULES)
326
+
327
+ self.assertTrue(len(findings) >= 3)
328
+
329
+ def test_scan_directory_with_multiple_extensions(self):
330
+ """测试多种文件类型扫描"""
331
+ (self.temp_path / "test.py").write_text('password = "secret123456"')
332
+ (self.temp_path / "test.js").write_text('element.innerHTML = userInput;')
333
+ (self.temp_path / "test.go").write_text('password := "secret123456"')
334
+
335
+ result = scan_directory(str(self.temp_path))
336
+
337
+ self.assertEqual(result.files_scanned, 3)
338
+ self.assertTrue(len(result.findings) > 0)
339
+
340
+ def test_scan_file_with_encoding_error(self):
341
+ """测试处理编码错误的文件"""
342
+ test_file = self.temp_path / "binary.bin"
343
+ # 写入二进制数据
344
+ test_file.write_bytes(b'\x80\x81\x82\x83')
345
+
346
+ findings = scan_file(test_file, SECURITY_RULES)
347
+
348
+ # 应该优雅地处理编码错误
349
+ self.assertIsInstance(findings, list)
350
+
351
+
352
+ class TestSecurityScannerEdgeCases(unittest.TestCase):
353
+ """安全扫描器边界条件测试"""
354
+
355
+ def setUp(self):
356
+ """测试前准备"""
357
+ self.temp_dir = tempfile.TemporaryDirectory()
358
+ self.temp_path = Path(self.temp_dir.name)
359
+
360
+ def tearDown(self):
361
+ """测试后清理"""
362
+ self.temp_dir.cleanup()
363
+
364
+ def test_scan_deeply_nested_directories(self):
365
+ """测试深层嵌套目录"""
366
+ deep_path = self.temp_path / "a" / "b" / "c" / "d" / "e"
367
+ deep_path.mkdir(parents=True)
368
+ (deep_path / "test.py").write_text('password = "secret123456"')
369
+
370
+ result = scan_directory(str(self.temp_path))
371
+
372
+ self.assertEqual(result.files_scanned, 1)
373
+ self.assertTrue(len(result.findings) > 0)
374
+
375
+ def test_scan_file_with_very_long_line(self):
376
+ """测试包含超长行的文件"""
377
+ test_file = self.temp_path / "long.py"
378
+ long_line = 'x = "' + 'a' * 10000 + '"'
379
+ test_file.write_text(long_line)
380
+
381
+ findings = scan_file(test_file, SECURITY_RULES)
382
+
383
+ # 应该能处理超长行
384
+ self.assertIsInstance(findings, list)
385
+
386
+ def test_scan_file_with_special_characters(self):
387
+ """测试包含特殊字符的文件"""
388
+ test_file = self.temp_path / "special.py"
389
+ test_file.write_text('# 中文注释\npassword = "secret123456" # 密码')
390
+
391
+ findings = scan_file(test_file, SECURITY_RULES)
392
+
393
+ self.assertTrue(len(findings) > 0)
394
+
395
+ def test_scan_result_sorting(self):
396
+ """测试发现按严重程度排序"""
397
+ result = ScanResult(scan_path="/test")
398
+
399
+ # 添加不同严重程度的发现
400
+ result.findings.append(Finding(
401
+ severity=Severity.LOW,
402
+ category="test",
403
+ message="low",
404
+ file_path="test.py",
405
+ line_number=1,
406
+ line_content="test",
407
+ recommendation="test"
408
+ ))
409
+ result.findings.append(Finding(
410
+ severity=Severity.CRITICAL,
411
+ category="test",
412
+ message="critical",
413
+ file_path="test.py",
414
+ line_number=2,
415
+ line_content="test",
416
+ recommendation="test"
417
+ ))
418
+
419
+ # 扫描会自动排序
420
+ result2 = scan_directory(str(self.temp_path))
421
+ # 验证排序逻辑存在
422
+ self.assertIsInstance(result2, ScanResult)
423
+
424
+
425
+ if __name__ == '__main__':
426
+ unittest.main()
@@ -0,0 +1,138 @@
1
+ ---
2
+ name: verify-change
3
+ description: 变更校验关卡。分析代码变更,检测文档同步状态,评估变更影响范围。当魔尊提到变更检查、文档同步、代码审查、提交前检查、diff分析时使用。在设计级变更、重构完成时自动触发。
4
+ user-invocable: true
5
+ disable-model-invocation: false
6
+ allowed-tools: Bash, Read, Grep
7
+ argument-hint: [--mode working|staged|committed]
8
+ ---
9
+
10
+ # ⚖ 校验关卡 · 变更校验
11
+
12
+
13
+ ## 核心原则
14
+
15
+ ```
16
+ 变更 = 代码改动 + 文档更新 + 理由记录
17
+ 无理由的变更是隐患,无记录的变更是灾难
18
+ 每一次变更都是历史,每一个决策都要留痕
19
+ ```
20
+
21
+ ## 自动分析
22
+
23
+ 运行变更分析脚本(跨平台):
24
+
25
+ ```bash
26
+ # 在 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 输出
32
+ ```
33
+
34
+ ## 检测能力
35
+
36
+ ### 自动检测项
37
+
38
+ | 检测项 | 说明 |
39
+ |--------|------|
40
+ | **文件分类** | 自动识别代码/文档/测试/配置文件 |
41
+ | **模块识别** | 识别受影响的模块 |
42
+ | **文档同步** | 检测代码变更是否同步更新文档 |
43
+ | **测试覆盖** | 检测代码变更是否有对应测试 |
44
+ | **影响评估** | 评估变更规模和影响范围 |
45
+
46
+ ### 触发警告的情况
47
+
48
+ - ⚠️ 代码变更 > 50 行但 DESIGN.md 未更新
49
+ - ⚠️ 代码变更 > 30 行但无测试更新
50
+ - ⚠️ 新增文件但 README.md 未更新
51
+ - ⚠️ 配置文件变更未记录
52
+ - ℹ️ 删除文件需确认引用已清理
53
+
54
+ ## 变更前置检查
55
+
56
+ 在修改任何模块前,必须:
57
+
58
+ 1. **读取 README.md** — 理解模块定位
59
+ 2. **读取 DESIGN.md** — 理解现有决策
60
+ 3. **评估影响范围** — 此变更影响哪些部分
61
+ 4. **确认变更理由** — 为什么要改
62
+
63
+ ## 变更后置检查
64
+
65
+ 代码修改完成后,必须:
66
+
67
+ ### README.md 更新检查
68
+
69
+ - [ ] 模块职责是否变化 → 更新职责描述
70
+ - [ ] 依赖关系是否变化 → 更新依赖说明
71
+ - [ ] 使用方式是否变化 → 更新示例代码
72
+
73
+ ### DESIGN.md 更新检查
74
+
75
+ - [ ] 新增设计决策 → 记录决策及理由
76
+ - [ ] 修改现有设计 → 记录变更及原因
77
+ - [ ] 引入新限制 → 更新已知限制
78
+ - [ ] 添加变更记录 → 更新变更历史
79
+
80
+ ## 变更记录格式
81
+
82
+ 在 DESIGN.md 的变更历史中添加:
83
+
84
+ ```markdown
85
+ ## 变更历史
86
+
87
+ ### [日期] - [变更标题]
88
+
89
+ **变更内容**: 简述改了什么
90
+
91
+ **变更理由**: 为什么要改
92
+
93
+ **影响范围**: 影响哪些功能/模块
94
+
95
+ **决策依据**: 为何选择此方案(如适用)
96
+ ```
97
+
98
+ ## 自动触发时机
99
+
100
+ | 场景 | 触发条件 |
101
+ |------|----------|
102
+ | 设计级变更 | 修改架构、接口、数据结构 |
103
+ | 重构完成 | 重构任务完成时 |
104
+ | 代码变更 > 30 行 | 较大规模代码修改 |
105
+ | 提交前 | 代码提交前检查 |
106
+
107
+ ## 校验流程
108
+
109
+ ```
110
+ 1. 运行 change_analyzer.py 自动分析
111
+ 2. 识别变更文件和受影响模块
112
+ 3. 检查文档同步状态
113
+ 4. 评估变更影响
114
+ 5. 输出变更校验报告
115
+ ```
116
+
117
+ ## 校验报告格式
118
+
119
+ ```
120
+ ## 变更校验报告
121
+
122
+ ### 变更概览
123
+ - 变更文件数: N
124
+ - 代码变更行数: +X / -Y
125
+ - 受影响模块: [模块列表]
126
+
127
+ ### 文档同步状态
128
+ - README.md: ✓ 已同步 / ⚠️ 需更新
129
+ - DESIGN.md: ✓ 已同步 / ⚠️ 需更新
130
+
131
+ ### 测试覆盖
132
+ - 测试文件变更: ✓ 有 / ⚠️ 无
133
+
134
+ ### 结论
135
+ 可提交 / 需补充文档后提交
136
+ ```
137
+
138
+ ---