rules-enforcer 1.0.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/README.md +58 -0
- package/detector/README.md +212 -0
- package/detector/decision-engine/README.md +203 -0
- package/detector/decision-engine/conflict-resolver.js +336 -0
- package/detector/decision-engine/de-verify.js +461 -0
- package/detector/decision-engine/index.js +204 -0
- package/detector/decision-engine/optimizer.js +325 -0
- package/detector/decision-engine/scorer.js +359 -0
- package/detector/knowledge-base/README.md +140 -0
- package/detector/knowledge-base/agent-knowledge.json +62 -0
- package/detector/knowledge-base/index.js +332 -0
- package/detector/knowledge-base/kb-verify.js +287 -0
- package/detector/knowledge-base/mcp-knowledge.json +135 -0
- package/detector/knowledge-base/rules-knowledge.json +184 -0
- package/detector/mcp-server.js +157 -0
- package/detector/mcp-service.js +118 -0
- package/detector/package.json +13 -0
- package/detector/plugin.json +122 -0
- package/detector/project-detector.js +710 -0
- package/detector/render-engine/ag-config-render.js +195 -0
- package/detector/render-engine/index.js +124 -0
- package/detector/render-engine/render-core.js +200 -0
- package/detector/render-engine/render-verify.js +282 -0
- package/detector/render-engine/rule-render.js +231 -0
- package/detector/test-exceptions.js +366 -0
- package/detector/verify-plugin.js +233 -0
- package/hooks/chain-invoker.js +98 -0
- package/hooks/custom-hook-server.js +312 -0
- package/hooks/mcp-hooks.js +153 -0
- package/hooks/validate-chain.js +147 -0
- package/package.json +35 -0
- package/rules-server.js +350 -0
- package/test/test-mcp-full.js +193 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimizer - 优化算法
|
|
3
|
+
* 基于评分结果生成最优配置组合
|
|
4
|
+
* 权重固定:MCP=40%, 规则=35%, Agent=25%
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Scorer } = require('./scorer.js');
|
|
8
|
+
|
|
9
|
+
class Optimizer {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.scorer = new Scorer();
|
|
12
|
+
this.weights = this.scorer.getWeights();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 优化MCP配置
|
|
17
|
+
*/
|
|
18
|
+
optimizeMcp(mcpKnowledge, projectInfo) {
|
|
19
|
+
const scoredComponents = [];
|
|
20
|
+
const services = mcpKnowledge.mcpServices || {};
|
|
21
|
+
|
|
22
|
+
for (const [id, service] of Object.entries(services)) {
|
|
23
|
+
const score = this.scorer.scoreMcpComponent(service, projectInfo);
|
|
24
|
+
scoredComponents.push(score);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 按总分降序排序
|
|
28
|
+
scoredComponents.sort((a, b) => b.totalScore - a.totalScore);
|
|
29
|
+
|
|
30
|
+
// 过滤已存在的组件
|
|
31
|
+
const existingMcp = projectInfo.existingComponents?.mcp || [];
|
|
32
|
+
const newComponents = scoredComponents.filter(c =>
|
|
33
|
+
!existingMcp.includes(c.id)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// 选择高分组件(最多推荐3个)
|
|
37
|
+
const recommended = newComponents.slice(0, 3);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
all: scoredComponents,
|
|
41
|
+
recommended,
|
|
42
|
+
existing: scoredComponents.filter(c =>
|
|
43
|
+
existingMcp.includes(c.id)
|
|
44
|
+
)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 优化规则配置
|
|
50
|
+
*/
|
|
51
|
+
optimizeRules(rulesKnowledge, projectInfo) {
|
|
52
|
+
const scoredComponents = [];
|
|
53
|
+
const rules = rulesKnowledge.rules || {};
|
|
54
|
+
|
|
55
|
+
for (const [id, rule] of Object.entries(rules)) {
|
|
56
|
+
const score = this.scorer.scoreRuleComponent(rule, projectInfo);
|
|
57
|
+
scoredComponents.push(score);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 按优先级和总分排序
|
|
61
|
+
scoredComponents.sort((a, b) => {
|
|
62
|
+
// 先按level排序:L1 > L2 > L3 > custom
|
|
63
|
+
const levelOrder = { 'L1': 1, 'L2': 2, 'L3': 3, 'custom': 4 };
|
|
64
|
+
const levelDiff = (levelOrder[a.level] || 5) - (levelOrder[b.level] || 5);
|
|
65
|
+
if (levelDiff !== 0) return levelDiff;
|
|
66
|
+
|
|
67
|
+
// 同级别按总分降序
|
|
68
|
+
return b.totalScore - a.totalScore;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// 过滤已存在的组件
|
|
72
|
+
const existingRules = (projectInfo.existingComponents?.rules || []).map(r => r.name || r);
|
|
73
|
+
const newComponents = scoredComponents.filter(c =>
|
|
74
|
+
!existingRules.includes(c.id) && !existingRules.includes(c.name)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// 选择高分组件
|
|
78
|
+
const recommended = newComponents.slice(0, 5);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
all: scoredComponents,
|
|
82
|
+
recommended,
|
|
83
|
+
existing: scoredComponents.filter(c =>
|
|
84
|
+
existingRules.includes(c.id) || existingRules.includes(c.name)
|
|
85
|
+
)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 优化Agent配置
|
|
91
|
+
*/
|
|
92
|
+
optimizeAgent(agentKnowledge, projectInfo) {
|
|
93
|
+
const scoredComponents = [];
|
|
94
|
+
const profiles = agentKnowledge.agentProfiles || {};
|
|
95
|
+
|
|
96
|
+
for (const [id, profile] of Object.entries(profiles)) {
|
|
97
|
+
const score = this.scorer.scoreAgentComponent({
|
|
98
|
+
id,
|
|
99
|
+
...profile
|
|
100
|
+
}, projectInfo);
|
|
101
|
+
scoredComponents.push(score);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 按总分降序排序
|
|
105
|
+
scoredComponents.sort((a, b) => b.totalScore - a.totalScore);
|
|
106
|
+
|
|
107
|
+
// 选择最高分
|
|
108
|
+
const recommended = scoredComponents[0] || null;
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
all: scoredComponents,
|
|
112
|
+
recommended
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 计算综合置信度
|
|
118
|
+
* 基于技术栈匹配度、复杂度适配度、依赖兼容度三项加权
|
|
119
|
+
* 复用分单独计算,不计入置信度
|
|
120
|
+
*/
|
|
121
|
+
calculateConfidence(mcpResult, rulesResult, agentResult) {
|
|
122
|
+
// 提取推荐组件的分项得分
|
|
123
|
+
const extractScores = (components) => {
|
|
124
|
+
if (!components || components.length === 0) {
|
|
125
|
+
return { techStackMatch: 50, complexityFit: 50, dependency: 50 };
|
|
126
|
+
}
|
|
127
|
+
const avg = (arr) => arr.reduce((sum, v) => sum + v, 0) / arr.length;
|
|
128
|
+
return {
|
|
129
|
+
techStackMatch: avg(components.map(c => c.scores.techStackMatch)),
|
|
130
|
+
complexityFit: avg(components.map(c => c.scores.complexityFit)),
|
|
131
|
+
dependency: avg(components.map(c => c.scores.dependency))
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const mcpScores = extractScores(mcpResult.recommended);
|
|
136
|
+
const rulesScores = extractScores(rulesResult.recommended);
|
|
137
|
+
const agentScores = agentResult.recommended
|
|
138
|
+
? {
|
|
139
|
+
techStackMatch: agentResult.recommended.scores.techStackMatch,
|
|
140
|
+
complexityFit: agentResult.recommended.scores.complexityFit,
|
|
141
|
+
dependency: agentResult.recommended.scores.dependency
|
|
142
|
+
}
|
|
143
|
+
: { techStackMatch: 50, complexityFit: 50, dependency: 50 };
|
|
144
|
+
|
|
145
|
+
// 按固定权重计算综合置信度:技术栈40%、复杂度35%、依赖25%
|
|
146
|
+
const techStackAvg = mcpScores.techStackMatch * this.weights.mcp +
|
|
147
|
+
rulesScores.techStackMatch * this.weights.rules +
|
|
148
|
+
agentScores.techStackMatch * this.weights.agent;
|
|
149
|
+
|
|
150
|
+
const complexityAvg = mcpScores.complexityFit * this.weights.mcp +
|
|
151
|
+
rulesScores.complexityFit * this.weights.rules +
|
|
152
|
+
agentScores.complexityFit * this.weights.agent;
|
|
153
|
+
|
|
154
|
+
const dependencyAvg = mcpScores.dependency * this.weights.mcp +
|
|
155
|
+
rulesScores.dependency * this.weights.rules +
|
|
156
|
+
agentScores.dependency * this.weights.agent;
|
|
157
|
+
|
|
158
|
+
// 置信度 = 技术栈*0.4 + 复杂度*0.35 + 依赖*0.25
|
|
159
|
+
const confidence = techStackAvg * this.weights.mcp +
|
|
160
|
+
complexityAvg * this.weights.rules +
|
|
161
|
+
dependencyAvg * this.weights.agent;
|
|
162
|
+
|
|
163
|
+
return Math.round(confidence) / 100; // 转换为0~1区间
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 计算复用得分(基于已有组件被推荐组件复用的程度)
|
|
168
|
+
*/
|
|
169
|
+
calculateReuseScore(mcpResult, rulesResult, agentResult) {
|
|
170
|
+
let reuseCount = 0;
|
|
171
|
+
let totalCount = 0;
|
|
172
|
+
|
|
173
|
+
// MCP复用
|
|
174
|
+
if (mcpResult.existing && mcpResult.existing.length > 0) {
|
|
175
|
+
reuseCount += mcpResult.existing.reduce((sum, c) => sum + c.scores.reuse, 0);
|
|
176
|
+
totalCount += mcpResult.existing.length;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Rules复用
|
|
180
|
+
if (rulesResult.existing && rulesResult.existing.length > 0) {
|
|
181
|
+
reuseCount += rulesResult.existing.reduce((sum, c) => sum + c.scores.reuse, 0);
|
|
182
|
+
totalCount += rulesResult.existing.length;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 如果没有已有组件(全是新增),复用分为0是正常的
|
|
186
|
+
// 如果有已有组件但复用分为0,说明评分逻辑有问题
|
|
187
|
+
if (totalCount === 0) {
|
|
188
|
+
return 0; // 全部是新增组件,复用分为0
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return Math.round(reuseCount / totalCount);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 计算分项得分明细
|
|
196
|
+
*/
|
|
197
|
+
calculateScoreBreakdown(mcpResult, rulesResult, agentResult) {
|
|
198
|
+
const extractScores = (components) => {
|
|
199
|
+
if (!components || components.length === 0) {
|
|
200
|
+
return { techStackMatch: 50, complexityFit: 50, dependency: 50, reuse: 50 };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const avg = (arr) => arr.reduce((sum, v) => sum + v, 0) / arr.length;
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
techStackMatch: avg(components.map(c => c.scores.techStackMatch)),
|
|
207
|
+
complexityFit: avg(components.map(c => c.scores.complexityFit)),
|
|
208
|
+
dependency: avg(components.map(c => c.scores.dependency)),
|
|
209
|
+
reuse: avg(components.map(c => c.scores.reuse))
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const mcpScores = extractScores(mcpResult.recommended);
|
|
214
|
+
const rulesScores = extractScores(rulesResult.recommended);
|
|
215
|
+
const agentScores = agentResult.recommended
|
|
216
|
+
? agentResult.recommended.scores
|
|
217
|
+
: { techStackMatch: 50, complexityFit: 50, dependency: 50, reuse: 50 };
|
|
218
|
+
|
|
219
|
+
// 综合计算(复用分从已有组件中单独计算)
|
|
220
|
+
const reuseScore = this.calculateReuseScore(mcpResult, rulesResult, agentResult);
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
techStackMatch: Math.round(
|
|
224
|
+
mcpScores.techStackMatch * this.weights.mcp +
|
|
225
|
+
rulesScores.techStackMatch * this.weights.rules +
|
|
226
|
+
agentScores.techStackMatch * this.weights.agent
|
|
227
|
+
),
|
|
228
|
+
complexityFit: Math.round(
|
|
229
|
+
mcpScores.complexityFit * this.weights.mcp +
|
|
230
|
+
rulesScores.complexityFit * this.weights.rules +
|
|
231
|
+
agentScores.complexityFit * this.weights.agent
|
|
232
|
+
),
|
|
233
|
+
dependencyScore: Math.round(
|
|
234
|
+
mcpScores.dependency * this.weights.mcp +
|
|
235
|
+
rulesScores.dependency * this.weights.rules +
|
|
236
|
+
agentScores.dependency * this.weights.agent
|
|
237
|
+
),
|
|
238
|
+
reuseScore
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 生成备选方案
|
|
244
|
+
*/
|
|
245
|
+
generateAlternatives(mcpResult, rulesResult, agentResult) {
|
|
246
|
+
const alternatives = [];
|
|
247
|
+
|
|
248
|
+
// MCP备选方案
|
|
249
|
+
if (mcpResult.all.length > 1) {
|
|
250
|
+
const mcpAlt = mcpResult.all.slice(1, 3).map(c => ({
|
|
251
|
+
type: 'mcp',
|
|
252
|
+
id: c.id,
|
|
253
|
+
name: c.name,
|
|
254
|
+
score: c.totalScore
|
|
255
|
+
}));
|
|
256
|
+
alternatives.push(...mcpAlt);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 规则备选方案
|
|
260
|
+
if (rulesResult.all.length > 1) {
|
|
261
|
+
const rulesAlt = rulesResult.all.slice(1, 4).map(c => ({
|
|
262
|
+
type: 'rule',
|
|
263
|
+
id: c.id,
|
|
264
|
+
name: c.name,
|
|
265
|
+
level: c.level,
|
|
266
|
+
score: c.totalScore
|
|
267
|
+
}));
|
|
268
|
+
alternatives.push(...rulesAlt);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Agent备选方案
|
|
272
|
+
if (agentResult.all.length > 1) {
|
|
273
|
+
const agentAlt = agentResult.all.slice(1, 2).map(c => ({
|
|
274
|
+
type: 'agent',
|
|
275
|
+
id: c.id,
|
|
276
|
+
name: c.name,
|
|
277
|
+
score: c.totalScore
|
|
278
|
+
}));
|
|
279
|
+
alternatives.push(...agentAlt);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return alternatives;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 执行完整优化
|
|
287
|
+
*/
|
|
288
|
+
optimize(knowledgeBase, projectInfo) {
|
|
289
|
+
// 优化各组件
|
|
290
|
+
const mcpResult = this.optimizeMcp(
|
|
291
|
+
knowledgeBase.mcpKnowledge,
|
|
292
|
+
projectInfo
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const rulesResult = this.optimizeRules(
|
|
296
|
+
knowledgeBase.rulesKnowledge,
|
|
297
|
+
projectInfo
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const agentResult = this.optimizeAgent(
|
|
301
|
+
knowledgeBase.agentKnowledge,
|
|
302
|
+
projectInfo
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// 计算置信度
|
|
306
|
+
const confidence = this.calculateConfidence(mcpResult, rulesResult, agentResult);
|
|
307
|
+
|
|
308
|
+
// 计算分项得分
|
|
309
|
+
const scoreBreakdown = this.calculateScoreBreakdown(mcpResult, rulesResult, agentResult);
|
|
310
|
+
|
|
311
|
+
// 生成备选方案
|
|
312
|
+
const alternatives = this.generateAlternatives(mcpResult, rulesResult, agentResult);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
mcpResult,
|
|
316
|
+
rulesResult,
|
|
317
|
+
agentResult,
|
|
318
|
+
confidence,
|
|
319
|
+
scoreBreakdown,
|
|
320
|
+
alternatives
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
module.exports = { Optimizer };
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scorer - 评分引擎
|
|
3
|
+
* 四项打分维度:技术栈匹配/复杂度适配/依赖兼容/已有组件复用
|
|
4
|
+
* 分值区间:0~100
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class Scorer {
|
|
8
|
+
constructor() {
|
|
9
|
+
// 固定权重配置(预留外置接口)
|
|
10
|
+
this.weights = {
|
|
11
|
+
mcp: 0.40, // MCP权重 40%
|
|
12
|
+
rules: 0.35, // 规则权重 35%
|
|
13
|
+
agent: 0.25 // Agent权重 25%
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 计算技术栈匹配分 (0~100)
|
|
19
|
+
*/
|
|
20
|
+
calculateTechStackMatchScore(projectTechStack, componentTechStack) {
|
|
21
|
+
if (!projectTechStack || projectTechStack.length === 0) {
|
|
22
|
+
return 50; // 无技术栈信息,返回中等分数
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!componentTechStack || componentTechStack.length === 0) {
|
|
26
|
+
return 50; // 组件无技术栈限制,返回中等分数
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 检查是否包含"any"通配符
|
|
30
|
+
if (componentTechStack.includes('any')) {
|
|
31
|
+
return 90; // 通用组件,高分
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let matchCount = 0;
|
|
35
|
+
let totalScore = 0;
|
|
36
|
+
|
|
37
|
+
for (const tech of projectTechStack) {
|
|
38
|
+
const normalizedTech = tech.toLowerCase();
|
|
39
|
+
for (const pattern of componentTechStack) {
|
|
40
|
+
const normalizedPattern = pattern.toLowerCase();
|
|
41
|
+
|
|
42
|
+
if (normalizedTech === normalizedPattern) {
|
|
43
|
+
totalScore += 100; // 完全匹配
|
|
44
|
+
matchCount++;
|
|
45
|
+
} else if (normalizedTech.includes(normalizedPattern)) {
|
|
46
|
+
totalScore += 80; // 包含匹配
|
|
47
|
+
matchCount++;
|
|
48
|
+
} else if (normalizedPattern.includes(normalizedTech)) {
|
|
49
|
+
totalScore += 70; // 被包含匹配
|
|
50
|
+
matchCount++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (matchCount === 0) {
|
|
56
|
+
return 20; // 无匹配,低分
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Math.min(100, Math.round(totalScore / matchCount));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 计算复杂度适配分 (0~100)
|
|
64
|
+
*/
|
|
65
|
+
calculateComplexityFitScore(projectComplexity, componentSizeRange) {
|
|
66
|
+
if (!projectComplexity || !componentSizeRange) {
|
|
67
|
+
return 50;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const complexityMap = {
|
|
71
|
+
'simple': 1,
|
|
72
|
+
'medium': 2,
|
|
73
|
+
'complex': 3
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const projectLevel = complexityMap[projectComplexity] || 2;
|
|
77
|
+
|
|
78
|
+
// 检查组件是否支持该项目规模
|
|
79
|
+
if (componentSizeRange.includes(projectComplexity)) {
|
|
80
|
+
return 100; // 完全支持
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 检查相邻规模支持
|
|
84
|
+
const supportedLevels = componentSizeRange.map(s => complexityMap[s] || 2);
|
|
85
|
+
const minLevel = Math.min(...supportedLevels);
|
|
86
|
+
const maxLevel = Math.max(...supportedLevels);
|
|
87
|
+
|
|
88
|
+
if (projectLevel >= minLevel && projectLevel <= maxLevel) {
|
|
89
|
+
return 90; // 范围内支持
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 计算距离分数
|
|
93
|
+
const distance = Math.min(
|
|
94
|
+
Math.abs(projectLevel - minLevel),
|
|
95
|
+
Math.abs(projectLevel - maxLevel)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return Math.max(30, 100 - distance * 25);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 计算依赖兼容分 (0~100)
|
|
103
|
+
*/
|
|
104
|
+
calculateDependencyScore(componentDependencies, existingComponents) {
|
|
105
|
+
if (!componentDependencies || componentDependencies.length === 0) {
|
|
106
|
+
return 100; // 无依赖,满分
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!existingComponents) {
|
|
110
|
+
existingComponents = { mcp: [], agents: [], rules: [] };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let satisfiedCount = 0;
|
|
114
|
+
const allExisting = [
|
|
115
|
+
...existingComponents.mcp,
|
|
116
|
+
...existingComponents.agents.map(a => a.name || a),
|
|
117
|
+
...existingComponents.rules.map(r => r.name || r)
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
for (const dep of componentDependencies) {
|
|
121
|
+
if (allExisting.some(e =>
|
|
122
|
+
(e.id && e.id === dep) ||
|
|
123
|
+
(e.name && e.name === dep) ||
|
|
124
|
+
(typeof e === 'string' && e === dep)
|
|
125
|
+
)) {
|
|
126
|
+
satisfiedCount++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (satisfiedCount === componentDependencies.length) {
|
|
131
|
+
return 100; // 全部依赖满足
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return Math.round((satisfiedCount / componentDependencies.length) * 100);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 计算复用得分(基于项目已有组件)
|
|
139
|
+
*/
|
|
140
|
+
calculateReuseScore(componentId, existingComponents) {
|
|
141
|
+
if (!existingComponents || !componentId) {
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 收集所有已有组件
|
|
146
|
+
const allExisting = [];
|
|
147
|
+
|
|
148
|
+
// MCP组件(直接是字符串ID数组)
|
|
149
|
+
if (Array.isArray(existingComponents.mcp)) {
|
|
150
|
+
allExisting.push(...existingComponents.mcp);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Agent组件(可能是对象数组)
|
|
154
|
+
if (Array.isArray(existingComponents.agents)) {
|
|
155
|
+
for (const agent of existingComponents.agents) {
|
|
156
|
+
if (typeof agent === 'string') {
|
|
157
|
+
allExisting.push(agent);
|
|
158
|
+
} else if (agent.name) {
|
|
159
|
+
allExisting.push(agent.name);
|
|
160
|
+
} else if (agent.id) {
|
|
161
|
+
allExisting.push(agent.id);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Rule组件(可能是对象数组)
|
|
167
|
+
if (Array.isArray(existingComponents.rules)) {
|
|
168
|
+
for (const rule of existingComponents.rules) {
|
|
169
|
+
if (typeof rule === 'string') {
|
|
170
|
+
allExisting.push(rule);
|
|
171
|
+
} else if (rule.name) {
|
|
172
|
+
allExisting.push(rule.name);
|
|
173
|
+
} else if (rule.id) {
|
|
174
|
+
allExisting.push(rule.id);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 精确匹配或ID部分匹配
|
|
180
|
+
const normalizedId = componentId.toLowerCase();
|
|
181
|
+
for (const existing of allExisting) {
|
|
182
|
+
if (typeof existing === 'string') {
|
|
183
|
+
const normalizedExisting = existing.toLowerCase();
|
|
184
|
+
// 精确匹配
|
|
185
|
+
if (normalizedExisting === normalizedId) {
|
|
186
|
+
return 100;
|
|
187
|
+
}
|
|
188
|
+
// 包含匹配(如 'rules-enforcer' 匹配 'rules-enforcer-mcp')
|
|
189
|
+
if (normalizedId.includes(normalizedExisting) || normalizedExisting.includes(normalizedId)) {
|
|
190
|
+
return 100;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return 0; // 不存在,0分
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 计算MCP组件综合分
|
|
200
|
+
*/
|
|
201
|
+
scoreMcpComponent(mcpComponent, projectInfo) {
|
|
202
|
+
const techStackScore = this.calculateTechStackMatchScore(
|
|
203
|
+
projectInfo.techStack,
|
|
204
|
+
mcpComponent.techStackMatch
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const complexityScore = this.calculateComplexityFitScore(
|
|
208
|
+
projectInfo.complexity,
|
|
209
|
+
mcpComponent.projectSizeRange
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const dependencyScore = this.calculateDependencyScore(
|
|
213
|
+
mcpComponent.dependencies,
|
|
214
|
+
projectInfo.existingComponents
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const reuseScore = this.calculateReuseScore(
|
|
218
|
+
mcpComponent.id,
|
|
219
|
+
projectInfo.existingComponents
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// 综合分 = (技术栈*0.4 + 复杂度*0.3 + 依赖*0.2 + 复用*0.1)
|
|
223
|
+
const totalScore = Math.round(
|
|
224
|
+
techStackScore * 0.4 +
|
|
225
|
+
complexityScore * 0.3 +
|
|
226
|
+
dependencyScore * 0.2 +
|
|
227
|
+
reuseScore * 0.1
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
id: mcpComponent.id,
|
|
232
|
+
name: mcpComponent.name,
|
|
233
|
+
type: mcpComponent.type,
|
|
234
|
+
scores: {
|
|
235
|
+
techStackMatch: techStackScore,
|
|
236
|
+
complexityFit: complexityScore,
|
|
237
|
+
dependency: dependencyScore,
|
|
238
|
+
reuse: reuseScore
|
|
239
|
+
},
|
|
240
|
+
totalScore,
|
|
241
|
+
weight: this.weights.mcp
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 计算规则组件综合分
|
|
247
|
+
*/
|
|
248
|
+
scoreRuleComponent(ruleComponent, projectInfo) {
|
|
249
|
+
const techStackScore = this.calculateTechStackMatchScore(
|
|
250
|
+
projectInfo.techStack,
|
|
251
|
+
ruleComponent.techStackMatch
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const complexityScore = this.calculateComplexityFitScore(
|
|
255
|
+
projectInfo.complexity,
|
|
256
|
+
ruleComponent.projectSizeRange
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const reuseScore = this.calculateReuseScore(
|
|
260
|
+
ruleComponent.id,
|
|
261
|
+
projectInfo.existingComponents
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// 规则通常无依赖
|
|
265
|
+
const dependencyScore = 100;
|
|
266
|
+
|
|
267
|
+
// 综合分 = (技术栈*0.4 + 复杂度*0.3 + 依赖*0.1 + 复用*0.2)
|
|
268
|
+
const totalScore = Math.round(
|
|
269
|
+
techStackScore * 0.4 +
|
|
270
|
+
complexityScore * 0.3 +
|
|
271
|
+
dependencyScore * 0.1 +
|
|
272
|
+
reuseScore * 0.2
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
id: ruleComponent.id,
|
|
277
|
+
name: ruleComponent.name,
|
|
278
|
+
level: ruleComponent.level,
|
|
279
|
+
module: ruleComponent.module,
|
|
280
|
+
scores: {
|
|
281
|
+
techStackMatch: techStackScore,
|
|
282
|
+
complexityFit: complexityScore,
|
|
283
|
+
dependency: dependencyScore,
|
|
284
|
+
reuse: reuseScore
|
|
285
|
+
},
|
|
286
|
+
totalScore,
|
|
287
|
+
weight: this.weights.rules
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 计算Agent组件综合分
|
|
293
|
+
*/
|
|
294
|
+
scoreAgentComponent(agentComponent, projectInfo) {
|
|
295
|
+
const techStackScore = this.calculateTechStackMatchScore(
|
|
296
|
+
projectInfo.techStack,
|
|
297
|
+
agentComponent.techStack
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const complexityScore = this.calculateComplexityFitScore(
|
|
301
|
+
projectInfo.complexity,
|
|
302
|
+
agentComponent.projectSizeRange || ['small', 'medium', 'large']
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
const dependencyScore = this.calculateDependencyScore(
|
|
306
|
+
agentComponent.recommendedMcp,
|
|
307
|
+
projectInfo.existingComponents
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const reuseScore = this.calculateReuseScore(
|
|
311
|
+
agentComponent.id,
|
|
312
|
+
projectInfo.existingComponents
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
// 综合分 = (技术栈*0.5 + 复杂度*0.2 + 依赖*0.2 + 复用*0.1)
|
|
316
|
+
const totalScore = Math.round(
|
|
317
|
+
techStackScore * 0.5 +
|
|
318
|
+
complexityScore * 0.2 +
|
|
319
|
+
dependencyScore * 0.2 +
|
|
320
|
+
reuseScore * 0.1
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
id: agentComponent.id,
|
|
325
|
+
name: agentComponent.name,
|
|
326
|
+
scores: {
|
|
327
|
+
techStackMatch: techStackScore,
|
|
328
|
+
complexityFit: complexityScore,
|
|
329
|
+
dependency: dependencyScore,
|
|
330
|
+
reuse: reuseScore
|
|
331
|
+
},
|
|
332
|
+
totalScore,
|
|
333
|
+
weight: this.weights.agent
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 获取当前权重配置
|
|
339
|
+
*/
|
|
340
|
+
getWeights() {
|
|
341
|
+
return { ...this.weights };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* 设置权重配置(预留外置接口)
|
|
346
|
+
*/
|
|
347
|
+
setWeights(newWeights) {
|
|
348
|
+
// 验证权重总和为1
|
|
349
|
+
const sum = (newWeights.mcp || 0) + (newWeights.rules || 0) + (newWeights.agent || 0);
|
|
350
|
+
if (Math.abs(sum - 1) > 0.001) {
|
|
351
|
+
throw new Error(`权重总和必须为1,当前总和: ${sum}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
this.weights = { ...newWeights };
|
|
355
|
+
return this.weights;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
module.exports = { Scorer };
|