@vodailoc/kilo-kit-mcp 1.1.1 → 1.2.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 +33 -3
- package/mcp/README.md +38 -10
- package/mcp/dist/formatters.js +142 -1
- package/mcp/dist/orchestration-audit.js +20 -0
- package/mcp/dist/orchestration-memory.js +258 -0
- package/mcp/dist/orchestration-types.js +1 -0
- package/mcp/dist/orchestrator.js +222 -0
- package/mcp/dist/question-templates.js +249 -0
- package/mcp/dist/route-analytics.js +149 -0
- package/mcp/dist/router.js +75 -82
- package/mcp/dist/routing-policy-data.js +241 -0
- package/mcp/dist/routing-policy.js +145 -0
- package/mcp/dist/server.js +93 -4
- package/mcp/dist/smoke-env.js +18 -0
- package/mcp/dist/smoke.js +68 -1
- package/mcp/package.json +1 -1
- package/package.json +1 -1
package/mcp/dist/router.js
CHANGED
|
@@ -1,98 +1,91 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { analyzeIntent, explainRoute, RULE_HIERARCHY, scoreSkillForRoute, workflowForMode, } from "./routing-policy.js";
|
|
2
|
+
export function routeIntent(registry, input, options = {}) {
|
|
3
|
+
const profile = analyzeIntent(input);
|
|
3
4
|
const limit = Math.max(1, Math.min(input.limit ?? 5, 10));
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
...searchMatches,
|
|
8
|
-
...registry.listSkills().filter((skill) => !candidateIds.has(skill.id)),
|
|
9
|
-
];
|
|
10
|
-
const ranked = candidates
|
|
11
|
-
.map((skill) => ({
|
|
12
|
-
skill,
|
|
13
|
-
score: scoreRoute(skill, input),
|
|
14
|
-
reason: explainRoute(skill, input),
|
|
15
|
-
}))
|
|
5
|
+
const ranked = registry
|
|
6
|
+
.listSkills()
|
|
7
|
+
.map((skill) => applyAnalyticsAdjustment(scoreSkillForRoute(skill, input, profile), profile.mode, options.analytics))
|
|
16
8
|
.filter((candidate) => candidate.score > 0)
|
|
17
|
-
.sort((left, right) => right.score - left.score || left.skill.id.localeCompare(right.skill.id))
|
|
18
|
-
|
|
19
|
-
const recommended = ranked.map((candidate, index) => ({
|
|
9
|
+
.sort((left, right) => right.score - left.score || left.skill.id.localeCompare(right.skill.id));
|
|
10
|
+
const recommended = ranked.slice(0, limit).map((candidate, index) => ({
|
|
20
11
|
skill: candidate.skill,
|
|
21
|
-
confidence: Math.max(0.1, Math.min(0.99, candidate.score / (candidate.score +
|
|
12
|
+
confidence: Math.max(0.1, Math.min(0.99, candidate.score / (candidate.score + 18 + index * 3))),
|
|
22
13
|
reason: candidate.reason,
|
|
14
|
+
score: candidate.score,
|
|
23
15
|
}));
|
|
24
|
-
|
|
16
|
+
const workflow = buildWorkflow(registry, profile.mode, ranked);
|
|
17
|
+
const decisionTrail = ranked.slice(0, 10).map((candidate) => ({
|
|
18
|
+
skillId: candidate.skill.id,
|
|
19
|
+
score: candidate.score,
|
|
20
|
+
matchedSignals: candidate.matchedSignals,
|
|
21
|
+
scoreBreakdown: candidate.scoreBreakdown,
|
|
22
|
+
reason: candidate.reason,
|
|
23
|
+
}));
|
|
24
|
+
const result = {
|
|
25
25
|
recommended,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
taskMode: profile.mode,
|
|
27
|
+
workflow,
|
|
28
|
+
ruleHierarchy: RULE_HIERARCHY,
|
|
29
|
+
decisionTrail,
|
|
30
|
+
nextAction: buildNextAction(recommended, workflow),
|
|
29
31
|
};
|
|
32
|
+
options.analytics?.record({
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
message: input.message,
|
|
35
|
+
taskMode: result.taskMode,
|
|
36
|
+
recommendedSkillIds: recommended.map((item) => item.skill.id),
|
|
37
|
+
workflowSkillIds: workflow.map((step) => step.skill.id),
|
|
38
|
+
decisionTrail,
|
|
39
|
+
});
|
|
40
|
+
return result;
|
|
30
41
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
input.context?.previousErrors,
|
|
36
|
-
...(input.context?.files ?? []),
|
|
37
|
-
]
|
|
38
|
-
.filter(Boolean)
|
|
39
|
-
.join(" ");
|
|
40
|
-
}
|
|
41
|
-
function scoreRoute(skill, input) {
|
|
42
|
-
const text = buildQuery(input).toLowerCase();
|
|
43
|
-
let score = 1;
|
|
44
|
-
if (text.includes("test") || text.includes("tdd") || text.includes("test trước")) {
|
|
45
|
-
if (skill.id === "engineering/tdd")
|
|
46
|
-
score += 50;
|
|
47
|
-
if (skill.id.includes("tdd"))
|
|
48
|
-
score += 25;
|
|
49
|
-
if (skill.id.includes("testing"))
|
|
50
|
-
score += 15;
|
|
51
|
-
}
|
|
52
|
-
if (text.includes("bug") || text.includes("fix") || text.includes("failing") || text.includes("error")) {
|
|
53
|
-
if (skill.id === "engineering/diagnose")
|
|
54
|
-
score += 35;
|
|
55
|
-
if (skill.id.includes("debug"))
|
|
56
|
-
score += 20;
|
|
57
|
-
if (skill.id === "kilo-kit/debugging/systematic")
|
|
58
|
-
score += 15;
|
|
59
|
-
}
|
|
60
|
-
if (text.includes("ui") || text.includes("dashboard") || text.includes("react") || text.includes("frontend")) {
|
|
61
|
-
if (skill.category === "design")
|
|
62
|
-
score += 35;
|
|
63
|
-
if (skill.id === "design/frontend-design")
|
|
64
|
-
score += 20;
|
|
42
|
+
function applyAnalyticsAdjustment(candidate, taskMode, analytics) {
|
|
43
|
+
const adjustment = analytics?.scoreAdjustment(candidate.skill.id, taskMode) ?? 0;
|
|
44
|
+
if (adjustment <= 0) {
|
|
45
|
+
return candidate;
|
|
65
46
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
47
|
+
const scoreBreakdown = { ...candidate.scoreBreakdown, analytics: adjustment };
|
|
48
|
+
return {
|
|
49
|
+
...candidate,
|
|
50
|
+
score: candidate.score + adjustment,
|
|
51
|
+
scoreBreakdown,
|
|
52
|
+
reason: explainRoute(candidate.skill, taskMode, candidate.matchedSignals, scoreBreakdown),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function buildWorkflow(registry, mode, ranked) {
|
|
56
|
+
const workflowDefinition = workflowForMode(mode);
|
|
57
|
+
if (workflowDefinition.length === 0) {
|
|
58
|
+
return ranked.slice(0, 1).map((candidate) => ({
|
|
59
|
+
skill: candidate.skill,
|
|
60
|
+
role: "primary",
|
|
61
|
+
reason: "Best available skill from metadata and signal scoring.",
|
|
62
|
+
}));
|
|
77
63
|
}
|
|
78
|
-
return
|
|
64
|
+
return workflowDefinition
|
|
65
|
+
.map((step) => {
|
|
66
|
+
const skill = findSkillById(registry, step.id);
|
|
67
|
+
return skill ? { skill, role: step.role, reason: step.reason } : null;
|
|
68
|
+
})
|
|
69
|
+
.filter((step) => step !== null);
|
|
79
70
|
}
|
|
80
|
-
function
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
reasons.push("the request asks for test-first work");
|
|
71
|
+
function findSkillById(registry, id) {
|
|
72
|
+
const [category, skill] = id.split("/");
|
|
73
|
+
if (!category || !skill) {
|
|
74
|
+
return undefined;
|
|
85
75
|
}
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
try {
|
|
77
|
+
return registry.getSkill(category, skill);
|
|
88
78
|
}
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
91
81
|
}
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
}
|
|
83
|
+
function buildNextAction(recommended, workflow) {
|
|
84
|
+
if (workflow.length > 0) {
|
|
85
|
+
const workflowOrder = workflow.map((step) => step.skill.id).join(" -> ");
|
|
86
|
+
return `Load skills in workflow order: ${workflowOrder}. Start with ${workflow[0]?.skill.id} using kilo_get_skill.`;
|
|
94
87
|
}
|
|
95
|
-
return
|
|
96
|
-
? `
|
|
97
|
-
:
|
|
88
|
+
return recommended[0]
|
|
89
|
+
? `Load ${recommended[0].skill.id} with kilo_get_skill before executing the workflow.`
|
|
90
|
+
: "No strong skill match found. Search with more specific task keywords or inspect skills/SKILLS_INDEX.md.";
|
|
98
91
|
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
export const RULE_HIERARCHY = [
|
|
2
|
+
"user-instructions",
|
|
3
|
+
"platform-safety",
|
|
4
|
+
"kilo-global",
|
|
5
|
+
"task-mode",
|
|
6
|
+
"selected-skill",
|
|
7
|
+
"on-demand-reference",
|
|
8
|
+
"verification",
|
|
9
|
+
];
|
|
10
|
+
export const SIGNAL_PATTERNS = [
|
|
11
|
+
{
|
|
12
|
+
signal: "workflow-optimization",
|
|
13
|
+
patterns: [
|
|
14
|
+
/\boptimi[sz]e\b/i,
|
|
15
|
+
/\btoi uu\b/i,
|
|
16
|
+
/\btối ưu\b/i,
|
|
17
|
+
/\bworkflow\b/i,
|
|
18
|
+
/\bwordflow\b/i,
|
|
19
|
+
/\brules?\b/i,
|
|
20
|
+
/\bskill(?:s)?\b/i,
|
|
21
|
+
/\bchong cheo\b/i,
|
|
22
|
+
/\bchồng chéo\b/i,
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
signal: "architecture",
|
|
27
|
+
patterns: [/\barchitecture\b/i, /\brefactor\b/i, /\bstructure\b/i, /\brouting\b/i, /\bkernel\b/i],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
signal: "context-engineering",
|
|
31
|
+
patterns: [/\bcontext\b/i, /\btoken\b/i, /\bagent\b/i, /\badaptive\b/i, /\bpredictive\b/i],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
signal: "skill-authoring",
|
|
35
|
+
patterns: [/\bSKILL\.md\b/i, /\bskill(?:s)?\b/i, /\brules?\b/i, /\bworkflow\b/i],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
signal: "bug-fix",
|
|
39
|
+
patterns: [/\bbugs?\b/i, /\bfix\b/i, /\bfailing\b/i, /\berrors?\b/i, /\bbroken\b/i, /\bcrash(?:es|ing)?\b/i],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
signal: "test-first",
|
|
43
|
+
patterns: [/\btdd\b/i, /\btest[- ]first\b/i, /\btest trước\b/i, /\bviết test trước\b/i, /\bviet test truoc\b/i],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
signal: "ui-work",
|
|
47
|
+
patterns: [/\bui\b/i, /\bdashboard\b/i, /\breact\b/i, /\bfrontend\b/i, /\binterface\b/i],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
signal: "review",
|
|
51
|
+
patterns: [/\breview\b/i, /\bPR\b/, /\bpull request\b/i, /\bmerge\b/i, /\bcode review\b/i],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
signal: "mcp",
|
|
55
|
+
patterns: [/\bmcp\b/i],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
signal: "verification",
|
|
59
|
+
patterns: [/\bverify\b/i, /\bverification\b/i, /\bvalidate\b/i, /\blint\b/i, /\bcomplete\b/i, /\bdone\b/i],
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
export const SKILL_SIGNAL_WEIGHTS = {
|
|
63
|
+
"engineering/improve-codebase-architecture": {
|
|
64
|
+
"workflow-optimization": 58,
|
|
65
|
+
architecture: 35,
|
|
66
|
+
"context-engineering": 8,
|
|
67
|
+
"skill-authoring": 12,
|
|
68
|
+
},
|
|
69
|
+
"engineering/context-engineering": {
|
|
70
|
+
"workflow-optimization": 46,
|
|
71
|
+
"context-engineering": 42,
|
|
72
|
+
architecture: 12,
|
|
73
|
+
mcp: 6,
|
|
74
|
+
},
|
|
75
|
+
"productivity/writing-skills": {
|
|
76
|
+
"workflow-optimization": 44,
|
|
77
|
+
"skill-authoring": 42,
|
|
78
|
+
"context-engineering": 6,
|
|
79
|
+
},
|
|
80
|
+
"engineering/diagnose": {
|
|
81
|
+
"bug-fix": 44,
|
|
82
|
+
"test-first": 8,
|
|
83
|
+
verification: 6,
|
|
84
|
+
},
|
|
85
|
+
"engineering/tdd": {
|
|
86
|
+
"test-first": 58,
|
|
87
|
+
"bug-fix": 22,
|
|
88
|
+
verification: 5,
|
|
89
|
+
},
|
|
90
|
+
"engineering/lint-and-validate": {
|
|
91
|
+
verification: 28,
|
|
92
|
+
"bug-fix": 10,
|
|
93
|
+
"test-first": 10,
|
|
94
|
+
review: 5,
|
|
95
|
+
},
|
|
96
|
+
"productivity/verification-before-completion": {
|
|
97
|
+
verification: 34,
|
|
98
|
+
"bug-fix": 12,
|
|
99
|
+
"test-first": 10,
|
|
100
|
+
"workflow-optimization": 10,
|
|
101
|
+
review: 18,
|
|
102
|
+
"ui-work": 8,
|
|
103
|
+
},
|
|
104
|
+
"productivity/brainstorming": {
|
|
105
|
+
"ui-work": 34,
|
|
106
|
+
"workflow-optimization": 8,
|
|
107
|
+
architecture: 5,
|
|
108
|
+
},
|
|
109
|
+
"design/frontend-design": {
|
|
110
|
+
"ui-work": 58,
|
|
111
|
+
},
|
|
112
|
+
"design/ui-styling": {
|
|
113
|
+
"ui-work": 46,
|
|
114
|
+
},
|
|
115
|
+
"engineering/code-review": {
|
|
116
|
+
review: 68,
|
|
117
|
+
verification: 10,
|
|
118
|
+
},
|
|
119
|
+
"operations/mcp-builder": {
|
|
120
|
+
mcp: 42,
|
|
121
|
+
architecture: 6,
|
|
122
|
+
},
|
|
123
|
+
"operations/mcp-management": {
|
|
124
|
+
mcp: 32,
|
|
125
|
+
"context-engineering": 8,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
export const WORKFLOWS = {
|
|
129
|
+
"workflow-optimization": [
|
|
130
|
+
{
|
|
131
|
+
id: "engineering/improve-codebase-architecture",
|
|
132
|
+
role: "primary",
|
|
133
|
+
reason: "Find the modules where workflow and rule responsibilities are shallow or duplicated.",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "engineering/context-engineering",
|
|
137
|
+
role: "support",
|
|
138
|
+
reason: "Tune context loading, routing metadata, and token-aware skill selection.",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: "productivity/writing-skills",
|
|
142
|
+
role: "support",
|
|
143
|
+
reason: "Improve rule wording, trigger metadata, and skill authoring discipline.",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "productivity/verification-before-completion",
|
|
147
|
+
role: "quality",
|
|
148
|
+
reason: "Require evidence before claiming workflow improvements are complete.",
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
"bug-test-first": [
|
|
152
|
+
{
|
|
153
|
+
id: "engineering/diagnose",
|
|
154
|
+
role: "prepare",
|
|
155
|
+
reason: "Reproduce and rank hypotheses before changing behavior.",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "engineering/tdd",
|
|
159
|
+
role: "primary",
|
|
160
|
+
reason: "Write the failing regression test before implementation.",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "engineering/lint-and-validate",
|
|
164
|
+
role: "quality",
|
|
165
|
+
reason: "Run static checks after implementation changes.",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "productivity/verification-before-completion",
|
|
169
|
+
role: "quality",
|
|
170
|
+
reason: "Verify tests and requirements before making completion claims.",
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
review: [
|
|
174
|
+
{
|
|
175
|
+
id: "engineering/code-review",
|
|
176
|
+
role: "primary",
|
|
177
|
+
reason: "Prioritize defects, regressions, and missing tests before summary.",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "productivity/verification-before-completion",
|
|
181
|
+
role: "quality",
|
|
182
|
+
reason: "Require fresh evidence before accepting review or merge readiness.",
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
ui: [
|
|
186
|
+
{
|
|
187
|
+
id: "productivity/brainstorming",
|
|
188
|
+
role: "prepare",
|
|
189
|
+
reason: "Resolve design intent before creating or modifying UI behavior.",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "design/frontend-design",
|
|
193
|
+
role: "primary",
|
|
194
|
+
reason: "Design the production-grade frontend experience.",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "design/ui-styling",
|
|
198
|
+
role: "support",
|
|
199
|
+
reason: "Apply accessible styling, component, and responsive layout patterns.",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: "productivity/verification-before-completion",
|
|
203
|
+
role: "quality",
|
|
204
|
+
reason: "Verify UI implementation evidence before completion claims.",
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
bug: [
|
|
208
|
+
{
|
|
209
|
+
id: "engineering/diagnose",
|
|
210
|
+
role: "primary",
|
|
211
|
+
reason: "Reproduce, minimize, hypothesize, instrument, fix, and regression-test.",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "engineering/lint-and-validate",
|
|
215
|
+
role: "quality",
|
|
216
|
+
reason: "Run static checks after implementation changes.",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "productivity/verification-before-completion",
|
|
220
|
+
role: "quality",
|
|
221
|
+
reason: "Verify the fix before claiming success.",
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
mcp: [
|
|
225
|
+
{
|
|
226
|
+
id: "operations/mcp-builder",
|
|
227
|
+
role: "primary",
|
|
228
|
+
reason: "Design or update MCP tools/resources/prompts with clear interfaces.",
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: "operations/mcp-management",
|
|
232
|
+
role: "support",
|
|
233
|
+
reason: "Inspect and manage MCP server capabilities efficiently.",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "productivity/verification-before-completion",
|
|
237
|
+
role: "quality",
|
|
238
|
+
reason: "Run MCP build/test/smoke evidence before completion claims.",
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { SIGNAL_PATTERNS, SKILL_SIGNAL_WEIGHTS, WORKFLOWS } from "./routing-policy-data.js";
|
|
2
|
+
export { RULE_HIERARCHY } from "./routing-policy-data.js";
|
|
3
|
+
export function analyzeIntent(input) {
|
|
4
|
+
const signals = new Set();
|
|
5
|
+
const message = input.message;
|
|
6
|
+
const contextText = [input.context?.mode, input.context?.previousErrors].filter(Boolean).join(" ");
|
|
7
|
+
const files = input.context?.files ?? [];
|
|
8
|
+
for (const { signal, patterns } of SIGNAL_PATTERNS) {
|
|
9
|
+
if (patterns.some((pattern) => pattern.test(message))) {
|
|
10
|
+
signals.add(signal);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (/\barchitecture\b/i.test(contextText)) {
|
|
14
|
+
signals.add("architecture");
|
|
15
|
+
}
|
|
16
|
+
if (/\breview\b/i.test(contextText)) {
|
|
17
|
+
signals.add("review");
|
|
18
|
+
}
|
|
19
|
+
if (/\bcoding\b/i.test(contextText) && signals.has("bug-fix")) {
|
|
20
|
+
signals.add("verification");
|
|
21
|
+
}
|
|
22
|
+
if (files.some((file) => file.endsWith(".tsx") || file.endsWith(".jsx"))) {
|
|
23
|
+
signals.add("ui-work");
|
|
24
|
+
}
|
|
25
|
+
if (files.some((file) => /KILO_MASTER|SKILLS_INDEX|router\.ts|SKILL\.md/.test(file))) {
|
|
26
|
+
signals.add("architecture");
|
|
27
|
+
signals.add("context-engineering");
|
|
28
|
+
}
|
|
29
|
+
if (signals.has("review")) {
|
|
30
|
+
return { mode: "review", signals };
|
|
31
|
+
}
|
|
32
|
+
if (signals.has("workflow-optimization")) {
|
|
33
|
+
return { mode: "workflow-optimization", signals };
|
|
34
|
+
}
|
|
35
|
+
if (signals.has("ui-work")) {
|
|
36
|
+
return { mode: "ui", signals };
|
|
37
|
+
}
|
|
38
|
+
if (signals.has("bug-fix") && signals.has("test-first")) {
|
|
39
|
+
return { mode: "bug-test-first", signals };
|
|
40
|
+
}
|
|
41
|
+
if (signals.has("bug-fix")) {
|
|
42
|
+
return { mode: "bug", signals };
|
|
43
|
+
}
|
|
44
|
+
if (signals.has("mcp")) {
|
|
45
|
+
return { mode: "mcp", signals };
|
|
46
|
+
}
|
|
47
|
+
return { mode: "general", signals };
|
|
48
|
+
}
|
|
49
|
+
export function scoreSkillForRoute(skill, input, profile) {
|
|
50
|
+
const matchedSignals = [];
|
|
51
|
+
const scoreBreakdown = {};
|
|
52
|
+
const policy = SKILL_SIGNAL_WEIGHTS[skill.id] ?? {};
|
|
53
|
+
for (const signal of profile.signals) {
|
|
54
|
+
const signalScore = policy[signal] ?? 0;
|
|
55
|
+
if (signalScore > 0) {
|
|
56
|
+
matchedSignals.push(signal);
|
|
57
|
+
scoreBreakdown[signal] = signalScore;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const metadataScore = scoreMetadata(skill, input);
|
|
61
|
+
if (metadataScore > 0) {
|
|
62
|
+
scoreBreakdown.metadata = metadataScore;
|
|
63
|
+
}
|
|
64
|
+
const workflowScore = workflowPriorityScore(skill.id, profile.mode);
|
|
65
|
+
if (workflowScore > 0) {
|
|
66
|
+
scoreBreakdown.workflow = workflowScore;
|
|
67
|
+
}
|
|
68
|
+
const penalty = conflictPenalty(skill.id, profile.mode);
|
|
69
|
+
if (penalty < 0) {
|
|
70
|
+
scoreBreakdown.conflict = penalty;
|
|
71
|
+
}
|
|
72
|
+
const score = Object.values(scoreBreakdown).reduce((total, value) => total + value, 0);
|
|
73
|
+
return {
|
|
74
|
+
skill,
|
|
75
|
+
score,
|
|
76
|
+
matchedSignals,
|
|
77
|
+
scoreBreakdown,
|
|
78
|
+
reason: explainRoute(skill, profile.mode, matchedSignals, scoreBreakdown),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function workflowForMode(mode) {
|
|
82
|
+
return WORKFLOWS[mode] ?? [];
|
|
83
|
+
}
|
|
84
|
+
export function explainRoute(skill, mode, matchedSignals, scoreBreakdown) {
|
|
85
|
+
const reasons = [];
|
|
86
|
+
if (matchedSignals.length > 0) {
|
|
87
|
+
reasons.push(`matched ${matchedSignals.join(", ")}`);
|
|
88
|
+
}
|
|
89
|
+
if (scoreBreakdown.workflow) {
|
|
90
|
+
reasons.push(`belongs to the ${mode} workflow`);
|
|
91
|
+
}
|
|
92
|
+
if (scoreBreakdown.metadata) {
|
|
93
|
+
reasons.push("matched skill metadata");
|
|
94
|
+
}
|
|
95
|
+
if (scoreBreakdown.analytics) {
|
|
96
|
+
reasons.push("has positive route analytics");
|
|
97
|
+
}
|
|
98
|
+
if (scoreBreakdown.conflict) {
|
|
99
|
+
reasons.push("received a conflict penalty");
|
|
100
|
+
}
|
|
101
|
+
return reasons.length > 0
|
|
102
|
+
? `Selected because it ${reasons.join(" and ")}.`
|
|
103
|
+
: `Selected from fallback ranking for ${skill.id}.`;
|
|
104
|
+
}
|
|
105
|
+
function scoreMetadata(skill, input) {
|
|
106
|
+
const query = normalizeText(buildQuery(input));
|
|
107
|
+
if (!query) {
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
const haystack = normalizeText([skill.id, skill.title, skill.description, skill.category].join(" "));
|
|
111
|
+
const tokens = query.split(" ").filter((token) => token.length >= 3);
|
|
112
|
+
const tokenHits = tokens.filter((token) => haystack.includes(token)).length;
|
|
113
|
+
const exactBoost = haystack.includes(query) ? 10 : 0;
|
|
114
|
+
return Math.min(18, exactBoost + tokenHits * 2);
|
|
115
|
+
}
|
|
116
|
+
function workflowPriorityScore(skillId, mode) {
|
|
117
|
+
const workflow = workflowForMode(mode);
|
|
118
|
+
const index = workflow.findIndex((step) => step.id === skillId);
|
|
119
|
+
if (index === -1) {
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
return 24 - index * 3;
|
|
123
|
+
}
|
|
124
|
+
function conflictPenalty(skillId, mode) {
|
|
125
|
+
if (mode === "review" && (skillId.includes("tdd") || skillId.includes("testing"))) {
|
|
126
|
+
return -45;
|
|
127
|
+
}
|
|
128
|
+
if (mode === "workflow-optimization" && skillId.startsWith("operations/mcp")) {
|
|
129
|
+
return -10;
|
|
130
|
+
}
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
function buildQuery(input) {
|
|
134
|
+
return [
|
|
135
|
+
input.message,
|
|
136
|
+
input.context?.mode,
|
|
137
|
+
input.context?.previousErrors,
|
|
138
|
+
...(input.context?.files ?? []),
|
|
139
|
+
]
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.join(" ");
|
|
142
|
+
}
|
|
143
|
+
function normalizeText(value) {
|
|
144
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
145
|
+
}
|