soloforge 1.1.67 → 1.1.69
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/README.md +33 -31
- package/dist/context/adapters/shared/integration_guide.js +1 -1
- package/dist/context/templates/observed_consumption.d.ts.map +1 -1
- package/dist/context/templates/observed_consumption.js +0 -1
- package/dist/context/templates/observed_consumption.js.map +1 -1
- package/dist/context/templates/scaffolder.js +6 -7
- package/dist/context/templates/scaffolder.js.map +1 -1
- package/dist/context/templates/standard_asset_coverage.js +2 -2
- package/dist/context/templates/standard_asset_coverage.js.map +1 -1
- package/dist/core/adversarial_review_store.d.ts +3 -0
- package/dist/core/adversarial_review_store.d.ts.map +1 -1
- package/dist/core/adversarial_review_store.js.map +1 -1
- package/dist/core/task_context/manager.d.ts +3 -24
- package/dist/core/task_context/manager.d.ts.map +1 -1
- package/dist/core/task_context/manager.js +1 -46
- package/dist/core/task_context/manager.js.map +1 -1
- package/dist/core/task_context/manager_setters.d.ts +4 -26
- package/dist/core/task_context/manager_setters.d.ts.map +1 -1
- package/dist/core/task_context/manager_setters.js +4 -115
- package/dist/core/task_context/manager_setters.js.map +1 -1
- package/dist/core/task_context/stage_fact_ownership.d.ts +1 -1
- package/dist/core/task_context/stage_fact_ownership.d.ts.map +1 -1
- package/dist/core/task_context/stage_fact_ownership.js +4 -7
- package/dist/core/task_context/stage_fact_ownership.js.map +1 -1
- package/dist/core/task_context.d.ts +1 -2
- package/dist/core/task_context.d.ts.map +1 -1
- package/dist/core/task_context.js +1 -2
- package/dist/core/task_context.js.map +1 -1
- package/dist/domain/asset_registry/template_asset_contract_registry.js +5 -5
- package/dist/domain/asset_registry/template_asset_contract_registry.js.map +1 -1
- package/dist/domain/design/engine.d.ts.map +1 -1
- package/dist/domain/design/engine.js +3 -3
- package/dist/domain/design/engine.js.map +1 -1
- package/dist/domain/engine_helpers.d.ts +5 -0
- package/dist/domain/engine_helpers.d.ts.map +1 -1
- package/dist/domain/engine_helpers.js +41 -0
- package/dist/domain/engine_helpers.js.map +1 -1
- package/dist/domain/workflow/next_action_planner.d.ts +1 -1
- package/dist/domain/workflow/next_action_planner.d.ts.map +1 -1
- package/dist/domain/workflow/next_action_planner.js +1 -10
- package/dist/domain/workflow/next_action_planner.js.map +1 -1
- package/dist/gate/contracts/control_plane_contract.d.ts.map +1 -1
- package/dist/gate/contracts/control_plane_contract.js +0 -10
- package/dist/gate/contracts/control_plane_contract.js.map +1 -1
- package/dist/gate/contracts/tool_invocation_contract_registry.js +1 -1
- package/dist/gate/contracts/tool_invocation_contract_registry.js.map +1 -1
- package/dist/gate/release/gate_checks/checkMainlineConsumption.d.ts.map +1 -1
- package/dist/gate/release/gate_checks/checkMainlineConsumption.js +3 -2
- package/dist/gate/release/gate_checks/checkMainlineConsumption.js.map +1 -1
- package/dist/gate/scope_controller.d.ts.map +1 -1
- package/dist/gate/scope_controller.js +3 -4
- package/dist/gate/scope_controller.js.map +1 -1
- package/dist/server/tools/sf_doctor.d.ts +7 -0
- package/dist/server/tools/sf_doctor.d.ts.map +1 -1
- package/dist/server/tools/sf_doctor.js +43 -1
- package/dist/server/tools/sf_doctor.js.map +1 -1
- package/dist/server/tools/sf_work.d.ts +8 -1
- package/dist/server/tools/sf_work.d.ts.map +1 -1
- package/dist/server/tools/sf_work.js +34 -5
- package/dist/server/tools/sf_work.js.map +1 -1
- package/dist/types/pipeline_types.d.ts +4 -31
- package/dist/types/pipeline_types.d.ts.map +1 -1
- package/dist/verify/contracts/runtime_state_recovery_registry.d.ts +2 -2
- package/dist/verify/contracts/runtime_state_recovery_registry.d.ts.map +1 -1
- package/dist/verify/contracts/runtime_state_recovery_registry.js +2 -9
- package/dist/verify/contracts/runtime_state_recovery_registry.js.map +1 -1
- package/dist/verify/index.d.ts +0 -4
- package/dist/verify/index.d.ts.map +1 -1
- package/dist/verify/index.js +0 -2
- package/dist/verify/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/core/task_context/phase_directive.d.ts +0 -20
- package/dist/core/task_context/phase_directive.d.ts.map +0 -1
- package/dist/core/task_context/phase_directive.js +0 -64
- package/dist/core/task_context/phase_directive.js.map +0 -1
- package/dist/verify/audit/code_reviewer.d.ts +0 -78
- package/dist/verify/audit/code_reviewer.d.ts.map +0 -1
- package/dist/verify/audit/code_reviewer.js +0 -647
- package/dist/verify/audit/code_reviewer.js.map +0 -1
- package/dist/verify/audit/confidence_scorer.d.ts +0 -65
- package/dist/verify/audit/confidence_scorer.d.ts.map +0 -1
- package/dist/verify/audit/confidence_scorer.js +0 -73
- package/dist/verify/audit/confidence_scorer.js.map +0 -1
- package/dist/verify/audit/debt_tracker.d.ts +0 -85
- package/dist/verify/audit/debt_tracker.d.ts.map +0 -1
- package/dist/verify/audit/debt_tracker.js +0 -244
- package/dist/verify/audit/debt_tracker.js.map +0 -1
- package/dist/verify/audit/evolver.d.ts +0 -103
- package/dist/verify/audit/evolver.d.ts.map +0 -1
- package/dist/verify/audit/evolver.js +0 -420
- package/dist/verify/audit/evolver.js.map +0 -1
|
@@ -1,647 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Code Reviewer — 审计层模块。
|
|
3
|
-
*
|
|
4
|
-
* 职责边界:
|
|
5
|
-
* - 负责:ReviewTrustLevel 等 审计层职责
|
|
6
|
-
* - 不负责:不属于本模块的职责由对应模块承担
|
|
7
|
-
*
|
|
8
|
-
* 被谁调用:发布门禁、质量检查
|
|
9
|
-
* 调用谁:promises、node:path、index、debt_tracker、index_manager、core_engineering_principles
|
|
10
|
-
*
|
|
11
|
-
* 数据流:审计输入(代码/配置) → 检查 → 评分/报告
|
|
12
|
-
* 持久化:读写本地文件(详见代码内路径)
|
|
13
|
-
*/
|
|
14
|
-
import fs from "node:fs/promises";
|
|
15
|
-
import path from "node:path";
|
|
16
|
-
import { checkSimplicityFirst, checkSurgicalChanges } from "./core_engineering_principles.js";
|
|
17
|
-
import { debugLog } from "../../shared/logger.js";
|
|
18
|
-
debugLog("code_reviewer: 代码审查引擎模块已加载");
|
|
19
|
-
/** 摘要缓存实例(模块级别,跨任务持久) */
|
|
20
|
-
const summaryCache = new Map();
|
|
21
|
-
const MAX_CACHE_ENTRIES = 500;
|
|
22
|
-
/**
|
|
23
|
-
* 为白盒审查项生成压缩摘要并缓存。
|
|
24
|
-
* 摘要提取: 文件名 + 维度 + 规则描述,压缩为一句话。
|
|
25
|
-
*
|
|
26
|
-
* @param taskId - 任务 ID
|
|
27
|
-
* @param findings - 审查发现列表
|
|
28
|
-
* @param classified - 信任分级后的发现列表
|
|
29
|
-
*/
|
|
30
|
-
function cacheWhiteBoxSummary(taskId, findings, classified) {
|
|
31
|
-
const entries = [];
|
|
32
|
-
for (let i = 0; i < classified.length; i++) {
|
|
33
|
-
if (classified[i].trust_level === "white_box") {
|
|
34
|
-
const f = findings[i];
|
|
35
|
-
const summary = `[${f.file.split("/").pop()}:${f.line ?? "?"}] ${f.dimension}: ${f.title}`;
|
|
36
|
-
entries.push({
|
|
37
|
-
original_index: i,
|
|
38
|
-
file: f.file,
|
|
39
|
-
compressed_summary: summary,
|
|
40
|
-
cached_at: Date.now(),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// 防止内存泄漏: 限制缓存大小
|
|
45
|
-
if (summaryCache.size > MAX_CACHE_ENTRIES) {
|
|
46
|
-
const oldest = [...summaryCache.entries()]
|
|
47
|
-
.sort((a, b) => a[1][0]?.cached_at ?? 0 - (b[1][0]?.cached_at ?? 0))[0];
|
|
48
|
-
if (oldest)
|
|
49
|
-
summaryCache.delete(oldest[0]);
|
|
50
|
-
}
|
|
51
|
-
if (entries.length > 0) {
|
|
52
|
-
summaryCache.set(taskId, entries);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* 回溯渲染: 当抽检哨兵升级白盒为灰盒时,渲染前置折叠任务的摘要流。
|
|
57
|
-
* 返回所有已缓存的白盒摘要,供人类快速回溯。
|
|
58
|
-
*
|
|
59
|
-
* @param taskId - 任务 ID
|
|
60
|
-
* @returns 白盒摘要列表,无缓存时返回空数组
|
|
61
|
-
* @deprecated 无运行时消费者,保留备用
|
|
62
|
-
*/
|
|
63
|
-
/**
|
|
64
|
-
* 机制 3: 对审查发现进行信任分级路由。
|
|
65
|
-
* 白盒 -> 建议跳过(CRUD/符合模式),灰盒 -> 建议审查(核心算法/安全),黑盒 -> 强制审查(非标逻辑)
|
|
66
|
-
*
|
|
67
|
-
* @param findings - 审查发现列表
|
|
68
|
-
* @param knowledgeIndex - 可选的知识库索引管理器
|
|
69
|
-
* @param changedFiles - 可选的变更文件列表
|
|
70
|
-
* @param fileContents - 可选的文件内容映射
|
|
71
|
-
* @param taskId - 可选的任务 ID,用于摘要缓存
|
|
72
|
-
* @returns 分级后的发现列表、汇总摘要和抽检回溯
|
|
73
|
-
*/
|
|
74
|
-
function classifyFindingsByTrust(findings, knowledgeIndex, fileContents, taskId) {
|
|
75
|
-
// 获取知识库中的 review_rule 条目用于匹配
|
|
76
|
-
const knowledgeRules = knowledgeIndex
|
|
77
|
-
? knowledgeIndex.query({ type: "review_rule", limit: 50 })
|
|
78
|
-
: [];
|
|
79
|
-
let whiteCount = 0;
|
|
80
|
-
let grayCount = 0;
|
|
81
|
-
let blackCount = 0;
|
|
82
|
-
const classified = findings.map((f) => {
|
|
83
|
-
const { level, reason } = determineTrustLevel(f, knowledgeRules, fileContents);
|
|
84
|
-
if (level === "white_box")
|
|
85
|
-
whiteCount++;
|
|
86
|
-
else if (level === "gray_box")
|
|
87
|
-
grayCount++;
|
|
88
|
-
else
|
|
89
|
-
blackCount++;
|
|
90
|
-
return { ...f, trust_level: level, trust_reason: reason };
|
|
91
|
-
});
|
|
92
|
-
// 抽检哨兵: 每处理 10 个白盒项,强制随机升级 1 个为灰盒,防止自动化偏见
|
|
93
|
-
// 维度5: 升级时同时渲染前置白盒摘要流,供人工回溯
|
|
94
|
-
let spotCheckBacktrace;
|
|
95
|
-
if (whiteCount >= 10) {
|
|
96
|
-
const whiteIndices = classified
|
|
97
|
-
.map((f, i) => f.trust_level === "white_box" ? i : -1)
|
|
98
|
-
.filter((i) => i >= 0);
|
|
99
|
-
if (whiteIndices.length > 0) {
|
|
100
|
-
const spotIdx = whiteIndices[Math.floor(Math.random() * whiteIndices.length)];
|
|
101
|
-
classified[spotIdx] = {
|
|
102
|
-
...classified[spotIdx],
|
|
103
|
-
trust_level: "gray_box",
|
|
104
|
-
trust_reason: "⚡ 抽检哨兵: 随机抽检项,防止模式误判。请确认系统未产生错误分类",
|
|
105
|
-
};
|
|
106
|
-
whiteCount--;
|
|
107
|
-
grayCount++;
|
|
108
|
-
// 回溯: 渲染所有白盒摘要
|
|
109
|
-
spotCheckBacktrace = classified
|
|
110
|
-
.filter((c) => c.trust_level === "white_box")
|
|
111
|
-
.map((c) => {
|
|
112
|
-
const orig = findings[classified.indexOf(c)];
|
|
113
|
-
return `[${orig.file.split("/").pop()}:${orig.line ?? "?"}] ${orig.dimension}: ${orig.title}`;
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
// 维度5: 缓存白盒摘要(供未来抽检回溯使用)
|
|
118
|
-
cacheWhiteBoxSummary(taskId ?? "default", findings, classified);
|
|
119
|
-
const recommendation = blackCount > 0
|
|
120
|
-
? `强制审查 ${blackCount} 项黑盒发现(无知识库支撑的非标逻辑)`
|
|
121
|
-
: grayCount > 0
|
|
122
|
-
? `建议审查 ${grayCount} 项灰盒发现(核心算法/安全边界相关${whiteCount >= 10 ? ",含抽检哨兵" : ""})`
|
|
123
|
-
: `全部为白盒发现,建议跳过详细审查`;
|
|
124
|
-
return {
|
|
125
|
-
classified,
|
|
126
|
-
summary: {
|
|
127
|
-
white_box_count: whiteCount,
|
|
128
|
-
gray_box_count: grayCount,
|
|
129
|
-
black_box_count: blackCount,
|
|
130
|
-
recommendation,
|
|
131
|
-
},
|
|
132
|
-
spot_check_backtrace: spotCheckBacktrace,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* 判断单条发现的信任级别。
|
|
137
|
-
* @param finding - 审查发现
|
|
138
|
-
* @param knowledgeRules - 知识库中的审查规则
|
|
139
|
-
* @param _fileContents - 文件内容映射(预留参数)
|
|
140
|
-
* @returns 信任级别和理由
|
|
141
|
-
*/
|
|
142
|
-
function determineTrustLevel(finding, knowledgeRules, _fileContents) {
|
|
143
|
-
// 黑盒判定: 无知识库支撑的 critical 发现
|
|
144
|
-
// 用规则 ID 精确匹配(如 [SQLI-001]),而非 body 子串匹配,防止恶意知识库条目操纵
|
|
145
|
-
if (finding.severity === "critical") {
|
|
146
|
-
const hasKnowledgeSupport = knowledgeRules.some((rule) => {
|
|
147
|
-
if (!rule.body)
|
|
148
|
-
return false;
|
|
149
|
-
// 提取 finding.title 中的规则 ID(格式如 [XXXX-NNN])
|
|
150
|
-
const titleIdMatch = finding.title.match(/\[([A-Z]+-\d+)\]/);
|
|
151
|
-
if (!titleIdMatch)
|
|
152
|
-
return false;
|
|
153
|
-
const ruleId = titleIdMatch[1];
|
|
154
|
-
// 用规则 ID 在知识库条目中精确匹配
|
|
155
|
-
return rule.body.includes(`[${ruleId}]`);
|
|
156
|
-
});
|
|
157
|
-
if (!hasKnowledgeSupport) {
|
|
158
|
-
return {
|
|
159
|
-
level: "black_box",
|
|
160
|
-
reason: "严重问题且无知识库规则支撑,属于非标逻辑",
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
// 灰盒判定: 安全维度 或 architecture 维度
|
|
165
|
-
if (finding.dimension === "security" || finding.dimension === "architecture") {
|
|
166
|
-
return {
|
|
167
|
-
level: "gray_box",
|
|
168
|
-
reason: `涉及${finding.dimension === "security" ? "安全边界" : "架构"},建议审查`,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
// 灰盒判定: 严重度为 warning 的性能问题
|
|
172
|
-
if (finding.dimension === "performance" && finding.severity === "warning") {
|
|
173
|
-
return {
|
|
174
|
-
level: "gray_box",
|
|
175
|
-
reason: "性能警告,涉及核心逻辑效率",
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
// 白盒判定: 其余所有(quality/info/debt 等)
|
|
179
|
-
return {
|
|
180
|
-
level: "white_box",
|
|
181
|
-
reason: "常规质量/技术债务检查项,符合已知模式",
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* 执行代码审查 — 对变更文件逐一进行质量、安全、性能、技术债务和知识规则检查。
|
|
186
|
-
* @param input - 审查输入参数,包含变更文件列表、项目路径、配置和分类结果等
|
|
187
|
-
* @param tracker - 可选的技术债务追踪器,审查中发现的技术债务会自动记录
|
|
188
|
-
* @returns 代码审查结果,包含发现列表、严重度统计和总体评估
|
|
189
|
-
*/
|
|
190
|
-
export async function reviewCode(input, tracker) {
|
|
191
|
-
const { changedFiles, projectPath, classification, fileContents, knowledgeIndex, review_role, uncertainty_triggers } = input;
|
|
192
|
-
debugLog(`code_reviewer: 开始代码审查,${changedFiles.length} 个变更文件`);
|
|
193
|
-
// 能力 5: 角色检测 — 默认自审,自审模式下置信度降低 0.2
|
|
194
|
-
const role = review_role ?? "self_review";
|
|
195
|
-
const selfReviewConfidencePenalty = role === "self_review" ? -0.2 : 0;
|
|
196
|
-
// 能力 1: 风险调整审查 — 高/中风险任务启用加强审查
|
|
197
|
-
const riskAdjusted = classification?.risk === "high" || classification?.risk === "medium";
|
|
198
|
-
// 跳过逻辑: hotfix 类型自动跳过审查以加速修复
|
|
199
|
-
if (classification?.task_type === "hotfix") {
|
|
200
|
-
debugLog("code_reviewer: hotfix 类型,跳过代码审查");
|
|
201
|
-
return {
|
|
202
|
-
task_id: "",
|
|
203
|
-
findings: [],
|
|
204
|
-
summary: { total: 0, by_dimension: emptyDimension(), by_severity: emptySeverity(), debt_items: 0 },
|
|
205
|
-
overall_assessment: "紧急修复场景,跳过代码审查",
|
|
206
|
-
skip_reason: "hotfix 类型自动跳过",
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
if (changedFiles.length === 0) {
|
|
210
|
-
debugLog("code_reviewer: 无变更文件,跳过审查");
|
|
211
|
-
return {
|
|
212
|
-
task_id: "",
|
|
213
|
-
findings: [],
|
|
214
|
-
summary: { total: 0, by_dimension: emptyDimension(), by_severity: emptySeverity(), debt_items: 0 },
|
|
215
|
-
overall_assessment: "无变更文件,跳过审查",
|
|
216
|
-
skip_reason: "无变更文件",
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
// 读取文件内容
|
|
220
|
-
const contents = fileContents ?? await readFiles(changedFiles, projectPath);
|
|
221
|
-
// 逐文件审查
|
|
222
|
-
const allFindings = [];
|
|
223
|
-
let findingIdx = 1;
|
|
224
|
-
for (const filePath of changedFiles) {
|
|
225
|
-
const content = contents[filePath] ?? "";
|
|
226
|
-
const lines = content.split("\n");
|
|
227
|
-
const ext = path.extname(filePath);
|
|
228
|
-
// 质量检查
|
|
229
|
-
allFindings.push(...checkQuality(filePath, content, lines, ext, findingIdx));
|
|
230
|
-
findingIdx += allFindings.length;
|
|
231
|
-
// 安全检查
|
|
232
|
-
allFindings.push(...checkSecurity(filePath, content, lines, ext, findingIdx));
|
|
233
|
-
findingIdx += allFindings.length;
|
|
234
|
-
// 性能检查
|
|
235
|
-
allFindings.push(...checkPerformance(filePath, content, lines, ext, findingIdx));
|
|
236
|
-
findingIdx += allFindings.length;
|
|
237
|
-
// 技术债务检查
|
|
238
|
-
allFindings.push(...checkDebt(filePath, content, lines, findingIdx));
|
|
239
|
-
findingIdx += allFindings.length;
|
|
240
|
-
// 知识驱动规则检查
|
|
241
|
-
if (knowledgeIndex) {
|
|
242
|
-
allFindings.push(...checkKnowledgeRules(filePath, content, lines, ext, knowledgeIndex, findingIdx, input.task_stage));
|
|
243
|
-
findingIdx += allFindings.length;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
// : Simplicity/Surgical 原则检查 — 发现过度工程和 scope 外修改
|
|
247
|
-
const principleCtx = {
|
|
248
|
-
changed_files: changedFiles,
|
|
249
|
-
declared_scope: input.declared_scope ?? [],
|
|
250
|
-
};
|
|
251
|
-
const simplicityResult = checkSimplicityFirst(principleCtx);
|
|
252
|
-
if (simplicityResult.status === "failed") {
|
|
253
|
-
for (const finding of simplicityResult.findings) {
|
|
254
|
-
allFindings.push(makeFinding(findingIdx++, "all", undefined, "architecture", "warning", "[Simplicity First] 过度工程", finding, "减少不必要的复杂度引入"));
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
const surgicalResult = checkSurgicalChanges(principleCtx);
|
|
258
|
-
if (surgicalResult.status === "failed") {
|
|
259
|
-
for (const finding of surgicalResult.findings) {
|
|
260
|
-
allFindings.push(makeFinding(findingIdx++, "all", undefined, "quality", "warning", "[Surgical Changes] Scope 外修改", finding, "只修改目标范围内的文件"));
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
// 重新编号
|
|
264
|
-
const findings = allFindings.map((f, i) => ({ ...f, id: `R-${String(i + 1).padStart(3, "0")}` }));
|
|
265
|
-
// 记录技术债务
|
|
266
|
-
if (tracker) {
|
|
267
|
-
for (const finding of findings) {
|
|
268
|
-
if (finding.is_debt) {
|
|
269
|
-
await tracker.recordDebt(finding, "");
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
// 汇总
|
|
274
|
-
const byDimension = emptyDimension();
|
|
275
|
-
const bySeverity = emptySeverity();
|
|
276
|
-
let debtCount = 0;
|
|
277
|
-
for (const f of findings) {
|
|
278
|
-
byDimension[f.dimension]++;
|
|
279
|
-
bySeverity[f.severity]++;
|
|
280
|
-
if (f.is_debt)
|
|
281
|
-
debtCount++;
|
|
282
|
-
}
|
|
283
|
-
// 能力 4: 检查不确定性触发条件是否被绕过
|
|
284
|
-
const uncertaintyViolations = [];
|
|
285
|
-
if (uncertainty_triggers && uncertainty_triggers.length > 0) {
|
|
286
|
-
for (const trigger of uncertainty_triggers) {
|
|
287
|
-
uncertaintyViolations.push(`${trigger.trigger_type}: ${trigger.description} — 未确认`);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
// 机制 3: 认知减负路由 — 按信任分级过滤审查发现
|
|
291
|
-
const { classified: trustClassified, summary: trustSummary } = classifyFindingsByTrust(findings, knowledgeIndex, fileContents, input.task_id);
|
|
292
|
-
debugLog(`code_reviewer: 审查完成 — 共 ${findings.length} 项发现(白盒 ${trustSummary.white_box_count},灰盒 ${trustSummary.gray_box_count},黑盒 ${trustSummary.black_box_count})`);
|
|
293
|
-
return {
|
|
294
|
-
task_id: "",
|
|
295
|
-
findings: trustClassified,
|
|
296
|
-
summary: { total: findings.length, by_dimension: byDimension, by_severity: bySeverity, debt_items: debtCount },
|
|
297
|
-
overall_assessment: formatAssessment(findings.length, bySeverity, role, riskAdjusted),
|
|
298
|
-
review_role: role,
|
|
299
|
-
confidence_adjustment: selfReviewConfidencePenalty,
|
|
300
|
-
disputes: [],
|
|
301
|
-
uncertainty_violations: uncertaintyViolations.length > 0 ? uncertaintyViolations : undefined,
|
|
302
|
-
risk_adjusted: riskAdjusted,
|
|
303
|
-
trust_classification: trustSummary,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* 质量维度检查 — 检测 console.log 和 TypeScript any 类型使用。
|
|
308
|
-
* @param filePath - 文件路径
|
|
309
|
-
* @param content - 文件内容
|
|
310
|
-
* @param lines - 文件行数组
|
|
311
|
-
* @param ext - 文件扩展名
|
|
312
|
-
* @param startIdx - 发现编号起始值
|
|
313
|
-
* @returns 质量发现列表
|
|
314
|
-
*/
|
|
315
|
-
function checkQuality(filePath, _content, lines, ext, startIdx) {
|
|
316
|
-
const findings = [];
|
|
317
|
-
let idx = startIdx;
|
|
318
|
-
// console.log 检测(跳过测试文件,测试中 console.log 可接受)
|
|
319
|
-
if (!/\.test\.|\.spec\./.test(filePath)) {
|
|
320
|
-
lines.forEach((line, i) => {
|
|
321
|
-
if (/console\.log\(/.test(line)) {
|
|
322
|
-
findings.push(makeFinding(idx++, filePath, i + 1, "quality", "warning", "生产代码中包含 console.log", `第 ${i + 1} 行使用了 console.log,应移除或替换为日志框架`, "使用项目的日志工具替代 console.log"));
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
// TypeScript any 类型检测 — 三种 any 用法: 类型注解(: any)、断言(as any)、泛型(<any>)
|
|
327
|
-
if (ext === ".ts" || ext === ".tsx") {
|
|
328
|
-
lines.forEach((line, i) => {
|
|
329
|
-
if (/: any\b/.test(line) || /as any/.test(line) || /<any>/.test(line)) {
|
|
330
|
-
findings.push(makeFinding(idx++, filePath, i + 1, "quality", "info", "使用了 any 类型", `第 ${i + 1} 行使用了 TypeScript any 类型`, "定义明确的类型接口"));
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
return findings;
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* 安全维度检查 — 检测 SQL 拼接、eval/Function 构造器和 innerHTML 赋值。
|
|
338
|
-
* @param filePath - 文件路径
|
|
339
|
-
* @param content - 文件内容
|
|
340
|
-
* @param lines - 文件行数组
|
|
341
|
-
* @param ext - 文件扩展名
|
|
342
|
-
* @param startIdx - 发现编号起始值
|
|
343
|
-
* @returns 安全发现列表
|
|
344
|
-
*/
|
|
345
|
-
function checkSecurity(filePath, _content, lines, ext, startIdx) {
|
|
346
|
-
const findings = [];
|
|
347
|
-
let idx = startIdx;
|
|
348
|
-
// SQL 拼接检测 — 匹配含 SQL 关键字且使用字符串拼接(+/${}/%s)的行,仅检查 Java/TS 文件
|
|
349
|
-
if (ext === ".java" || ext === ".ts") {
|
|
350
|
-
lines.forEach((line, i) => {
|
|
351
|
-
if (/["'`]SELECT|INSERT|UPDATE|DELETE/i.test(line) && (/\+/.test(line) || /\$\{/.test(line) || /%s/.test(line))) {
|
|
352
|
-
findings.push(makeFinding(idx++, filePath, i + 1, "security", "critical", "疑似 SQL 拼接", `第 ${i + 1} 行可能存在 SQL 字符串拼接`, "使用参数化查询或 ORM 方法"));
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
// eval/Function 构造器检测 — 动态代码执行,任意文件类型均检查
|
|
357
|
-
lines.forEach((line, i) => {
|
|
358
|
-
if (/\beval\s*\(/.test(line) || /\bFunction\s*\(/.test(line)) {
|
|
359
|
-
findings.push(makeFinding(idx++, filePath, i + 1, "security", "critical", "使用了 eval 或 Function 构造器", `第 ${i + 1} 行使用了动态代码执行`, "避免 eval,使用安全的替代方案"));
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
// innerHTML 检测 — XSS 攻击面,仅检查前端文件 (TSX/JSX/HTML)
|
|
363
|
-
if (ext === ".tsx" || ext === ".jsx" || ext === ".html") {
|
|
364
|
-
lines.forEach((line, i) => {
|
|
365
|
-
if (/\.innerHTML\s*=/.test(line)) {
|
|
366
|
-
findings.push(makeFinding(idx++, filePath, i + 1, "security", "warning", "使用了 innerHTML", `第 ${i + 1} 行直接设置 innerHTML,存在 XSS 风险`, "使用 textContent 或安全的 DOM 操作"));
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
return findings;
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* 性能维度检查 — 检测循环内数据库调用(N+1 问题)和 SELECT * 语句。
|
|
374
|
-
* @param filePath - 文件路径
|
|
375
|
-
* @param content - 文件内容
|
|
376
|
-
* @param lines - 文件行数组
|
|
377
|
-
* @param ext - 文件扩展名
|
|
378
|
-
* @param startIdx - 发现编号起始值
|
|
379
|
-
* @returns 性能发现列表
|
|
380
|
-
*/
|
|
381
|
-
function checkPerformance(filePath, _content, lines, ext, startIdx) {
|
|
382
|
-
const findings = [];
|
|
383
|
-
let idx = startIdx;
|
|
384
|
-
// 循环内数据库调用检测 — 通过花括号深度追踪 for/while 循环范围,仅检查 Java 文件
|
|
385
|
-
if (ext === ".java") {
|
|
386
|
-
const loopPattern = /(for|while)\s*\(/;
|
|
387
|
-
const dbPattern = /\.(select|insert|update|delete|save|findBy|query)\s*\(/i;
|
|
388
|
-
let braceDepth = 0;
|
|
389
|
-
let loopDepth = -1;
|
|
390
|
-
let loopStart = 0;
|
|
391
|
-
lines.forEach((line, i) => {
|
|
392
|
-
for (const ch of line) {
|
|
393
|
-
if (ch === "{") {
|
|
394
|
-
braceDepth++;
|
|
395
|
-
}
|
|
396
|
-
else if (ch === "}") {
|
|
397
|
-
if (loopDepth >= 0 && braceDepth <= loopDepth) {
|
|
398
|
-
loopDepth = -1;
|
|
399
|
-
}
|
|
400
|
-
braceDepth = Math.max(0, braceDepth - 1);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
if (loopPattern.test(line)) {
|
|
404
|
-
loopDepth = braceDepth;
|
|
405
|
-
loopStart = i + 1;
|
|
406
|
-
}
|
|
407
|
-
if (loopDepth >= 0 && dbPattern.test(line)) {
|
|
408
|
-
findings.push(makeFinding(idx++, filePath, i + 1, "performance", "warning", "循环内可能存在数据库调用", `第 ${loopStart} 行循环体内第 ${i + 1} 行调用了数据库方法,可能导致 N+1 问题`, "使用批量查询替代循环内单次查询"));
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
// SELECT * 检测 — 返回不必要字段造成资源浪费,任意文件类型均检查
|
|
413
|
-
lines.forEach((line, i) => {
|
|
414
|
-
if (/SELECT\s+\*/i.test(line)) {
|
|
415
|
-
findings.push(makeFinding(idx++, filePath, i + 1, "performance", "info", "使用了 SELECT *", `第 ${i + 1} 行使用了 SELECT *,可能返回不必要的数据`, "明确列出需要的字段"));
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
return findings;
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* 技术债务维度检查 — 检测 TODO/FIXME/HACK/XXX 标记和过长文件(>500行)。
|
|
422
|
-
* @param filePath - 文件路径
|
|
423
|
-
* @param content - 文件内容
|
|
424
|
-
* @param lines - 文件行数组
|
|
425
|
-
* @param startIdx - 发现编号起始值
|
|
426
|
-
* @returns 技术债务发现列表
|
|
427
|
-
*/
|
|
428
|
-
function checkDebt(filePath, _content, lines, startIdx) {
|
|
429
|
-
const findings = [];
|
|
430
|
-
let idx = startIdx;
|
|
431
|
-
// TODO/FIXME/HACK/XXX 标记
|
|
432
|
-
lines.forEach((line, i) => {
|
|
433
|
-
const match = line.match(/\b(TODO|FIXME|HACK|XXX)\b/);
|
|
434
|
-
if (match) {
|
|
435
|
-
findings.push({
|
|
436
|
-
...makeFinding(idx++, filePath, i + 1, "debt", "info", `发现 ${match[1]} 标记`, `第 ${i + 1} 行: ${line.trim().slice(0, 80)}`, undefined),
|
|
437
|
-
is_debt: true,
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
});
|
|
441
|
-
// 过长文件
|
|
442
|
-
if (lines.length > 500) {
|
|
443
|
-
findings.push({
|
|
444
|
-
...makeFinding(idx++, filePath, undefined, "debt", "info", `文件过长(${lines.length} 行)`, `${filePath} 超过 500 行,建议拆分`, "按职责拆分为多个模块"),
|
|
445
|
-
is_debt: true,
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
return findings;
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* 从知识条目正文中解析审查规则 — 支持格式: `- [ID] 描述 | pattern: /regex/flags | severity: level | scope: scope`
|
|
452
|
-
* @param body - 知识条目正文内容
|
|
453
|
-
* @param fileName - 知识条目文件名,用于推断审查维度
|
|
454
|
-
* @returns 解析后的规则数组,无效规则(如非法正则)自动跳过
|
|
455
|
-
*/
|
|
456
|
-
function parseRulesFromEntry(body, _fileName) {
|
|
457
|
-
const rules = [];
|
|
458
|
-
if (!body.includes("## 规则"))
|
|
459
|
-
return rules;
|
|
460
|
-
const lines = body.split("\n");
|
|
461
|
-
let inSection = false;
|
|
462
|
-
for (const line of lines) {
|
|
463
|
-
if (line.startsWith("## 规则")) {
|
|
464
|
-
inSection = true;
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
if (inSection && line.startsWith("## "))
|
|
468
|
-
break;
|
|
469
|
-
if (inSection) {
|
|
470
|
-
// 规则格式: - [ID] 描述 | pattern: regex | severity: level | scope: scope
|
|
471
|
-
const match = line.match(/- \[([A-Z]+-\d+)\]\s*(.+?)(?:\s*\|\s*(.+))?$/);
|
|
472
|
-
if (!match)
|
|
473
|
-
continue;
|
|
474
|
-
const id = match[1];
|
|
475
|
-
const description = match[2].trim();
|
|
476
|
-
const attrs = match[3] || "";
|
|
477
|
-
const patternMatch = attrs.match(/pattern:\s*\/(.+?)\/([gimsuy]*)/);
|
|
478
|
-
const severityMatch = attrs.match(/severity:\s*(info|warning|critical)/);
|
|
479
|
-
const scopeMatch = attrs.match(/scope:\s*(\S+)/);
|
|
480
|
-
if (patternMatch) {
|
|
481
|
-
try {
|
|
482
|
-
const regex = new RegExp(patternMatch[1], patternMatch[2] || "");
|
|
483
|
-
rules.push({
|
|
484
|
-
id,
|
|
485
|
-
description,
|
|
486
|
-
pattern: regex,
|
|
487
|
-
severity: severityMatch?.[1] || "info",
|
|
488
|
-
scope: scopeMatch?.[1],
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
catch {
|
|
492
|
-
// 无效正则跳过
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
return rules;
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* 从知识库加载审查规则并对代码进行匹配检查。
|
|
501
|
-
* @param filePath - 文件路径
|
|
502
|
-
* @param content - 文件内容
|
|
503
|
-
* @param lines - 文件行数组
|
|
504
|
-
* @param ext - 文件扩展名
|
|
505
|
-
* @param knowledgeIndex - 知识库索引管理器
|
|
506
|
-
* @param startIdx - 发现编号起始值
|
|
507
|
-
* @returns 知识规则发现列表(已去重)
|
|
508
|
-
*/
|
|
509
|
-
function checkKnowledgeRules(filePath, _content, lines, _ext, knowledgeIndex, startIdx, taskStage) {
|
|
510
|
-
const findings = [];
|
|
511
|
-
let idx = startIdx;
|
|
512
|
-
// 查询知识库中所有 review_rule 类型的条目(最多 20 条)
|
|
513
|
-
const domains = taskStage ? [taskStage] : undefined;
|
|
514
|
-
const entries = knowledgeIndex.query({ type: "review_rule", domain: domains, limit: 20 });
|
|
515
|
-
if (entries.length === 0)
|
|
516
|
-
return findings;
|
|
517
|
-
// 根据文件扩展名判断所属端(前端/后端),用于 scope 过滤
|
|
518
|
-
const isFrontend = /\.(tsx?|jsx?|vue|css|scss|html)$/.test(filePath);
|
|
519
|
-
const isTest = /\.test\.|\.spec\./.test(filePath);
|
|
520
|
-
const fileSide = isFrontend ? "frontend" : "backend";
|
|
521
|
-
for (const entry of entries) {
|
|
522
|
-
if (!entry.body)
|
|
523
|
-
continue;
|
|
524
|
-
const rules = parseRulesFromEntry(entry.body, entry.file_path);
|
|
525
|
-
for (const rule of rules) {
|
|
526
|
-
// 跳过测试文件中不适用于测试的规则
|
|
527
|
-
if (isTest && rule.scope === "!test")
|
|
528
|
-
continue;
|
|
529
|
-
// 跳过不适用于当前端的规则
|
|
530
|
-
if (rule.scope && rule.scope !== "backend" && rule.scope !== "frontend" && rule.scope !== fileSide)
|
|
531
|
-
continue;
|
|
532
|
-
if (rule.scope === "backend" && isFrontend)
|
|
533
|
-
continue;
|
|
534
|
-
if (rule.scope === "frontend" && !isFrontend)
|
|
535
|
-
continue;
|
|
536
|
-
// 特殊 scope: "check: lines" — 文件行数检查规则,阈值 300 行
|
|
537
|
-
if (rule.scope === "check: lines") {
|
|
538
|
-
if (lines.length > 300) {
|
|
539
|
-
findings.push(makeFinding(idx++, filePath, undefined, inferDimension(entry.file_path), rule.severity, `文件过长(${lines.length} 行)`, `${filePath} 超过 300 行,建议按职责拆分`, "按单一职责拆分为多个组件/模块"));
|
|
540
|
-
}
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
// 逐行正则匹配
|
|
544
|
-
lines.forEach((line, i) => {
|
|
545
|
-
if (rule.pattern.test(line)) {
|
|
546
|
-
findings.push(makeFinding(idx++, filePath, i + 1, inferDimension(entry.file_path), rule.severity, `[${rule.id}] ${rule.description}`, `第 ${i + 1} 行: ${line.trim().slice(0, 80)}`, `参考知识条目: ${entry.name}`));
|
|
547
|
-
}
|
|
548
|
-
// 重置正则 lastIndex(某些正则有 g 标志)
|
|
549
|
-
rule.pattern.lastIndex = 0;
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
// 去重:同一文件+行号+标题末30字符视为重复,保留先出现的发现
|
|
554
|
-
return deduplicateFindings(findings);
|
|
555
|
-
}
|
|
556
|
-
/** 从文件路径推断审查维度 — 根据路径关键字映射到 quality/security/performance/architecture */
|
|
557
|
-
function inferDimension(filePath) {
|
|
558
|
-
if (filePath.includes("security"))
|
|
559
|
-
return "security";
|
|
560
|
-
if (filePath.includes("quality"))
|
|
561
|
-
return "quality";
|
|
562
|
-
if (filePath.includes("performance"))
|
|
563
|
-
return "performance";
|
|
564
|
-
if (filePath.includes("architecture"))
|
|
565
|
-
return "architecture";
|
|
566
|
-
return "quality";
|
|
567
|
-
}
|
|
568
|
-
/** 发现去重 — 同一文件+行号+标题末30字符视为重复项,仅保留首次出现 */
|
|
569
|
-
function deduplicateFindings(findings) {
|
|
570
|
-
const seen = new Map();
|
|
571
|
-
for (const f of findings) {
|
|
572
|
-
const key = `${f.file}:${f.line}:${f.title.slice(-30)}`;
|
|
573
|
-
if (!seen.has(key)) {
|
|
574
|
-
seen.set(key, f);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
return [...seen.values()];
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* 构造审查发现对象 — 生成统一格式的 ReviewFinding。
|
|
581
|
-
* @param idx - 发现编号
|
|
582
|
-
* @param file - 文件路径
|
|
583
|
-
* @param line - 行号
|
|
584
|
-
* @param dimension - 审查维度
|
|
585
|
-
* @param severity - 严重度
|
|
586
|
-
* @param title - 发现标题
|
|
587
|
-
* @param description - 详细描述
|
|
588
|
-
* @param suggestion - 可选的修复建议
|
|
589
|
-
* @returns 审查发现对象
|
|
590
|
-
*/
|
|
591
|
-
function makeFinding(idx, file, line, dimension, severity, title, description, suggestion) {
|
|
592
|
-
return { id: `R-${String(idx).padStart(3, "0")}`, file, line, dimension, severity, title, description, suggestion, is_debt: false };
|
|
593
|
-
}
|
|
594
|
-
/** 创建全零的维度计数器 */
|
|
595
|
-
function emptyDimension() {
|
|
596
|
-
return { quality: 0, security: 0, performance: 0, debt: 0, architecture: 0 };
|
|
597
|
-
}
|
|
598
|
-
/** 创建全零的严重度计数器 */
|
|
599
|
-
function emptySeverity() {
|
|
600
|
-
return { info: 0, warning: 0, critical: 0 };
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* 格式化总体评估文本 — 根据发现问题数量和严重度生成中文摘要。
|
|
604
|
-
* @param total - 发现总数
|
|
605
|
-
* @param bySeverity - 各严重度计数
|
|
606
|
-
* @param role - 审查角色,自审模式附加置信度提示
|
|
607
|
-
* @param riskAdjusted - 是否启用风险调整审查
|
|
608
|
-
* @returns 中文格式的评估摘要字符串
|
|
609
|
-
*/
|
|
610
|
-
function formatAssessment(total, bySeverity, role, riskAdjusted) {
|
|
611
|
-
if (total === 0)
|
|
612
|
-
return "代码审查通过,未发现问题";
|
|
613
|
-
const parts = [];
|
|
614
|
-
if (bySeverity.critical > 0)
|
|
615
|
-
parts.push(`${bySeverity.critical} 个严重问题`);
|
|
616
|
-
if (bySeverity.warning > 0)
|
|
617
|
-
parts.push(`${bySeverity.warning} 个警告`);
|
|
618
|
-
if (bySeverity.info > 0)
|
|
619
|
-
parts.push(`${bySeverity.info} 个提示`);
|
|
620
|
-
let assessment = `共发现 ${total} 项: ${parts.join(",")}`;
|
|
621
|
-
if (role === "self_review") {
|
|
622
|
-
assessment += " | ⚠ 自审模式,置信度已降级";
|
|
623
|
-
}
|
|
624
|
-
if (riskAdjusted) {
|
|
625
|
-
assessment += " | 风险调整审查已启用";
|
|
626
|
-
}
|
|
627
|
-
return assessment;
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* 批量读取文件内容 — 无法读取的文件自动跳过(如已被删除)。
|
|
631
|
-
* @param files - 相对于 basePath 的文件路径列表
|
|
632
|
-
* @param basePath - 项目根目录
|
|
633
|
-
* @returns 文件路径到内容的映射
|
|
634
|
-
*/
|
|
635
|
-
async function readFiles(files, basePath) {
|
|
636
|
-
const contents = {};
|
|
637
|
-
await Promise.all(files.map(async (file) => {
|
|
638
|
-
try {
|
|
639
|
-
contents[file] = await fs.readFile(path.join(basePath, file), "utf-8");
|
|
640
|
-
}
|
|
641
|
-
catch {
|
|
642
|
-
debugLog(`代码审查: 文件读取失败,跳过 — ${path.join(basePath, file)}`);
|
|
643
|
-
}
|
|
644
|
-
}));
|
|
645
|
-
return contents;
|
|
646
|
-
}
|
|
647
|
-
//# sourceMappingURL=code_reviewer.js.map
|