@reconcrap/boss-recommend-mcp 1.2.6 → 1.2.7
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 +7 -0
- package/package.json +2 -1
- package/src/adapters.js +112 -60
- package/src/index.js +97 -0
- package/src/recommend-healing-config.js +131 -0
- package/src/recommend-healing-rules.json +261 -0
- package/src/self-heal.js +2237 -0
- package/src/test-self-heal.js +224 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +259 -153
- package/vendor/boss-recommend-search-cli/src/cli.js +98 -50
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { __testables as indexTestables } from "./index.js";
|
|
6
|
+
import { runRecommendSelfHeal, __testables as selfHealTestables } from "./self-heal.js";
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
handleRequest,
|
|
10
|
+
setRunSelfHealImplForTests
|
|
11
|
+
} = indexTestables;
|
|
12
|
+
|
|
13
|
+
const TOOL_RUN_RECOMMEND_SELF_HEAL = "run_recommend_self_heal";
|
|
14
|
+
|
|
15
|
+
function makeToolCall(id, name, args = {}) {
|
|
16
|
+
return {
|
|
17
|
+
jsonrpc: "2.0",
|
|
18
|
+
id,
|
|
19
|
+
method: "tools/call",
|
|
20
|
+
params: {
|
|
21
|
+
name,
|
|
22
|
+
arguments: args
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function readToolPayload(response) {
|
|
28
|
+
return response?.result?.structuredContent;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function callTool(name, args, id = 1) {
|
|
32
|
+
const response = await handleRequest(makeToolCall(id, name, args), process.cwd());
|
|
33
|
+
return {
|
|
34
|
+
payload: await readToolPayload(response),
|
|
35
|
+
response
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function testToolsListShouldIncludeSelfHeal() {
|
|
40
|
+
const response = await handleRequest({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }, process.cwd());
|
|
41
|
+
const tools = response?.result?.tools || [];
|
|
42
|
+
assert.equal(tools.some((tool) => tool?.name === TOOL_RUN_RECOMMEND_SELF_HEAL), true);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function testIndexShouldRouteSelfHealTool() {
|
|
46
|
+
setRunSelfHealImplForTests(async () => ({ status: "HEALTHY", message: "ok" }));
|
|
47
|
+
try {
|
|
48
|
+
const { payload } = await callTool(TOOL_RUN_RECOMMEND_SELF_HEAL, {}, 2);
|
|
49
|
+
assert.equal(payload?.status, "HEALTHY");
|
|
50
|
+
} finally {
|
|
51
|
+
setRunSelfHealImplForTests(null);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function testScanShouldCreateRepairSession() {
|
|
56
|
+
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
57
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-self-heal-home-"));
|
|
58
|
+
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
59
|
+
try {
|
|
60
|
+
const result = await runRecommendSelfHeal(
|
|
61
|
+
{ workspaceRoot: process.cwd(), args: { mode: "scan" } },
|
|
62
|
+
{
|
|
63
|
+
scanRuntimeSurface: async () => ({
|
|
64
|
+
selector_checks: [
|
|
65
|
+
{
|
|
66
|
+
rule_id: "filter_trigger",
|
|
67
|
+
path: ["frame", "filter_trigger"],
|
|
68
|
+
root: "frame",
|
|
69
|
+
matches: [
|
|
70
|
+
{ selector: ".filter-label-wrap", index: 0, count: 0 },
|
|
71
|
+
{ selector: ".recommend-filter.op-filter", index: 1, count: 1 }
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
network_checks: [],
|
|
76
|
+
side_effect_summary: { opened_candidate_detail: false }
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
assert.equal(result.status, "NEED_CONFIRMATION");
|
|
81
|
+
assert.equal(typeof result.repair_session_id, "string");
|
|
82
|
+
assert.equal(result.proposed_repairs.length, 1);
|
|
83
|
+
const sessionPath = path.join(selfHealTestables.getSelfHealSessionsDir(), `${result.repair_session_id}.json`);
|
|
84
|
+
assert.equal(fs.existsSync(sessionPath), true);
|
|
85
|
+
} finally {
|
|
86
|
+
if (previousHome === undefined) {
|
|
87
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
88
|
+
} else {
|
|
89
|
+
process.env.BOSS_RECOMMEND_HOME = previousHome;
|
|
90
|
+
}
|
|
91
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function testOptionalSelectorMissShouldNotBecomeDrift() {
|
|
96
|
+
const drifts = selfHealTestables.analyzeSelectorChecks([
|
|
97
|
+
{
|
|
98
|
+
rule_id: "featured_cards",
|
|
99
|
+
path: ["frame", "featured_cards"],
|
|
100
|
+
root: "frame",
|
|
101
|
+
required: false,
|
|
102
|
+
report_on_no_match: false,
|
|
103
|
+
skipped: false,
|
|
104
|
+
matches: [
|
|
105
|
+
{ selector: "li.geek-info-card", index: 0, count: 0 }
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
]);
|
|
109
|
+
assert.equal(drifts.length, 0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function testApplyShouldRequireConfirmation() {
|
|
113
|
+
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
114
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-self-heal-apply-home-"));
|
|
115
|
+
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
116
|
+
try {
|
|
117
|
+
const scanResult = await runRecommendSelfHeal(
|
|
118
|
+
{ workspaceRoot: process.cwd(), args: { mode: "scan" } },
|
|
119
|
+
{
|
|
120
|
+
scanRuntimeSurface: async () => ({
|
|
121
|
+
selector_checks: [
|
|
122
|
+
{
|
|
123
|
+
rule_id: "filter_trigger",
|
|
124
|
+
path: ["frame", "filter_trigger"],
|
|
125
|
+
root: "frame",
|
|
126
|
+
matches: [
|
|
127
|
+
{ selector: ".filter-label-wrap", index: 0, count: 0 },
|
|
128
|
+
{ selector: ".recommend-filter.op-filter", index: 1, count: 1 }
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
network_checks: [],
|
|
133
|
+
side_effect_summary: null
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
const result = await runRecommendSelfHeal({
|
|
138
|
+
workspaceRoot: process.cwd(),
|
|
139
|
+
args: {
|
|
140
|
+
mode: "apply",
|
|
141
|
+
repair_session_id: scanResult.repair_session_id
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
assert.equal(result.status, "FAILED");
|
|
145
|
+
assert.equal(result.error?.code, "SELF_HEAL_CONFIRMATION_REQUIRED");
|
|
146
|
+
} finally {
|
|
147
|
+
if (previousHome === undefined) {
|
|
148
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
149
|
+
} else {
|
|
150
|
+
process.env.BOSS_RECOMMEND_HOME = previousHome;
|
|
151
|
+
}
|
|
152
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function testApplyShouldUpdateRulesFile() {
|
|
157
|
+
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
158
|
+
const previousRulesPath = process.env.BOSS_RECOMMEND_HEALING_RULES_FILE;
|
|
159
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-self-heal-rules-home-"));
|
|
160
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-self-heal-rules-"));
|
|
161
|
+
const rulesSourcePath = path.join(process.cwd(), "src", "recommend-healing-rules.json");
|
|
162
|
+
const tempRulesPath = path.join(tempDir, "recommend-healing-rules.json");
|
|
163
|
+
fs.copyFileSync(rulesSourcePath, tempRulesPath);
|
|
164
|
+
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
165
|
+
process.env.BOSS_RECOMMEND_HEALING_RULES_FILE = tempRulesPath;
|
|
166
|
+
try {
|
|
167
|
+
const scanResult = await runRecommendSelfHeal(
|
|
168
|
+
{ workspaceRoot: process.cwd(), args: { mode: "scan" } },
|
|
169
|
+
{
|
|
170
|
+
scanRuntimeSurface: async () => ({
|
|
171
|
+
selector_checks: [
|
|
172
|
+
{
|
|
173
|
+
rule_id: "filter_trigger",
|
|
174
|
+
path: ["frame", "filter_trigger"],
|
|
175
|
+
root: "frame",
|
|
176
|
+
matches: [
|
|
177
|
+
{ selector: ".filter-label-wrap", index: 0, count: 0 },
|
|
178
|
+
{ selector: ".recommend-filter.op-filter", index: 1, count: 1 }
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
network_checks: [],
|
|
183
|
+
side_effect_summary: null
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
const result = await runRecommendSelfHeal({
|
|
188
|
+
workspaceRoot: process.cwd(),
|
|
189
|
+
args: {
|
|
190
|
+
mode: "apply",
|
|
191
|
+
repair_session_id: scanResult.repair_session_id,
|
|
192
|
+
confirm_apply: true
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
assert.equal(result.status, "REPAIRED");
|
|
196
|
+
const updatedRules = JSON.parse(fs.readFileSync(tempRulesPath, "utf8"));
|
|
197
|
+
assert.equal(updatedRules.selectors.frame.filter_trigger[0], ".recommend-filter.op-filter");
|
|
198
|
+
} finally {
|
|
199
|
+
if (previousHome === undefined) {
|
|
200
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
201
|
+
} else {
|
|
202
|
+
process.env.BOSS_RECOMMEND_HOME = previousHome;
|
|
203
|
+
}
|
|
204
|
+
if (previousRulesPath === undefined) {
|
|
205
|
+
delete process.env.BOSS_RECOMMEND_HEALING_RULES_FILE;
|
|
206
|
+
} else {
|
|
207
|
+
process.env.BOSS_RECOMMEND_HEALING_RULES_FILE = previousRulesPath;
|
|
208
|
+
}
|
|
209
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
210
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function main() {
|
|
215
|
+
await testToolsListShouldIncludeSelfHeal();
|
|
216
|
+
await testIndexShouldRouteSelfHealTool();
|
|
217
|
+
await testScanShouldCreateRepairSession();
|
|
218
|
+
await testOptionalSelectorMissShouldNotBecomeDrift();
|
|
219
|
+
await testApplyShouldRequireConfirmation();
|
|
220
|
+
await testApplyShouldUpdateRulesFile();
|
|
221
|
+
console.log("self-heal tests passed");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
await main();
|