claude-code-workflow 6.3.22 → 6.3.24
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/.claude/agents/issue-plan-agent.md +10 -5
- package/.claude/commands/issue/plan.md +1 -1
- package/.claude/skills/review-code/SKILL.md +170 -0
- package/.claude/skills/review-code/phases/actions/action-collect-context.md +139 -0
- package/.claude/skills/review-code/phases/actions/action-complete.md +115 -0
- package/.claude/skills/review-code/phases/actions/action-deep-review.md +302 -0
- package/.claude/skills/review-code/phases/actions/action-generate-report.md +263 -0
- package/.claude/skills/review-code/phases/actions/action-quick-scan.md +164 -0
- package/.claude/skills/review-code/phases/orchestrator.md +251 -0
- package/.claude/skills/review-code/phases/state-manager.md +752 -0
- package/.claude/skills/review-code/phases/state-schema.md +174 -0
- package/.claude/skills/review-code/specs/issue-classification.md +228 -0
- package/.claude/skills/review-code/specs/quality-standards.md +214 -0
- package/.claude/skills/review-code/specs/review-dimensions.md +337 -0
- package/.claude/skills/review-code/specs/rules/architecture-rules.json +63 -0
- package/.claude/skills/review-code/specs/rules/correctness-rules.json +60 -0
- package/.claude/skills/review-code/specs/rules/index.md +140 -0
- package/.claude/skills/review-code/specs/rules/performance-rules.json +59 -0
- package/.claude/skills/review-code/specs/rules/readability-rules.json +60 -0
- package/.claude/skills/review-code/specs/rules/security-rules.json +58 -0
- package/.claude/skills/review-code/specs/rules/testing-rules.json +59 -0
- package/.claude/skills/review-code/templates/issue-template.md +186 -0
- package/.claude/skills/review-code/templates/review-report.md +173 -0
- package/.claude/skills/skill-generator/SKILL.md +56 -17
- package/.claude/skills/skill-generator/templates/autonomous-orchestrator.md +10 -0
- package/.claude/skills/skill-generator/templates/sequential-phase.md +9 -0
- package/.claude/skills/skill-generator/templates/skill-md.md +84 -5
- package/.claude/workflows/cli-templates/schemas/solution-schema.json +3 -3
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +8 -0
- package/package.json +1 -1
- package/.claude/skills/code-reviewer/README.md +0 -340
- package/.claude/skills/code-reviewer/SKILL.md +0 -308
- package/.claude/skills/code-reviewer/phases/01-code-discovery.md +0 -246
- package/.claude/skills/code-reviewer/phases/02-security-analysis.md +0 -442
- package/.claude/skills/code-reviewer/phases/03-best-practices-review.md +0 -36
- package/.claude/skills/code-reviewer/phases/04-report-generation.md +0 -278
- package/.claude/skills/code-reviewer/specs/best-practices-requirements.md +0 -346
- package/.claude/skills/code-reviewer/specs/quality-standards.md +0 -252
- package/.claude/skills/code-reviewer/specs/security-requirements.md +0 -243
- package/.claude/skills/code-reviewer/templates/best-practice-finding.md +0 -234
- package/.claude/skills/code-reviewer/templates/report-template.md +0 -316
- package/.claude/skills/code-reviewer/templates/security-finding.md +0 -161
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Action: Deep Review
|
|
2
|
+
|
|
3
|
+
深入审查指定维度的代码质量。
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
针对单个维度进行深入审查:
|
|
8
|
+
- 逐文件检查
|
|
9
|
+
- 记录发现的问题
|
|
10
|
+
- 提供具体的修复建议
|
|
11
|
+
|
|
12
|
+
## Preconditions
|
|
13
|
+
|
|
14
|
+
- [ ] state.status === 'running'
|
|
15
|
+
- [ ] state.scan_completed === true
|
|
16
|
+
- [ ] 存在未审查的维度
|
|
17
|
+
|
|
18
|
+
## Dimension Focus Areas
|
|
19
|
+
|
|
20
|
+
### Correctness (正确性)
|
|
21
|
+
- 逻辑错误和边界条件
|
|
22
|
+
- Null/undefined 处理
|
|
23
|
+
- 错误处理完整性
|
|
24
|
+
- 类型安全
|
|
25
|
+
- 资源泄漏
|
|
26
|
+
|
|
27
|
+
### Readability (可读性)
|
|
28
|
+
- 命名规范
|
|
29
|
+
- 函数长度和复杂度
|
|
30
|
+
- 代码重复
|
|
31
|
+
- 注释质量
|
|
32
|
+
- 代码组织
|
|
33
|
+
|
|
34
|
+
### Performance (性能)
|
|
35
|
+
- 算法复杂度
|
|
36
|
+
- 不必要的计算
|
|
37
|
+
- 内存使用
|
|
38
|
+
- I/O 效率
|
|
39
|
+
- 缓存策略
|
|
40
|
+
|
|
41
|
+
### Security (安全性)
|
|
42
|
+
- 注入风险 (SQL, XSS, Command)
|
|
43
|
+
- 认证和授权
|
|
44
|
+
- 敏感数据处理
|
|
45
|
+
- 加密使用
|
|
46
|
+
- 依赖安全
|
|
47
|
+
|
|
48
|
+
### Testing (测试)
|
|
49
|
+
- 测试覆盖率
|
|
50
|
+
- 边界条件测试
|
|
51
|
+
- 错误路径测试
|
|
52
|
+
- 测试可维护性
|
|
53
|
+
- Mock 使用
|
|
54
|
+
|
|
55
|
+
### Architecture (架构)
|
|
56
|
+
- 分层结构
|
|
57
|
+
- 依赖方向
|
|
58
|
+
- 单一职责
|
|
59
|
+
- 接口设计
|
|
60
|
+
- 扩展性
|
|
61
|
+
|
|
62
|
+
## Execution
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
async function execute(state, workDir, currentDimension) {
|
|
66
|
+
const context = state.context;
|
|
67
|
+
const dimension = currentDimension;
|
|
68
|
+
const findings = [];
|
|
69
|
+
|
|
70
|
+
// 从外部 JSON 文件加载规则
|
|
71
|
+
const rulesConfig = loadRulesConfig(dimension, workDir);
|
|
72
|
+
const rules = rulesConfig.rules || [];
|
|
73
|
+
const prefix = rulesConfig.prefix || getDimensionPrefix(dimension);
|
|
74
|
+
|
|
75
|
+
// 优先审查高风险区域
|
|
76
|
+
const filesToReview = state.scan_summary?.risk_areas
|
|
77
|
+
?.map(r => r.file)
|
|
78
|
+
?.filter(f => context.files.includes(f)) || context.files;
|
|
79
|
+
|
|
80
|
+
const filesToCheck = [...new Set([
|
|
81
|
+
...filesToReview.slice(0, 20),
|
|
82
|
+
...context.files.slice(0, 30)
|
|
83
|
+
])].slice(0, 50); // 最多50个文件
|
|
84
|
+
|
|
85
|
+
let findingCounter = 1;
|
|
86
|
+
|
|
87
|
+
for (const file of filesToCheck) {
|
|
88
|
+
try {
|
|
89
|
+
const content = Read(file);
|
|
90
|
+
const lines = content.split('\n');
|
|
91
|
+
|
|
92
|
+
// 应用外部规则文件中的规则
|
|
93
|
+
for (const rule of rules) {
|
|
94
|
+
const matches = detectByPattern(content, lines, file, rule);
|
|
95
|
+
for (const match of matches) {
|
|
96
|
+
findings.push({
|
|
97
|
+
id: `${prefix}-${String(findingCounter++).padStart(3, '0')}`,
|
|
98
|
+
severity: rule.severity || match.severity,
|
|
99
|
+
dimension: dimension,
|
|
100
|
+
category: rule.category,
|
|
101
|
+
file: file,
|
|
102
|
+
line: match.line,
|
|
103
|
+
code_snippet: match.snippet,
|
|
104
|
+
description: rule.description,
|
|
105
|
+
recommendation: rule.recommendation,
|
|
106
|
+
fix_example: rule.fixExample
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// 跳过无法读取的文件
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 保存维度发现
|
|
116
|
+
Write(`${workDir}/findings/${dimension}.json`, JSON.stringify(findings, null, 2));
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
stateUpdates: {
|
|
120
|
+
reviewed_dimensions: [...(state.reviewed_dimensions || []), dimension],
|
|
121
|
+
current_dimension: null,
|
|
122
|
+
[`findings.${dimension}`]: findings
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 从外部 JSON 文件加载规则配置
|
|
129
|
+
* 规则文件位于 specs/rules/{dimension}-rules.json
|
|
130
|
+
* @param {string} dimension - 维度名称 (correctness, security, etc.)
|
|
131
|
+
* @param {string} workDir - 工作目录 (用于日志记录)
|
|
132
|
+
* @returns {object} 规则配置对象,包含 rules 数组和 prefix
|
|
133
|
+
*/
|
|
134
|
+
function loadRulesConfig(dimension, workDir) {
|
|
135
|
+
// 规则文件路径:相对于 skill 目录
|
|
136
|
+
const rulesPath = `specs/rules/${dimension}-rules.json`;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const rulesFile = Read(rulesPath);
|
|
140
|
+
const rulesConfig = JSON.parse(rulesFile);
|
|
141
|
+
return rulesConfig;
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.warn(`Failed to load rules for ${dimension}: ${e.message}`);
|
|
144
|
+
// 返回空规则配置,保持向后兼容
|
|
145
|
+
return { rules: [], prefix: getDimensionPrefix(dimension) };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 根据规则的 patternType 检测代码问题
|
|
151
|
+
* 支持的 patternType: regex, includes
|
|
152
|
+
* @param {string} content - 文件内容
|
|
153
|
+
* @param {string[]} lines - 按行分割的内容
|
|
154
|
+
* @param {string} file - 文件路径
|
|
155
|
+
* @param {object} rule - 规则配置对象
|
|
156
|
+
* @returns {Array} 匹配结果数组
|
|
157
|
+
*/
|
|
158
|
+
function detectByPattern(content, lines, file, rule) {
|
|
159
|
+
const matches = [];
|
|
160
|
+
const { pattern, patternType, negativePatterns, caseInsensitive } = rule;
|
|
161
|
+
|
|
162
|
+
if (!pattern) return matches;
|
|
163
|
+
|
|
164
|
+
switch (patternType) {
|
|
165
|
+
case 'regex':
|
|
166
|
+
return detectByRegex(content, lines, pattern, negativePatterns, caseInsensitive);
|
|
167
|
+
|
|
168
|
+
case 'includes':
|
|
169
|
+
return detectByIncludes(content, lines, pattern, negativePatterns);
|
|
170
|
+
|
|
171
|
+
default:
|
|
172
|
+
// 默认使用 includes 模式
|
|
173
|
+
return detectByIncludes(content, lines, pattern, negativePatterns);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 使用正则表达式检测代码问题
|
|
179
|
+
* @param {string} content - 文件完整内容
|
|
180
|
+
* @param {string[]} lines - 按行分割的内容
|
|
181
|
+
* @param {string} pattern - 正则表达式模式
|
|
182
|
+
* @param {string[]} negativePatterns - 排除模式列表
|
|
183
|
+
* @param {boolean} caseInsensitive - 是否忽略大小写
|
|
184
|
+
* @returns {Array} 匹配结果数组
|
|
185
|
+
*/
|
|
186
|
+
function detectByRegex(content, lines, pattern, negativePatterns, caseInsensitive) {
|
|
187
|
+
const matches = [];
|
|
188
|
+
const flags = caseInsensitive ? 'gi' : 'g';
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const regex = new RegExp(pattern, flags);
|
|
192
|
+
let match;
|
|
193
|
+
|
|
194
|
+
while ((match = regex.exec(content)) !== null) {
|
|
195
|
+
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
196
|
+
const lineContent = lines[lineNum - 1] || '';
|
|
197
|
+
|
|
198
|
+
// 检查排除模式 - 如果行内容匹配任一排除模式则跳过
|
|
199
|
+
if (negativePatterns && negativePatterns.length > 0) {
|
|
200
|
+
const shouldExclude = negativePatterns.some(np => {
|
|
201
|
+
try {
|
|
202
|
+
return new RegExp(np).test(lineContent);
|
|
203
|
+
} catch {
|
|
204
|
+
return lineContent.includes(np);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
if (shouldExclude) continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
matches.push({
|
|
211
|
+
line: lineNum,
|
|
212
|
+
snippet: lineContent.trim().substring(0, 100),
|
|
213
|
+
matchedText: match[0]
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
} catch (e) {
|
|
217
|
+
console.warn(`Invalid regex pattern: ${pattern}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return matches;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 使用字符串包含检测代码问题
|
|
225
|
+
* @param {string} content - 文件完整内容 (未使用但保持接口一致)
|
|
226
|
+
* @param {string[]} lines - 按行分割的内容
|
|
227
|
+
* @param {string} pattern - 要查找的字符串
|
|
228
|
+
* @param {string[]} negativePatterns - 排除模式列表
|
|
229
|
+
* @returns {Array} 匹配结果数组
|
|
230
|
+
*/
|
|
231
|
+
function detectByIncludes(content, lines, pattern, negativePatterns) {
|
|
232
|
+
const matches = [];
|
|
233
|
+
|
|
234
|
+
lines.forEach((line, i) => {
|
|
235
|
+
if (line.includes(pattern)) {
|
|
236
|
+
// 检查排除模式 - 如果行内容包含任一排除字符串则跳过
|
|
237
|
+
if (negativePatterns && negativePatterns.length > 0) {
|
|
238
|
+
const shouldExclude = negativePatterns.some(np => line.includes(np));
|
|
239
|
+
if (shouldExclude) return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
matches.push({
|
|
243
|
+
line: i + 1,
|
|
244
|
+
snippet: line.trim().substring(0, 100),
|
|
245
|
+
matchedText: pattern
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return matches;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 获取维度前缀(作为规则文件不存在时的备用)
|
|
255
|
+
* @param {string} dimension - 维度名称
|
|
256
|
+
* @returns {string} 4字符前缀
|
|
257
|
+
*/
|
|
258
|
+
function getDimensionPrefix(dimension) {
|
|
259
|
+
const prefixes = {
|
|
260
|
+
correctness: 'CORR',
|
|
261
|
+
readability: 'READ',
|
|
262
|
+
performance: 'PERF',
|
|
263
|
+
security: 'SEC',
|
|
264
|
+
testing: 'TEST',
|
|
265
|
+
architecture: 'ARCH'
|
|
266
|
+
};
|
|
267
|
+
return prefixes[dimension] || 'MISC';
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## State Updates
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
return {
|
|
275
|
+
stateUpdates: {
|
|
276
|
+
reviewed_dimensions: [...state.reviewed_dimensions, currentDimension],
|
|
277
|
+
current_dimension: null,
|
|
278
|
+
findings: {
|
|
279
|
+
...state.findings,
|
|
280
|
+
[currentDimension]: newFindings
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Output
|
|
287
|
+
|
|
288
|
+
- **File**: `findings/{dimension}.json`
|
|
289
|
+
- **Location**: `${workDir}/findings/`
|
|
290
|
+
- **Format**: JSON array of Finding objects
|
|
291
|
+
|
|
292
|
+
## Error Handling
|
|
293
|
+
|
|
294
|
+
| Error Type | Recovery |
|
|
295
|
+
|------------|----------|
|
|
296
|
+
| 文件读取失败 | 跳过该文件,记录警告 |
|
|
297
|
+
| 规则执行错误 | 跳过该规则,继续其他规则 |
|
|
298
|
+
|
|
299
|
+
## Next Actions
|
|
300
|
+
|
|
301
|
+
- 还有未审查维度: 继续 action-deep-review
|
|
302
|
+
- 所有维度完成: action-generate-report
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Action: Generate Report
|
|
2
|
+
|
|
3
|
+
汇总所有发现,生成结构化审查报告。
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
生成最终的代码审查报告:
|
|
8
|
+
- 汇总所有维度的发现
|
|
9
|
+
- 按严重程度排序
|
|
10
|
+
- 提供统计摘要
|
|
11
|
+
- 输出 Markdown 格式报告
|
|
12
|
+
|
|
13
|
+
## Preconditions
|
|
14
|
+
|
|
15
|
+
- [ ] state.status === 'running'
|
|
16
|
+
- [ ] 所有维度已审查完成 (reviewed_dimensions.length === 6)
|
|
17
|
+
|
|
18
|
+
## Execution
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
async function execute(state, workDir) {
|
|
22
|
+
const context = state.context;
|
|
23
|
+
const findings = state.findings;
|
|
24
|
+
|
|
25
|
+
// 1. 汇总所有发现
|
|
26
|
+
const allFindings = [
|
|
27
|
+
...findings.correctness,
|
|
28
|
+
...findings.readability,
|
|
29
|
+
...findings.performance,
|
|
30
|
+
...findings.security,
|
|
31
|
+
...findings.testing,
|
|
32
|
+
...findings.architecture
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// 2. 按严重程度排序
|
|
36
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
37
|
+
allFindings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
38
|
+
|
|
39
|
+
// 3. 统计
|
|
40
|
+
const stats = {
|
|
41
|
+
total_issues: allFindings.length,
|
|
42
|
+
critical: allFindings.filter(f => f.severity === 'critical').length,
|
|
43
|
+
high: allFindings.filter(f => f.severity === 'high').length,
|
|
44
|
+
medium: allFindings.filter(f => f.severity === 'medium').length,
|
|
45
|
+
low: allFindings.filter(f => f.severity === 'low').length,
|
|
46
|
+
info: allFindings.filter(f => f.severity === 'info').length,
|
|
47
|
+
by_dimension: {
|
|
48
|
+
correctness: findings.correctness.length,
|
|
49
|
+
readability: findings.readability.length,
|
|
50
|
+
performance: findings.performance.length,
|
|
51
|
+
security: findings.security.length,
|
|
52
|
+
testing: findings.testing.length,
|
|
53
|
+
architecture: findings.architecture.length
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// 4. 生成报告
|
|
58
|
+
const report = generateMarkdownReport(context, stats, allFindings, state.scan_summary);
|
|
59
|
+
|
|
60
|
+
// 5. 保存报告
|
|
61
|
+
const reportPath = `${workDir}/review-report.md`;
|
|
62
|
+
Write(reportPath, report);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
stateUpdates: {
|
|
66
|
+
report_generated: true,
|
|
67
|
+
report_path: reportPath,
|
|
68
|
+
summary: {
|
|
69
|
+
...stats,
|
|
70
|
+
review_duration_ms: Date.now() - new Date(state.started_at).getTime()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function generateMarkdownReport(context, stats, findings, scanSummary) {
|
|
77
|
+
const severityEmoji = {
|
|
78
|
+
critical: '🔴',
|
|
79
|
+
high: '🟠',
|
|
80
|
+
medium: '🟡',
|
|
81
|
+
low: '🔵',
|
|
82
|
+
info: '⚪'
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let report = `# Code Review Report
|
|
86
|
+
|
|
87
|
+
## 审查概览
|
|
88
|
+
|
|
89
|
+
| 项目 | 值 |
|
|
90
|
+
|------|------|
|
|
91
|
+
| 目标路径 | \`${context.target_path}\` |
|
|
92
|
+
| 文件数量 | ${context.file_count} |
|
|
93
|
+
| 代码行数 | ${context.total_lines} |
|
|
94
|
+
| 主要语言 | ${context.language} |
|
|
95
|
+
| 框架 | ${context.framework || 'N/A'} |
|
|
96
|
+
|
|
97
|
+
## 问题统计
|
|
98
|
+
|
|
99
|
+
| 严重程度 | 数量 |
|
|
100
|
+
|----------|------|
|
|
101
|
+
| 🔴 Critical | ${stats.critical} |
|
|
102
|
+
| 🟠 High | ${stats.high} |
|
|
103
|
+
| 🟡 Medium | ${stats.medium} |
|
|
104
|
+
| 🔵 Low | ${stats.low} |
|
|
105
|
+
| ⚪ Info | ${stats.info} |
|
|
106
|
+
| **总计** | **${stats.total_issues}** |
|
|
107
|
+
|
|
108
|
+
### 按维度统计
|
|
109
|
+
|
|
110
|
+
| 维度 | 问题数 |
|
|
111
|
+
|------|--------|
|
|
112
|
+
| Correctness (正确性) | ${stats.by_dimension.correctness} |
|
|
113
|
+
| Security (安全性) | ${stats.by_dimension.security} |
|
|
114
|
+
| Performance (性能) | ${stats.by_dimension.performance} |
|
|
115
|
+
| Readability (可读性) | ${stats.by_dimension.readability} |
|
|
116
|
+
| Testing (测试) | ${stats.by_dimension.testing} |
|
|
117
|
+
| Architecture (架构) | ${stats.by_dimension.architecture} |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 高风险区域
|
|
122
|
+
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
if (scanSummary?.risk_areas?.length > 0) {
|
|
126
|
+
report += `| 文件 | 原因 | 优先级 |
|
|
127
|
+
|------|------|--------|
|
|
128
|
+
`;
|
|
129
|
+
for (const area of scanSummary.risk_areas.slice(0, 10)) {
|
|
130
|
+
report += `| \`${area.file}\` | ${area.reason} | ${area.priority} |\n`;
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
report += `未发现明显的高风险区域。\n`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
report += `
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 问题详情
|
|
140
|
+
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
// 按维度分组输出
|
|
144
|
+
const dimensions = ['correctness', 'security', 'performance', 'readability', 'testing', 'architecture'];
|
|
145
|
+
const dimensionNames = {
|
|
146
|
+
correctness: '正确性 (Correctness)',
|
|
147
|
+
security: '安全性 (Security)',
|
|
148
|
+
performance: '性能 (Performance)',
|
|
149
|
+
readability: '可读性 (Readability)',
|
|
150
|
+
testing: '测试 (Testing)',
|
|
151
|
+
architecture: '架构 (Architecture)'
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
for (const dim of dimensions) {
|
|
155
|
+
const dimFindings = findings.filter(f => f.dimension === dim);
|
|
156
|
+
if (dimFindings.length === 0) continue;
|
|
157
|
+
|
|
158
|
+
report += `### ${dimensionNames[dim]}
|
|
159
|
+
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
for (const finding of dimFindings) {
|
|
163
|
+
report += `#### ${severityEmoji[finding.severity]} [${finding.id}] ${finding.category}
|
|
164
|
+
|
|
165
|
+
- **严重程度**: ${finding.severity.toUpperCase()}
|
|
166
|
+
- **文件**: \`${finding.file}\`${finding.line ? `:${finding.line}` : ''}
|
|
167
|
+
- **描述**: ${finding.description}
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
if (finding.code_snippet) {
|
|
171
|
+
report += `
|
|
172
|
+
\`\`\`
|
|
173
|
+
${finding.code_snippet}
|
|
174
|
+
\`\`\`
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
report += `
|
|
179
|
+
**建议**: ${finding.recommendation}
|
|
180
|
+
`;
|
|
181
|
+
|
|
182
|
+
if (finding.fix_example) {
|
|
183
|
+
report += `
|
|
184
|
+
**修复示例**:
|
|
185
|
+
\`\`\`
|
|
186
|
+
${finding.fix_example}
|
|
187
|
+
\`\`\`
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
report += `
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
report += `
|
|
199
|
+
## 审查建议
|
|
200
|
+
|
|
201
|
+
### 必须修复 (Must Fix)
|
|
202
|
+
|
|
203
|
+
${stats.critical + stats.high > 0
|
|
204
|
+
? `发现 ${stats.critical} 个严重问题和 ${stats.high} 个高优先级问题,建议在合并前修复。`
|
|
205
|
+
: '未发现必须立即修复的问题。'}
|
|
206
|
+
|
|
207
|
+
### 建议改进 (Should Fix)
|
|
208
|
+
|
|
209
|
+
${stats.medium > 0
|
|
210
|
+
? `发现 ${stats.medium} 个中等优先级问题,建议在后续迭代中改进。`
|
|
211
|
+
: '代码质量良好,无明显需要改进的地方。'}
|
|
212
|
+
|
|
213
|
+
### 可选优化 (Nice to Have)
|
|
214
|
+
|
|
215
|
+
${stats.low + stats.info > 0
|
|
216
|
+
? `发现 ${stats.low + stats.info} 个低优先级建议,可根据团队规范酌情处理。`
|
|
217
|
+
: '无额外建议。'}
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
*报告生成时间: ${new Date().toISOString()}*
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
return report;
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## State Updates
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
return {
|
|
232
|
+
stateUpdates: {
|
|
233
|
+
report_generated: true,
|
|
234
|
+
report_path: reportPath,
|
|
235
|
+
summary: {
|
|
236
|
+
total_issues: totalCount,
|
|
237
|
+
critical: criticalCount,
|
|
238
|
+
high: highCount,
|
|
239
|
+
medium: mediumCount,
|
|
240
|
+
low: lowCount,
|
|
241
|
+
info: infoCount,
|
|
242
|
+
review_duration_ms: duration
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Output
|
|
249
|
+
|
|
250
|
+
- **File**: `review-report.md`
|
|
251
|
+
- **Location**: `${workDir}/review-report.md`
|
|
252
|
+
- **Format**: Markdown
|
|
253
|
+
|
|
254
|
+
## Error Handling
|
|
255
|
+
|
|
256
|
+
| Error Type | Recovery |
|
|
257
|
+
|------------|----------|
|
|
258
|
+
| 写入失败 | 尝试备用位置 |
|
|
259
|
+
| 模板错误 | 使用简化格式 |
|
|
260
|
+
|
|
261
|
+
## Next Actions
|
|
262
|
+
|
|
263
|
+
- 成功: action-complete
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Action: Quick Scan
|
|
2
|
+
|
|
3
|
+
快速扫描代码,识别高风险区域。
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
进行第一遍快速扫描:
|
|
8
|
+
- 识别复杂度高的文件
|
|
9
|
+
- 标记潜在的高风险区域
|
|
10
|
+
- 发现明显的问题模式
|
|
11
|
+
|
|
12
|
+
## Preconditions
|
|
13
|
+
|
|
14
|
+
- [ ] state.status === 'running'
|
|
15
|
+
- [ ] state.context !== null
|
|
16
|
+
|
|
17
|
+
## Execution
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
async function execute(state, workDir) {
|
|
21
|
+
const context = state.context;
|
|
22
|
+
const riskAreas = [];
|
|
23
|
+
const quickIssues = [];
|
|
24
|
+
|
|
25
|
+
// 1. 扫描每个文件
|
|
26
|
+
for (const file of context.files) {
|
|
27
|
+
try {
|
|
28
|
+
const content = Read(file);
|
|
29
|
+
const lines = content.split('\n');
|
|
30
|
+
|
|
31
|
+
// --- 复杂度检查 ---
|
|
32
|
+
const functionMatches = content.match(/function\s+\w+|=>\s*{|async\s+\w+/g) || [];
|
|
33
|
+
const nestingDepth = Math.max(...lines.map(l => (l.match(/^\s*/)?.[0].length || 0) / 2));
|
|
34
|
+
|
|
35
|
+
if (lines.length > 500 || functionMatches.length > 20 || nestingDepth > 8) {
|
|
36
|
+
riskAreas.push({
|
|
37
|
+
file: file,
|
|
38
|
+
reason: `High complexity: ${lines.length} lines, ${functionMatches.length} functions, depth ${nestingDepth}`,
|
|
39
|
+
priority: 'high'
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// --- 快速问题检测 ---
|
|
44
|
+
|
|
45
|
+
// 安全问题快速检测
|
|
46
|
+
if (content.includes('eval(') || content.includes('innerHTML')) {
|
|
47
|
+
quickIssues.push({
|
|
48
|
+
type: 'security',
|
|
49
|
+
file: file,
|
|
50
|
+
message: 'Potential XSS/injection risk: eval() or innerHTML usage'
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 硬编码密钥检测
|
|
55
|
+
if (/(?:password|secret|api_key|token)\s*[=:]\s*['"][^'"]{8,}/i.test(content)) {
|
|
56
|
+
quickIssues.push({
|
|
57
|
+
type: 'security',
|
|
58
|
+
file: file,
|
|
59
|
+
message: 'Potential hardcoded credential detected'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// TODO/FIXME 检测
|
|
64
|
+
const todoCount = (content.match(/TODO|FIXME|HACK|XXX/gi) || []).length;
|
|
65
|
+
if (todoCount > 5) {
|
|
66
|
+
quickIssues.push({
|
|
67
|
+
type: 'maintenance',
|
|
68
|
+
file: file,
|
|
69
|
+
message: `${todoCount} TODO/FIXME comments found`
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// console.log 检测(生产代码)
|
|
74
|
+
if (!file.includes('test') && !file.includes('spec')) {
|
|
75
|
+
const consoleCount = (content.match(/console\.(log|debug|info)/g) || []).length;
|
|
76
|
+
if (consoleCount > 3) {
|
|
77
|
+
quickIssues.push({
|
|
78
|
+
type: 'readability',
|
|
79
|
+
file: file,
|
|
80
|
+
message: `${consoleCount} console statements (should be removed in production)`
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 长函数检测
|
|
86
|
+
const longFunctions = content.match(/function[^{]+\{[^}]{2000,}\}/g) || [];
|
|
87
|
+
if (longFunctions.length > 0) {
|
|
88
|
+
quickIssues.push({
|
|
89
|
+
type: 'readability',
|
|
90
|
+
file: file,
|
|
91
|
+
message: `${longFunctions.length} long function(s) detected (>50 lines)`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 错误处理检测
|
|
96
|
+
if (content.includes('catch') && content.includes('catch (') && content.match(/catch\s*\([^)]*\)\s*{\s*}/)) {
|
|
97
|
+
quickIssues.push({
|
|
98
|
+
type: 'correctness',
|
|
99
|
+
file: file,
|
|
100
|
+
message: 'Empty catch block detected'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// 跳过无法读取的文件
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 2. 计算复杂度评分
|
|
110
|
+
const complexityScore = Math.min(100, Math.round(
|
|
111
|
+
(riskAreas.length * 10 + quickIssues.length * 5) / context.file_count * 100
|
|
112
|
+
));
|
|
113
|
+
|
|
114
|
+
// 3. 构建扫描摘要
|
|
115
|
+
const scanSummary = {
|
|
116
|
+
risk_areas: riskAreas,
|
|
117
|
+
complexity_score: complexityScore,
|
|
118
|
+
quick_issues: quickIssues
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// 4. 保存扫描结果
|
|
122
|
+
Write(`${workDir}/scan-summary.json`, JSON.stringify(scanSummary, null, 2));
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
stateUpdates: {
|
|
126
|
+
scan_completed: true,
|
|
127
|
+
scan_summary: scanSummary
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## State Updates
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
return {
|
|
137
|
+
stateUpdates: {
|
|
138
|
+
scan_completed: true,
|
|
139
|
+
scan_summary: {
|
|
140
|
+
risk_areas: riskAreas,
|
|
141
|
+
complexity_score: score,
|
|
142
|
+
quick_issues: quickIssues
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Output
|
|
149
|
+
|
|
150
|
+
- **File**: `scan-summary.json`
|
|
151
|
+
- **Location**: `${workDir}/scan-summary.json`
|
|
152
|
+
- **Format**: JSON
|
|
153
|
+
|
|
154
|
+
## Error Handling
|
|
155
|
+
|
|
156
|
+
| Error Type | Recovery |
|
|
157
|
+
|------------|----------|
|
|
158
|
+
| 文件读取失败 | 跳过该文件,继续扫描 |
|
|
159
|
+
| 编码问题 | 以二进制跳过 |
|
|
160
|
+
|
|
161
|
+
## Next Actions
|
|
162
|
+
|
|
163
|
+
- 成功: action-deep-review (开始逐维度审查)
|
|
164
|
+
- 风险区域过多 (>20): 可询问用户是否缩小范围
|