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.
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/bin/install.js +193 -0
- package/bin/uninstall.js +42 -0
- package/config/AGENTS.md +247 -0
- package/config/CLAUDE.md +207 -0
- package/config/settings.example.json +27 -0
- package/output-styles/abyss-cultivator.md +399 -0
- package/package.json +41 -0
- package/skills/SKILL.md +115 -0
- package/skills/ai/SKILL.md +29 -0
- package/skills/ai/agent-dev.md +242 -0
- package/skills/ai/llm-security.md +288 -0
- package/skills/architecture/SKILL.md +41 -0
- package/skills/architecture/api-design.md +225 -0
- package/skills/architecture/caching.md +299 -0
- package/skills/architecture/cloud-native.md +285 -0
- package/skills/architecture/compliance.md +299 -0
- package/skills/architecture/data-security.md +184 -0
- package/skills/architecture/message-queue.md +329 -0
- package/skills/architecture/security-arch.md +210 -0
- package/skills/development/SKILL.md +43 -0
- package/skills/development/cpp.md +246 -0
- package/skills/development/go.md +323 -0
- package/skills/development/java.md +277 -0
- package/skills/development/python.md +288 -0
- package/skills/development/rust.md +313 -0
- package/skills/development/shell.md +313 -0
- package/skills/development/typescript.md +277 -0
- package/skills/devops/SKILL.md +36 -0
- package/skills/devops/cost-optimization.md +272 -0
- package/skills/devops/database.md +217 -0
- package/skills/devops/devsecops.md +198 -0
- package/skills/devops/git-workflow.md +181 -0
- package/skills/devops/observability.md +280 -0
- package/skills/devops/performance.md +273 -0
- package/skills/devops/testing.md +186 -0
- package/skills/gen-docs/SKILL.md +114 -0
- package/skills/gen-docs/scripts/doc_generator.py +491 -0
- package/skills/multi-agent/SKILL.md +268 -0
- package/skills/run_skill.py +88 -0
- package/skills/security/SKILL.md +51 -0
- package/skills/security/blue-team.md +379 -0
- package/skills/security/code-audit.md +265 -0
- package/skills/security/pentest.md +226 -0
- package/skills/security/red-team.md +321 -0
- package/skills/security/threat-intel.md +322 -0
- package/skills/security/vuln-research.md +369 -0
- package/skills/tests/README.md +225 -0
- package/skills/tests/SUMMARY.md +362 -0
- package/skills/tests/__init__.py +3 -0
- package/skills/tests/test_change_analyzer.py +558 -0
- package/skills/tests/test_doc_generator.py +538 -0
- package/skills/tests/test_module_scanner.py +376 -0
- package/skills/tests/test_quality_checker.py +516 -0
- package/skills/tests/test_security_scanner.py +426 -0
- package/skills/verify-change/SKILL.md +138 -0
- package/skills/verify-change/scripts/change_analyzer.py +529 -0
- package/skills/verify-module/SKILL.md +125 -0
- package/skills/verify-module/scripts/module_scanner.py +321 -0
- package/skills/verify-quality/SKILL.md +158 -0
- package/skills/verify-quality/scripts/quality_checker.py +481 -0
- package/skills/verify-security/SKILL.md +141 -0
- package/skills/verify-security/scripts/security_scanner.py +368 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
变更分析器
|
|
4
|
+
分析代码变更,检测文档同步状态,评估变更影响
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
import json
|
|
11
|
+
import subprocess
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import List, Dict, Optional, Set
|
|
15
|
+
from enum import Enum
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ChangeType(Enum):
|
|
19
|
+
ADDED = "added"
|
|
20
|
+
MODIFIED = "modified"
|
|
21
|
+
DELETED = "deleted"
|
|
22
|
+
RENAMED = "renamed"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Severity(Enum):
|
|
26
|
+
ERROR = "error"
|
|
27
|
+
WARNING = "warning"
|
|
28
|
+
INFO = "info"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class FileChange:
|
|
33
|
+
path: str
|
|
34
|
+
change_type: ChangeType
|
|
35
|
+
additions: int = 0
|
|
36
|
+
deletions: int = 0
|
|
37
|
+
is_code: bool = False
|
|
38
|
+
is_doc: bool = False
|
|
39
|
+
is_test: bool = False
|
|
40
|
+
is_config: bool = False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Issue:
|
|
45
|
+
severity: Severity
|
|
46
|
+
message: str
|
|
47
|
+
related_files: List[str] = field(default_factory=list)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class AnalysisResult:
|
|
52
|
+
changes: List[FileChange] = field(default_factory=list)
|
|
53
|
+
issues: List[Issue] = field(default_factory=list)
|
|
54
|
+
affected_modules: Set[str] = field(default_factory=set)
|
|
55
|
+
doc_sync_status: Dict[str, bool] = field(default_factory=dict)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def passed(self) -> bool:
|
|
59
|
+
return not any(i.severity == Severity.ERROR for i in self.issues)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def total_additions(self) -> int:
|
|
63
|
+
return sum(c.additions for c in self.changes)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def total_deletions(self) -> int:
|
|
67
|
+
return sum(c.deletions for c in self.changes)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
CODE_EXTENSIONS = {'.py', '.go', '.rs', '.ts', '.js', '.jsx', '.tsx', '.java', '.c', '.cpp', '.h', '.hpp'}
|
|
71
|
+
DOC_EXTENSIONS = {'.md', '.rst', '.txt', '.adoc'}
|
|
72
|
+
TEST_PATTERNS = ['test_', '_test.', '.test.', 'spec_', '_spec.', '/tests/', '/test/', '/__tests__/']
|
|
73
|
+
CONFIG_FILES = {'package.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'pom.xml', 'Makefile', 'Dockerfile'}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def classify_file(path: str) -> FileChange:
|
|
77
|
+
"""分类文件类型"""
|
|
78
|
+
p = Path(path)
|
|
79
|
+
suffix = p.suffix.lower()
|
|
80
|
+
name = p.name.lower()
|
|
81
|
+
|
|
82
|
+
change = FileChange(path=path, change_type=ChangeType.MODIFIED)
|
|
83
|
+
change.is_code = suffix in CODE_EXTENSIONS
|
|
84
|
+
change.is_doc = suffix in DOC_EXTENSIONS
|
|
85
|
+
change.is_test = any(pattern in path.lower() for pattern in TEST_PATTERNS)
|
|
86
|
+
change.is_config = name in CONFIG_FILES or suffix in {'.yaml', '.yml', '.json', '.toml', '.ini'}
|
|
87
|
+
|
|
88
|
+
return change
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def normalize_path(path: str) -> str:
|
|
92
|
+
"""规范化相对路径"""
|
|
93
|
+
normalized = path.strip()
|
|
94
|
+
|
|
95
|
+
if normalized.startswith('"') and normalized.endswith('"') and len(normalized) >= 2:
|
|
96
|
+
normalized = normalized[1:-1]
|
|
97
|
+
normalized = normalized.replace('\\"', '"').replace('\\\\', '\\')
|
|
98
|
+
|
|
99
|
+
if normalized.startswith("./"):
|
|
100
|
+
normalized = normalized[2:]
|
|
101
|
+
|
|
102
|
+
return normalized
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def parse_name_status_line(line: str) -> Optional[FileChange]:
|
|
106
|
+
"""解析 git diff --name-status 输出行"""
|
|
107
|
+
parts = line.split('\t')
|
|
108
|
+
if len(parts) < 2:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
status_token = parts[0]
|
|
112
|
+
status = status_token[0]
|
|
113
|
+
path = normalize_path(parts[-1])
|
|
114
|
+
|
|
115
|
+
if not path:
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
change = classify_file(path)
|
|
119
|
+
|
|
120
|
+
if status == 'A':
|
|
121
|
+
change.change_type = ChangeType.ADDED
|
|
122
|
+
elif status == 'M':
|
|
123
|
+
change.change_type = ChangeType.MODIFIED
|
|
124
|
+
elif status == 'D':
|
|
125
|
+
change.change_type = ChangeType.DELETED
|
|
126
|
+
elif status == 'R':
|
|
127
|
+
change.change_type = ChangeType.RENAMED
|
|
128
|
+
|
|
129
|
+
return change
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def parse_porcelain_line(line: str) -> Optional[FileChange]:
|
|
133
|
+
"""解析 git status --porcelain 输出行"""
|
|
134
|
+
if len(line) < 3:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
status = line[:2]
|
|
138
|
+
raw_path = line[3:] if len(line) > 3 else ""
|
|
139
|
+
|
|
140
|
+
if not raw_path:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
if " -> " in raw_path:
|
|
144
|
+
raw_path = raw_path.split(" -> ", 1)[1]
|
|
145
|
+
|
|
146
|
+
path = normalize_path(raw_path)
|
|
147
|
+
if not path:
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
change = classify_file(path)
|
|
151
|
+
|
|
152
|
+
if '?' in status or 'A' in status:
|
|
153
|
+
change.change_type = ChangeType.ADDED
|
|
154
|
+
elif 'R' in status:
|
|
155
|
+
change.change_type = ChangeType.RENAMED
|
|
156
|
+
elif 'M' in status:
|
|
157
|
+
change.change_type = ChangeType.MODIFIED
|
|
158
|
+
elif 'D' in status:
|
|
159
|
+
change.change_type = ChangeType.DELETED
|
|
160
|
+
|
|
161
|
+
return change
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def is_path_in_module(file_path: str, module: str) -> bool:
|
|
165
|
+
"""判断文件是否属于模块范围"""
|
|
166
|
+
normalized_path = normalize_path(file_path)
|
|
167
|
+
|
|
168
|
+
if module == ".":
|
|
169
|
+
return len(Path(normalized_path).parts) == 1
|
|
170
|
+
|
|
171
|
+
module_prefix = f"{module}/"
|
|
172
|
+
return normalized_path == module or normalized_path.startswith(module_prefix)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_git_changes(base: str = "HEAD~1", target: str = "HEAD") -> List[FileChange]:
|
|
176
|
+
"""获取 Git 变更"""
|
|
177
|
+
changes = []
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
# 获取变更文件列表
|
|
181
|
+
result = subprocess.run(
|
|
182
|
+
["git", "diff", "--name-status", base, target],
|
|
183
|
+
capture_output=True, text=True, check=True
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
for line in result.stdout.splitlines():
|
|
187
|
+
if not line:
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
change = parse_name_status_line(line)
|
|
191
|
+
if change:
|
|
192
|
+
changes.append(change)
|
|
193
|
+
|
|
194
|
+
# 获取行数统计
|
|
195
|
+
stat_result = subprocess.run(
|
|
196
|
+
["git", "diff", "--numstat", base, target],
|
|
197
|
+
capture_output=True, text=True, check=True
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
stat_map = {}
|
|
201
|
+
for line in stat_result.stdout.splitlines():
|
|
202
|
+
if not line:
|
|
203
|
+
continue
|
|
204
|
+
parts = line.split('\t')
|
|
205
|
+
if len(parts) >= 3:
|
|
206
|
+
adds = int(parts[0]) if parts[0] != '-' else 0
|
|
207
|
+
dels = int(parts[1]) if parts[1] != '-' else 0
|
|
208
|
+
stat_map[normalize_path(parts[2])] = (adds, dels)
|
|
209
|
+
|
|
210
|
+
for change in changes:
|
|
211
|
+
if change.path in stat_map:
|
|
212
|
+
change.additions, change.deletions = stat_map[change.path]
|
|
213
|
+
|
|
214
|
+
except subprocess.CalledProcessError:
|
|
215
|
+
pass
|
|
216
|
+
except FileNotFoundError:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
return changes
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def get_staged_changes() -> List[FileChange]:
|
|
223
|
+
"""获取暂存区变更"""
|
|
224
|
+
changes = []
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
result = subprocess.run(
|
|
228
|
+
["git", "diff", "--cached", "--name-status"],
|
|
229
|
+
capture_output=True, text=True, check=True
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
for line in result.stdout.splitlines():
|
|
233
|
+
if not line:
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
change = parse_name_status_line(line)
|
|
237
|
+
if change:
|
|
238
|
+
changes.append(change)
|
|
239
|
+
|
|
240
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
return changes
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def get_working_changes() -> List[FileChange]:
|
|
247
|
+
"""获取工作区变更"""
|
|
248
|
+
changes = []
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
result = subprocess.run(
|
|
252
|
+
["git", "status", "--porcelain"],
|
|
253
|
+
capture_output=True, text=True, check=True
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
for line in result.stdout.splitlines():
|
|
257
|
+
if not line:
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
change = parse_porcelain_line(line)
|
|
261
|
+
if change:
|
|
262
|
+
changes.append(change)
|
|
263
|
+
|
|
264
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
return changes
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def identify_affected_modules(changes: List[FileChange]) -> Set[str]:
|
|
271
|
+
"""识别受影响的模块"""
|
|
272
|
+
modules = set()
|
|
273
|
+
|
|
274
|
+
for change in changes:
|
|
275
|
+
normalized_path = normalize_path(change.path)
|
|
276
|
+
parts = Path(normalized_path).parts
|
|
277
|
+
|
|
278
|
+
if len(parts) == 1:
|
|
279
|
+
modules.add(".")
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
# 查找模块边界(包含 README.md 或 DESIGN.md 的目录)
|
|
283
|
+
for i in range(len(parts)):
|
|
284
|
+
potential_module = Path(*parts[:i+1])
|
|
285
|
+
if (potential_module / "README.md").exists() or (potential_module / "DESIGN.md").exists():
|
|
286
|
+
modules.add(str(potential_module))
|
|
287
|
+
break
|
|
288
|
+
else:
|
|
289
|
+
# 使用顶层目录作为模块
|
|
290
|
+
if len(parts) > 1:
|
|
291
|
+
modules.add(parts[0])
|
|
292
|
+
|
|
293
|
+
return modules
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def check_doc_sync(changes: List[FileChange], modules: Set[str]) -> tuple[Dict[str, bool], List[Issue]]:
|
|
297
|
+
"""检查文档同步状态"""
|
|
298
|
+
doc_status = {}
|
|
299
|
+
issues = []
|
|
300
|
+
|
|
301
|
+
code_changes = [c for c in changes if c.is_code and c.change_type != ChangeType.DELETED]
|
|
302
|
+
doc_changes = {normalize_path(c.path) for c in changes if c.is_doc}
|
|
303
|
+
|
|
304
|
+
# 检查每个模块
|
|
305
|
+
for module in modules:
|
|
306
|
+
module_path = Path(module)
|
|
307
|
+
readme = normalize_path(str(module_path / "README.md"))
|
|
308
|
+
design = normalize_path(str(module_path / "DESIGN.md"))
|
|
309
|
+
|
|
310
|
+
# 检查模块内是否有代码变更
|
|
311
|
+
module_code_changes = [c for c in code_changes if is_path_in_module(c.path, module)]
|
|
312
|
+
|
|
313
|
+
if module_code_changes:
|
|
314
|
+
# 检查是否有对应的文档更新
|
|
315
|
+
readme_updated = readme in doc_changes
|
|
316
|
+
design_updated = design in doc_changes
|
|
317
|
+
|
|
318
|
+
# 计算变更规模
|
|
319
|
+
total_changes = sum(c.additions + c.deletions for c in module_code_changes)
|
|
320
|
+
|
|
321
|
+
if total_changes > 50 and not design_updated:
|
|
322
|
+
issues.append(Issue(
|
|
323
|
+
severity=Severity.WARNING,
|
|
324
|
+
message=f"模块 {module} 有较大代码变更 ({total_changes} 行),但 DESIGN.md 未更新",
|
|
325
|
+
related_files=[c.path for c in module_code_changes]
|
|
326
|
+
))
|
|
327
|
+
doc_status[f"{module}/DESIGN.md"] = False
|
|
328
|
+
else:
|
|
329
|
+
doc_status[f"{module}/DESIGN.md"] = True
|
|
330
|
+
|
|
331
|
+
# 新增文件检查
|
|
332
|
+
new_files = [c for c in module_code_changes if c.change_type == ChangeType.ADDED]
|
|
333
|
+
if new_files and not readme_updated:
|
|
334
|
+
issues.append(Issue(
|
|
335
|
+
severity=Severity.INFO,
|
|
336
|
+
message=f"模块 {module} 新增了文件,建议更新 README.md",
|
|
337
|
+
related_files=[c.path for c in new_files]
|
|
338
|
+
))
|
|
339
|
+
|
|
340
|
+
return doc_status, issues
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def analyze_impact(changes: List[FileChange]) -> List[Issue]:
|
|
344
|
+
"""分析变更影响"""
|
|
345
|
+
issues = []
|
|
346
|
+
|
|
347
|
+
# 检查是否只改代码不改测试
|
|
348
|
+
code_changes = [c for c in changes if c.is_code and not c.is_test]
|
|
349
|
+
test_changes = [c for c in changes if c.is_test]
|
|
350
|
+
|
|
351
|
+
if code_changes and not test_changes:
|
|
352
|
+
total_code_changes = sum(c.additions + c.deletions for c in code_changes)
|
|
353
|
+
if total_code_changes > 30:
|
|
354
|
+
issues.append(Issue(
|
|
355
|
+
severity=Severity.WARNING,
|
|
356
|
+
message=f"代码变更 {total_code_changes} 行,但没有对应的测试更新",
|
|
357
|
+
related_files=[c.path for c in code_changes]
|
|
358
|
+
))
|
|
359
|
+
|
|
360
|
+
# 检查配置文件变更
|
|
361
|
+
config_changes = [c for c in changes if c.is_config]
|
|
362
|
+
if config_changes:
|
|
363
|
+
issues.append(Issue(
|
|
364
|
+
severity=Severity.INFO,
|
|
365
|
+
message="配置文件有变更,请确认是否需要更新文档",
|
|
366
|
+
related_files=[c.path for c in config_changes]
|
|
367
|
+
))
|
|
368
|
+
|
|
369
|
+
# 检查删除操作
|
|
370
|
+
deleted = [c for c in changes if c.change_type == ChangeType.DELETED]
|
|
371
|
+
if deleted:
|
|
372
|
+
issues.append(Issue(
|
|
373
|
+
severity=Severity.INFO,
|
|
374
|
+
message=f"删除了 {len(deleted)} 个文件,请确认相关引用已清理",
|
|
375
|
+
related_files=[c.path for c in deleted]
|
|
376
|
+
))
|
|
377
|
+
|
|
378
|
+
return issues
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def analyze_changes(mode: str = "working") -> AnalysisResult:
|
|
382
|
+
"""分析变更"""
|
|
383
|
+
result = AnalysisResult()
|
|
384
|
+
|
|
385
|
+
# 获取变更
|
|
386
|
+
if mode == "staged":
|
|
387
|
+
result.changes = get_staged_changes()
|
|
388
|
+
elif mode == "committed":
|
|
389
|
+
result.changes = get_git_changes()
|
|
390
|
+
else:
|
|
391
|
+
result.changes = get_working_changes()
|
|
392
|
+
|
|
393
|
+
if not result.changes:
|
|
394
|
+
return result
|
|
395
|
+
|
|
396
|
+
# 识别受影响模块
|
|
397
|
+
result.affected_modules = identify_affected_modules(result.changes)
|
|
398
|
+
|
|
399
|
+
# 检查文档同步
|
|
400
|
+
doc_status, doc_issues = check_doc_sync(result.changes, result.affected_modules)
|
|
401
|
+
result.doc_sync_status = doc_status
|
|
402
|
+
result.issues.extend(doc_issues)
|
|
403
|
+
|
|
404
|
+
# 分析影响
|
|
405
|
+
impact_issues = analyze_impact(result.changes)
|
|
406
|
+
result.issues.extend(impact_issues)
|
|
407
|
+
|
|
408
|
+
return result
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def format_report(result: AnalysisResult, verbose: bool = False) -> str:
|
|
412
|
+
"""格式化分析报告"""
|
|
413
|
+
lines = []
|
|
414
|
+
lines.append("=" * 60)
|
|
415
|
+
lines.append("变更分析报告")
|
|
416
|
+
lines.append("=" * 60)
|
|
417
|
+
|
|
418
|
+
lines.append(f"\n变更文件: {len(result.changes)}")
|
|
419
|
+
lines.append(f"新增行数: +{result.total_additions}")
|
|
420
|
+
lines.append(f"删除行数: -{result.total_deletions}")
|
|
421
|
+
lines.append(f"受影响模块: {', '.join(result.affected_modules) or '无'}")
|
|
422
|
+
lines.append(f"分析结果: {'✓ 通过' if result.passed else '✗ 需要关注'}")
|
|
423
|
+
|
|
424
|
+
if result.changes and verbose:
|
|
425
|
+
lines.append("\n" + "-" * 40)
|
|
426
|
+
lines.append("变更文件列表:")
|
|
427
|
+
lines.append("-" * 40)
|
|
428
|
+
|
|
429
|
+
type_icons = {
|
|
430
|
+
ChangeType.ADDED: "➕",
|
|
431
|
+
ChangeType.MODIFIED: "📝",
|
|
432
|
+
ChangeType.DELETED: "➖",
|
|
433
|
+
ChangeType.RENAMED: "📋"
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
for change in result.changes:
|
|
437
|
+
icon = type_icons[change.change_type]
|
|
438
|
+
tags = []
|
|
439
|
+
if change.is_code:
|
|
440
|
+
tags.append("代码")
|
|
441
|
+
if change.is_doc:
|
|
442
|
+
tags.append("文档")
|
|
443
|
+
if change.is_test:
|
|
444
|
+
tags.append("测试")
|
|
445
|
+
if change.is_config:
|
|
446
|
+
tags.append("配置")
|
|
447
|
+
|
|
448
|
+
tag_str = f" [{', '.join(tags)}]" if tags else ""
|
|
449
|
+
lines.append(f" {icon} {change.path}{tag_str} (+{change.additions}/-{change.deletions})")
|
|
450
|
+
|
|
451
|
+
if result.issues:
|
|
452
|
+
lines.append("\n" + "-" * 40)
|
|
453
|
+
lines.append("问题与建议:")
|
|
454
|
+
lines.append("-" * 40)
|
|
455
|
+
|
|
456
|
+
severity_icons = {"error": "✗", "warning": "⚠", "info": "ℹ"}
|
|
457
|
+
|
|
458
|
+
for issue in result.issues:
|
|
459
|
+
icon = severity_icons[issue.severity.value]
|
|
460
|
+
lines.append(f"\n {icon} [{issue.severity.value.upper()}] {issue.message}")
|
|
461
|
+
if verbose and issue.related_files:
|
|
462
|
+
for f in issue.related_files[:5]:
|
|
463
|
+
lines.append(f" - {f}")
|
|
464
|
+
if len(issue.related_files) > 5:
|
|
465
|
+
lines.append(f" ... 及其他 {len(issue.related_files) - 5} 个文件")
|
|
466
|
+
|
|
467
|
+
if result.doc_sync_status:
|
|
468
|
+
lines.append("\n" + "-" * 40)
|
|
469
|
+
lines.append("文档同步状态:")
|
|
470
|
+
lines.append("-" * 40)
|
|
471
|
+
|
|
472
|
+
for doc, synced in result.doc_sync_status.items():
|
|
473
|
+
icon = "✓" if synced else "✗"
|
|
474
|
+
lines.append(f" {icon} {doc}")
|
|
475
|
+
|
|
476
|
+
lines.append("\n" + "=" * 60)
|
|
477
|
+
return "\n".join(lines)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def main():
|
|
481
|
+
import argparse
|
|
482
|
+
|
|
483
|
+
parser = argparse.ArgumentParser(description="变更分析器")
|
|
484
|
+
parser.add_argument("--mode", choices=["working", "staged", "committed"],
|
|
485
|
+
default="working", help="分析模式")
|
|
486
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="详细输出")
|
|
487
|
+
parser.add_argument("--json", action="store_true", help="JSON 格式输出")
|
|
488
|
+
|
|
489
|
+
args = parser.parse_args()
|
|
490
|
+
|
|
491
|
+
result = analyze_changes(args.mode)
|
|
492
|
+
|
|
493
|
+
if args.json:
|
|
494
|
+
output = {
|
|
495
|
+
"passed": result.passed,
|
|
496
|
+
"total_additions": result.total_additions,
|
|
497
|
+
"total_deletions": result.total_deletions,
|
|
498
|
+
"affected_modules": list(result.affected_modules),
|
|
499
|
+
"changes": [
|
|
500
|
+
{
|
|
501
|
+
"path": c.path,
|
|
502
|
+
"type": c.change_type.value,
|
|
503
|
+
"additions": c.additions,
|
|
504
|
+
"deletions": c.deletions,
|
|
505
|
+
"is_code": c.is_code,
|
|
506
|
+
"is_doc": c.is_doc,
|
|
507
|
+
"is_test": c.is_test
|
|
508
|
+
}
|
|
509
|
+
for c in result.changes
|
|
510
|
+
],
|
|
511
|
+
"issues": [
|
|
512
|
+
{
|
|
513
|
+
"severity": i.severity.value,
|
|
514
|
+
"message": i.message,
|
|
515
|
+
"related_files": i.related_files
|
|
516
|
+
}
|
|
517
|
+
for i in result.issues
|
|
518
|
+
],
|
|
519
|
+
"doc_sync_status": result.doc_sync_status
|
|
520
|
+
}
|
|
521
|
+
print(json.dumps(output, ensure_ascii=False, indent=2))
|
|
522
|
+
else:
|
|
523
|
+
print(format_report(result, args.verbose))
|
|
524
|
+
|
|
525
|
+
sys.exit(0 if result.passed else 1)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
if __name__ == "__main__":
|
|
529
|
+
main()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify-module
|
|
3
|
+
description: 模块完整性校验关卡。扫描目录结构、检测缺失文档、验证代码与文档同步。当魔尊提到模块校验、文档检查、结构完整性、README检查、DESIGN检查时使用。在新建模块完成时自动触发。
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: false
|
|
6
|
+
allowed-tools: Bash, Read, Glob
|
|
7
|
+
argument-hint: <模块路径>
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# ⚖ 校验关卡 · 模块完整性
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## 核心原则
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
模块 = 代码 + README.md + DESIGN.md
|
|
17
|
+
缺一不可,残缺即异端
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 自动扫描
|
|
21
|
+
|
|
22
|
+
运行扫描脚本(跨平台):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 在 verify-module 目录下运行(推荐)
|
|
26
|
+
python scripts/module_scanner.py <模块路径>
|
|
27
|
+
python scripts/module_scanner.py <模块路径> -v # 详细模式
|
|
28
|
+
python scripts/module_scanner.py <模块路径> --json # JSON 输出
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 校验标准
|
|
32
|
+
|
|
33
|
+
一个完整的模块必须包含:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
module/
|
|
37
|
+
├── README.md # 必须 - 模块是什么、为什么存在
|
|
38
|
+
├── DESIGN.md # 必须 - 设计决策、权衡取舍
|
|
39
|
+
├── src/ # 代码实现
|
|
40
|
+
└── tests/ # 测试用例(如适用)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 检测项
|
|
44
|
+
|
|
45
|
+
### 必须存在
|
|
46
|
+
|
|
47
|
+
| 文件 | 说明 | 缺失后果 |
|
|
48
|
+
|------|------|----------|
|
|
49
|
+
| `README.md` | 模块说明文档 | 🔴 阻断交付 |
|
|
50
|
+
| `DESIGN.md` | 设计决策文档 | 🔴 阻断交付 |
|
|
51
|
+
|
|
52
|
+
### 推荐存在
|
|
53
|
+
|
|
54
|
+
| 文件/目录 | 说明 | 缺失后果 |
|
|
55
|
+
|-----------|------|----------|
|
|
56
|
+
| `tests/` | 测试目录 | 🟠 警告 |
|
|
57
|
+
| `__init__.py` | Python 包标识 | 🟡 提示 |
|
|
58
|
+
| `.gitignore` | Git 忽略配置 | 🔵 信息 |
|
|
59
|
+
|
|
60
|
+
### README.md 必须包含
|
|
61
|
+
|
|
62
|
+
- [ ] **模块名称与定位** — 一句话说明是什么
|
|
63
|
+
- [ ] **存在理由** — 为什么需要这个模块
|
|
64
|
+
- [ ] **核心职责** — 做什么、不做什么
|
|
65
|
+
- [ ] **依赖关系** — 依赖谁、被谁依赖
|
|
66
|
+
- [ ] **快速使用** — 最简示例
|
|
67
|
+
|
|
68
|
+
### DESIGN.md 必须包含
|
|
69
|
+
|
|
70
|
+
- [ ] **设计目标** — 要解决什么问题
|
|
71
|
+
- [ ] **方案选择** — 考虑过哪些方案、为何选当前方案
|
|
72
|
+
- [ ] **关键决策** — 重要的技术决策及理由
|
|
73
|
+
- [ ] **已知限制** — 当前方案的局限性
|
|
74
|
+
- [ ] **变更历史** — 重大变更记录
|
|
75
|
+
|
|
76
|
+
## 自动触发时机
|
|
77
|
+
|
|
78
|
+
| 场景 | 触发条件 |
|
|
79
|
+
|------|----------|
|
|
80
|
+
| 新建模块 | 模块创建完成时 |
|
|
81
|
+
| 模块重构 | 重构完成时 |
|
|
82
|
+
| 提交前 | 代码提交前检查 |
|
|
83
|
+
|
|
84
|
+
## 校验流程
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
1. 运行 module_scanner.py 自动扫描
|
|
88
|
+
2. 检查文件结构是否完整
|
|
89
|
+
3. 检查 README.md 各项是否齐全
|
|
90
|
+
4. 检查 DESIGN.md 各项是否齐全
|
|
91
|
+
5. 检查代码与文档描述是否一致
|
|
92
|
+
6. 输出校验报告
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 校验报告格式
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
## 模块校验报告
|
|
99
|
+
|
|
100
|
+
### 模块: <模块名>
|
|
101
|
+
|
|
102
|
+
✓ 通过 | ✗ 未通过
|
|
103
|
+
|
|
104
|
+
### 文件检查
|
|
105
|
+
- README.md: ✓ 存在 / ✗ 缺失
|
|
106
|
+
- DESIGN.md: ✓ 存在 / ✗ 缺失
|
|
107
|
+
- tests/: ✓ 存在 / ⚠️ 缺失
|
|
108
|
+
|
|
109
|
+
### 内容检查
|
|
110
|
+
- README 完整性: ✓ 完整 / ⚠️ 缺少 [X, Y, Z]
|
|
111
|
+
- DESIGN 完整性: ✓ 完整 / ⚠️ 缺少 [X, Y, Z]
|
|
112
|
+
|
|
113
|
+
### 结论
|
|
114
|
+
可交付 / 需补充后交付
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 快速修复
|
|
118
|
+
|
|
119
|
+
如果缺少文档,可使用文档生成器:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
/gen-docs <模块路径>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|