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.
- package/package.json +2 -2
- package/skills/SKILL.md +24 -16
- package/skills/domains/ai/SKILL.md +2 -2
- package/skills/domains/ai/prompt-and-eval.md +279 -0
- package/skills/domains/architecture/SKILL.md +2 -3
- package/skills/domains/architecture/security-arch.md +87 -0
- package/skills/domains/data-engineering/SKILL.md +188 -26
- package/skills/domains/development/SKILL.md +1 -4
- package/skills/domains/devops/SKILL.md +3 -5
- package/skills/domains/devops/performance.md +63 -0
- package/skills/domains/devops/testing.md +97 -0
- package/skills/domains/frontend-design/SKILL.md +12 -3
- package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
- package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
- package/skills/domains/frontend-design/engineering.md +287 -0
- package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
- package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
- package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
- package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
- package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
- package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
- package/skills/domains/infrastructure/SKILL.md +174 -34
- package/skills/domains/mobile/SKILL.md +211 -21
- package/skills/domains/orchestration/SKILL.md +1 -0
- package/skills/domains/security/SKILL.md +4 -6
- package/skills/domains/security/blue-team.md +57 -0
- package/skills/domains/security/red-team.md +54 -0
- package/skills/domains/security/threat-intel.md +50 -0
- package/skills/orchestration/multi-agent/SKILL.md +195 -46
- package/skills/run_skill.js +134 -0
- package/skills/tools/gen-docs/SKILL.md +6 -4
- package/skills/tools/gen-docs/scripts/doc_generator.js +349 -0
- package/skills/tools/verify-change/SKILL.md +8 -6
- package/skills/tools/verify-change/scripts/change_analyzer.js +270 -0
- package/skills/tools/verify-module/SKILL.md +6 -4
- package/skills/tools/verify-module/scripts/module_scanner.js +145 -0
- package/skills/tools/verify-quality/SKILL.md +5 -3
- package/skills/tools/verify-quality/scripts/quality_checker.js +276 -0
- package/skills/tools/verify-security/SKILL.md +7 -5
- package/skills/tools/verify-security/scripts/security_scanner.js +133 -0
- package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
- package/skills/domains/COVERAGE_PLAN.md +0 -232
- package/skills/domains/ai/model-evaluation.md +0 -790
- package/skills/domains/ai/prompt-engineering.md +0 -703
- package/skills/domains/architecture/compliance.md +0 -299
- package/skills/domains/architecture/data-security.md +0 -184
- package/skills/domains/data-engineering/data-pipeline.md +0 -762
- package/skills/domains/data-engineering/data-quality.md +0 -894
- package/skills/domains/data-engineering/stream-processing.md +0 -791
- package/skills/domains/development/dart.md +0 -963
- package/skills/domains/development/kotlin.md +0 -834
- package/skills/domains/development/php.md +0 -659
- package/skills/domains/development/swift.md +0 -755
- package/skills/domains/devops/e2e-testing.md +0 -914
- package/skills/domains/devops/performance-testing.md +0 -734
- package/skills/domains/devops/testing-strategy.md +0 -667
- package/skills/domains/frontend-design/build-tools.md +0 -743
- package/skills/domains/frontend-design/performance.md +0 -734
- package/skills/domains/frontend-design/testing.md +0 -699
- package/skills/domains/infrastructure/gitops.md +0 -735
- package/skills/domains/infrastructure/iac.md +0 -855
- package/skills/domains/infrastructure/kubernetes.md +0 -1018
- package/skills/domains/mobile/android-dev.md +0 -979
- package/skills/domains/mobile/cross-platform.md +0 -795
- package/skills/domains/mobile/ios-dev.md +0 -931
- package/skills/domains/security/secrets-management.md +0 -834
- package/skills/domains/security/supply-chain.md +0 -931
- package/skills/domains/security/threat-modeling.md +0 -828
- package/skills/run_skill.py +0 -153
- package/skills/tests/README.md +0 -225
- package/skills/tests/SUMMARY.md +0 -362
- package/skills/tests/__init__.py +0 -3
- package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
- package/skills/tests/test_change_analyzer.py +0 -558
- package/skills/tests/test_doc_generator.py +0 -538
- package/skills/tests/test_module_scanner.py +0 -376
- package/skills/tests/test_quality_checker.py +0 -516
- package/skills/tests/test_security_scanner.py +0 -426
- package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
- package/skills/tools/gen-docs/scripts/doc_generator.py +0 -520
- package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
- package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
- package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
- package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
- package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
- package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-security/scripts/security_scanner.py +0 -374
|
@@ -1,529 +0,0 @@
|
|
|
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()
|