@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.
@@ -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();