executable-stories-mcp 0.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 +81 -0
- package/dist/http.cjs +313 -0
- package/dist/http.cjs.map +1 -0
- package/dist/http.d.cts +11 -0
- package/dist/http.d.ts +11 -0
- package/dist/http.js +282 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +254 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +94 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +209 -0
- package/dist/index.js.map +1 -0
- package/dist/server.js +332 -0
- package/dist/server.js.map +1 -0
- package/package.json +63 -0
package/dist/http.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// src/http.ts
|
|
2
|
+
import http from "http";
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import {
|
|
9
|
+
diffStoryReports,
|
|
10
|
+
scenariosCoveringPaths,
|
|
11
|
+
toBehaviorManifest,
|
|
12
|
+
toScenarioIndex
|
|
13
|
+
} from "executable-stories-formatters";
|
|
14
|
+
function loadStoryReport(reportPath) {
|
|
15
|
+
const absolutePath = path.resolve(reportPath);
|
|
16
|
+
const parsed = JSON.parse(fs.readFileSync(absolutePath, "utf8"));
|
|
17
|
+
assertStoryReport(parsed, absolutePath);
|
|
18
|
+
return parsed;
|
|
19
|
+
}
|
|
20
|
+
function listScenarios(report, filters) {
|
|
21
|
+
return toScenarioIndex(report, filters).scenarios;
|
|
22
|
+
}
|
|
23
|
+
function getFailingScenarios(report) {
|
|
24
|
+
return listScenarios(report, { statuses: ["failed"] });
|
|
25
|
+
}
|
|
26
|
+
function getScenariosForPaths(report, paths) {
|
|
27
|
+
return scenariosCoveringPaths(toScenarioIndex(report), paths);
|
|
28
|
+
}
|
|
29
|
+
function getBehaviorDiff(baseline, current) {
|
|
30
|
+
return diffStoryReports(baseline, current);
|
|
31
|
+
}
|
|
32
|
+
function getScenario(report, idOrTitle) {
|
|
33
|
+
for (const feature of report.features) {
|
|
34
|
+
const scenario = feature.scenarios.find(
|
|
35
|
+
(candidate) => candidate.id === idOrTitle || candidate.title === idOrTitle
|
|
36
|
+
);
|
|
37
|
+
if (scenario) return { feature, scenario };
|
|
38
|
+
}
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
function getFeatureSummary(report) {
|
|
42
|
+
return report.features.map((feature) => ({
|
|
43
|
+
id: feature.id,
|
|
44
|
+
title: feature.title,
|
|
45
|
+
sourceFile: feature.sourceFile,
|
|
46
|
+
total: feature.summary.total,
|
|
47
|
+
passed: feature.summary.passed,
|
|
48
|
+
failed: feature.summary.failed,
|
|
49
|
+
skipped: feature.summary.skipped,
|
|
50
|
+
pending: feature.summary.pending,
|
|
51
|
+
durationMs: feature.summary.durationMs
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
function resolveReportPath(reportPath) {
|
|
55
|
+
return path.resolve(reportPath ?? "reports/index.story-report.json");
|
|
56
|
+
}
|
|
57
|
+
function getScenarioIndex(report) {
|
|
58
|
+
return toScenarioIndex(report);
|
|
59
|
+
}
|
|
60
|
+
function getBehaviorManifest(report) {
|
|
61
|
+
return toBehaviorManifest(report);
|
|
62
|
+
}
|
|
63
|
+
var readOnlyTools = [
|
|
64
|
+
{
|
|
65
|
+
name: "get_failing_scenarios",
|
|
66
|
+
title: "Get failing scenarios",
|
|
67
|
+
description: "List failing executable story scenarios from StoryReport JSON.",
|
|
68
|
+
route: "/scenarios/failing",
|
|
69
|
+
run: getFailingScenarios
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "get_feature_summary",
|
|
73
|
+
title: "Get feature summary",
|
|
74
|
+
description: "Summarize features and scenario status counts from StoryReport JSON.",
|
|
75
|
+
route: "/features",
|
|
76
|
+
run: getFeatureSummary
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "get_scenario_index",
|
|
80
|
+
title: "Get scenario index",
|
|
81
|
+
description: "Return the Storybook-like scenario index artifact (schema v1) derived from StoryReport JSON.",
|
|
82
|
+
route: "/scenarios-index",
|
|
83
|
+
run: getScenarioIndex
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "get_behavior_manifest",
|
|
87
|
+
title: "Get behavior manifest",
|
|
88
|
+
description: "Return agent-oriented manifest metadata: source files, tags, doc coverage, debugger warnings.",
|
|
89
|
+
route: "/manifest",
|
|
90
|
+
run: getBehaviorManifest
|
|
91
|
+
}
|
|
92
|
+
];
|
|
93
|
+
var RUNNERS = {
|
|
94
|
+
vitest: {
|
|
95
|
+
framework: "vitest",
|
|
96
|
+
buildCommand: ({ sourceFile, scenarioTitle }) => ({
|
|
97
|
+
command: "pnpm",
|
|
98
|
+
args: ["exec", "vitest", "run", sourceFile, ...scenarioTitle ? ["-t", scenarioTitle] : []]
|
|
99
|
+
})
|
|
100
|
+
},
|
|
101
|
+
jest: {
|
|
102
|
+
framework: "jest",
|
|
103
|
+
buildCommand: ({ sourceFile, scenarioTitle }) => ({
|
|
104
|
+
command: "pnpm",
|
|
105
|
+
args: ["exec", "jest", sourceFile, ...scenarioTitle ? ["-t", scenarioTitle] : [], "--runInBand"]
|
|
106
|
+
})
|
|
107
|
+
},
|
|
108
|
+
playwright: {
|
|
109
|
+
framework: "playwright",
|
|
110
|
+
detect: (sourceFile) => sourceFile.includes(".story.spec."),
|
|
111
|
+
buildCommand: ({ sourceFile, scenarioTitle }) => ({
|
|
112
|
+
command: "pnpm",
|
|
113
|
+
args: ["exec", "playwright", "test", sourceFile, ...scenarioTitle ? ["-g", scenarioTitle] : []]
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
cypress: {
|
|
117
|
+
framework: "cypress",
|
|
118
|
+
detect: (sourceFile) => sourceFile.includes(".story.cy."),
|
|
119
|
+
buildCommand: ({ sourceFile }) => ({
|
|
120
|
+
command: "pnpm",
|
|
121
|
+
args: ["exec", "cypress", "run", "--spec", sourceFile]
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function buildFocusedRunCommand(args) {
|
|
126
|
+
return RUNNERS[args.framework].buildCommand(args);
|
|
127
|
+
}
|
|
128
|
+
async function runFocusedScenario(args) {
|
|
129
|
+
const command = buildFocusedRunCommand(args);
|
|
130
|
+
const spawnFn = args.spawnFn ?? spawn;
|
|
131
|
+
return new Promise((resolve2) => {
|
|
132
|
+
const child = spawnFn(command.command, command.args, {
|
|
133
|
+
cwd: args.cwd,
|
|
134
|
+
env: process.env
|
|
135
|
+
});
|
|
136
|
+
let stdout = "";
|
|
137
|
+
let stderr = "";
|
|
138
|
+
child.stdout.on("data", (chunk) => {
|
|
139
|
+
stdout += String(chunk);
|
|
140
|
+
});
|
|
141
|
+
child.stderr.on("data", (chunk) => {
|
|
142
|
+
stderr += String(chunk);
|
|
143
|
+
});
|
|
144
|
+
child.on("error", (error) => {
|
|
145
|
+
resolve2({
|
|
146
|
+
ok: false,
|
|
147
|
+
exitCode: null,
|
|
148
|
+
command: command.command,
|
|
149
|
+
args: command.args,
|
|
150
|
+
stdout,
|
|
151
|
+
stderr: stderr + error.message
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
child.on("close", (exitCode) => {
|
|
155
|
+
resolve2({
|
|
156
|
+
ok: exitCode === 0,
|
|
157
|
+
exitCode,
|
|
158
|
+
command: command.command,
|
|
159
|
+
args: command.args,
|
|
160
|
+
stdout,
|
|
161
|
+
stderr
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function assertStoryReport(value, reportPath) {
|
|
167
|
+
if (!value || typeof value !== "object") {
|
|
168
|
+
throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected object`);
|
|
169
|
+
}
|
|
170
|
+
const report = value;
|
|
171
|
+
if (typeof report.schemaVersion !== "string" || !report.schemaVersion.startsWith("1.")) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Invalid StoryReport JSON in ${reportPath}: expected schemaVersion 1.x`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (!Array.isArray(report.features)) {
|
|
177
|
+
throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected features array`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/http.ts
|
|
182
|
+
var readOnlyRoutes = new Map(readOnlyTools.map((tool) => [tool.route, tool.run]));
|
|
183
|
+
function parseFilters(params) {
|
|
184
|
+
const statuses = params.getAll("status");
|
|
185
|
+
const tags = params.getAll("tag");
|
|
186
|
+
const sourceFiles = params.getAll("sourceFile");
|
|
187
|
+
return {
|
|
188
|
+
statuses: statuses.length ? statuses : void 0,
|
|
189
|
+
tags: tags.length ? tags : void 0,
|
|
190
|
+
sourceFiles: sourceFiles.length ? sourceFiles : void 0
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function createHttpServer(options = {}) {
|
|
194
|
+
return http.createServer(async (request, response) => {
|
|
195
|
+
try {
|
|
196
|
+
if (!request.url) {
|
|
197
|
+
sendJson(response, 404, { error: "Missing URL" });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const url = new URL(request.url, "http://localhost");
|
|
201
|
+
const reportPath = url.searchParams.get("reportPath") ?? options.reportPath;
|
|
202
|
+
if (request.method === "GET" && url.pathname === "/health") {
|
|
203
|
+
sendJson(response, 200, { ok: true, name: "executable-stories-mcp" });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (request.method === "GET" && url.pathname === "/diff") {
|
|
207
|
+
const baseline = loadStoryReport(
|
|
208
|
+
resolveReportPath(url.searchParams.get("baseline") ?? void 0)
|
|
209
|
+
);
|
|
210
|
+
const current = loadStoryReport(
|
|
211
|
+
resolveReportPath(url.searchParams.get("current") ?? reportPath ?? void 0)
|
|
212
|
+
);
|
|
213
|
+
sendJson(response, 200, getBehaviorDiff(baseline, current));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (request.method === "GET") {
|
|
217
|
+
const report = loadStoryReport(resolveReportPath(reportPath));
|
|
218
|
+
if (url.pathname === "/scenarios") {
|
|
219
|
+
sendJson(response, 200, listScenarios(report, parseFilters(url.searchParams)));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (url.pathname === "/scenarios/covering") {
|
|
223
|
+
sendJson(response, 200, getScenariosForPaths(report, url.searchParams.getAll("path")));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const handler = readOnlyRoutes.get(url.pathname);
|
|
227
|
+
if (handler) {
|
|
228
|
+
sendJson(response, 200, handler(report));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (url.pathname.startsWith("/scenarios/")) {
|
|
232
|
+
const id = decodeURIComponent(url.pathname.slice("/scenarios/".length));
|
|
233
|
+
const scenario = getScenario(report, id);
|
|
234
|
+
sendJson(response, scenario ? 200 : 404, scenario ?? { error: `Scenario not found: ${id}` });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (request.method === "POST" && url.pathname === "/run-scenarios") {
|
|
239
|
+
const body = await readJsonBody(request);
|
|
240
|
+
const result = await runFocusedScenario({
|
|
241
|
+
framework: body.framework,
|
|
242
|
+
sourceFile: String(body.sourceFile),
|
|
243
|
+
scenarioTitle: typeof body.scenarioTitle === "string" ? body.scenarioTitle : void 0,
|
|
244
|
+
cwd: typeof body.cwd === "string" ? body.cwd : void 0
|
|
245
|
+
});
|
|
246
|
+
sendJson(response, result.ok ? 200 : 500, result);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
sendJson(response, 404, { error: "Not found" });
|
|
250
|
+
} catch (error) {
|
|
251
|
+
sendJson(response, 500, { error: error.message });
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
async function startHttpServer(options = {}) {
|
|
256
|
+
const server = createHttpServer(options);
|
|
257
|
+
const port = options.port ?? 7357;
|
|
258
|
+
const host = options.host ?? "127.0.0.1";
|
|
259
|
+
await new Promise((resolve2) => {
|
|
260
|
+
server.listen(port, host, resolve2);
|
|
261
|
+
});
|
|
262
|
+
return server;
|
|
263
|
+
}
|
|
264
|
+
function sendJson(response, statusCode, value) {
|
|
265
|
+
response.writeHead(statusCode, {
|
|
266
|
+
"content-type": "application/json; charset=utf-8",
|
|
267
|
+
"access-control-allow-origin": "*"
|
|
268
|
+
});
|
|
269
|
+
response.end(JSON.stringify(value, null, 2));
|
|
270
|
+
}
|
|
271
|
+
async function readJsonBody(request) {
|
|
272
|
+
const chunks = [];
|
|
273
|
+
for await (const chunk of request) {
|
|
274
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
275
|
+
}
|
|
276
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
277
|
+
}
|
|
278
|
+
export {
|
|
279
|
+
createHttpServer,
|
|
280
|
+
startHttpServer
|
|
281
|
+
};
|
|
282
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http.ts","../src/index.ts"],"sourcesContent":["import http from \"node:http\";\n\nimport {\n getBehaviorDiff,\n getScenario,\n getScenariosForPaths,\n listScenarios,\n loadStoryReport,\n readOnlyTools,\n resolveReportPath,\n runFocusedScenario,\n type FocusedRunFramework,\n type ScenarioIndexFilters,\n} from \"./index.js\";\n\nexport interface HttpServerOptions {\n port?: number;\n host?: string;\n reportPath?: string;\n}\n\n/** Exact GET routes shared with the stdio MCP server (see readOnlyTools). */\nconst readOnlyRoutes = new Map(readOnlyTools.map((tool) => [tool.route, tool.run]));\n\n/** Parse the repeatable filter query params (?status=&tag=&sourceFile=). */\nfunction parseFilters(params: URLSearchParams): ScenarioIndexFilters {\n const statuses = params.getAll(\"status\");\n const tags = params.getAll(\"tag\");\n const sourceFiles = params.getAll(\"sourceFile\");\n return {\n statuses: statuses.length ? (statuses as ScenarioIndexFilters[\"statuses\"]) : undefined,\n tags: tags.length ? tags : undefined,\n sourceFiles: sourceFiles.length ? sourceFiles : undefined,\n };\n}\n\nexport function createHttpServer(options: HttpServerOptions = {}): http.Server {\n return http.createServer(async (request, response) => {\n try {\n if (!request.url) {\n sendJson(response, 404, { error: \"Missing URL\" });\n return;\n }\n\n const url = new URL(request.url, \"http://localhost\");\n const reportPath = url.searchParams.get(\"reportPath\") ?? options.reportPath;\n\n if (request.method === \"GET\" && url.pathname === \"/health\") {\n sendJson(response, 200, { ok: true, name: \"executable-stories-mcp\" });\n return;\n }\n\n // Diff compares two reports, so it resolves before the single-report load.\n if (request.method === \"GET\" && url.pathname === \"/diff\") {\n const baseline = loadStoryReport(\n resolveReportPath(url.searchParams.get(\"baseline\") ?? undefined),\n );\n const current = loadStoryReport(\n resolveReportPath(url.searchParams.get(\"current\") ?? reportPath ?? undefined),\n );\n sendJson(response, 200, getBehaviorDiff(baseline, current));\n return;\n }\n\n if (request.method === \"GET\") {\n const report = loadStoryReport(resolveReportPath(reportPath));\n\n // Arg-taking routes resolve before the no-arg catalog and the dynamic\n // /scenarios/:id route.\n if (url.pathname === \"/scenarios\") {\n sendJson(response, 200, listScenarios(report, parseFilters(url.searchParams)));\n return;\n }\n if (url.pathname === \"/scenarios/covering\") {\n sendJson(response, 200, getScenariosForPaths(report, url.searchParams.getAll(\"path\")));\n return;\n }\n\n // Exact catalog routes resolve before the dynamic /scenarios/:id route,\n // so /scenarios/failing keeps working as a fixed endpoint.\n const handler = readOnlyRoutes.get(url.pathname);\n if (handler) {\n sendJson(response, 200, handler(report));\n return;\n }\n if (url.pathname.startsWith(\"/scenarios/\")) {\n const id = decodeURIComponent(url.pathname.slice(\"/scenarios/\".length));\n const scenario = getScenario(report, id);\n sendJson(response, scenario ? 200 : 404, scenario ?? { error: `Scenario not found: ${id}` });\n return;\n }\n }\n\n if (request.method === \"POST\" && url.pathname === \"/run-scenarios\") {\n const body = await readJsonBody(request);\n const result = await runFocusedScenario({\n framework: body.framework as FocusedRunFramework,\n sourceFile: String(body.sourceFile),\n scenarioTitle: typeof body.scenarioTitle === \"string\" ? body.scenarioTitle : undefined,\n cwd: typeof body.cwd === \"string\" ? body.cwd : undefined,\n });\n sendJson(response, result.ok ? 200 : 500, result);\n return;\n }\n\n sendJson(response, 404, { error: \"Not found\" });\n } catch (error) {\n sendJson(response, 500, { error: (error as Error).message });\n }\n });\n}\n\nexport async function startHttpServer(options: HttpServerOptions = {}): Promise<http.Server> {\n const server = createHttpServer(options);\n const port = options.port ?? 7357;\n const host = options.host ?? \"127.0.0.1\";\n await new Promise<void>((resolve) => {\n server.listen(port, host, resolve);\n });\n return server;\n}\n\nfunction sendJson(response: http.ServerResponse, statusCode: number, value: unknown): void {\n response.writeHead(statusCode, {\n \"content-type\": \"application/json; charset=utf-8\",\n \"access-control-allow-origin\": \"*\",\n });\n response.end(JSON.stringify(value, null, 2));\n}\n\nasync function readJsonBody(request: http.IncomingMessage): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n for await (const chunk of request) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return JSON.parse(Buffer.concat(chunks).toString(\"utf8\") || \"{}\") as Record<string, unknown>;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\n\nimport type {\n ReportFeature,\n ReportScenario,\n StoryReport,\n BehaviorManifest,\n BehaviorDiff,\n ScenarioIndex,\n ScenarioIndexFilters,\n ScenarioIndexItem,\n} from \"executable-stories-formatters\";\nimport {\n diffStoryReports,\n scenariosCoveringPaths,\n toBehaviorManifest,\n toScenarioIndex,\n} from \"executable-stories-formatters\";\n\n// Scenario serialization is owned by the formatters package; re-export so MCP\n// consumers get the same shape without a parallel definition to maintain.\nexport type { ScenarioIndexItem, ScenarioIndexFilters };\n\nexport interface FeatureSummaryItem {\n id: string;\n title: string;\n sourceFile: string;\n total: number;\n passed: number;\n failed: number;\n skipped: number;\n pending: number;\n durationMs: number;\n}\n\nexport interface ScenarioLookup {\n feature: ReportFeature;\n scenario: ReportScenario;\n}\n\nexport function loadStoryReport(reportPath: string): StoryReport {\n const absolutePath = path.resolve(reportPath);\n const parsed: unknown = JSON.parse(fs.readFileSync(absolutePath, \"utf8\"));\n assertStoryReport(parsed, absolutePath);\n return parsed;\n}\n\nexport function listScenarios(\n report: StoryReport,\n filters?: ScenarioIndexFilters,\n): ScenarioIndexItem[] {\n return toScenarioIndex(report, filters).scenarios;\n}\n\nexport function getFailingScenarios(report: StoryReport): ScenarioIndexItem[] {\n return listScenarios(report, { statuses: [\"failed\"] });\n}\n\nexport function getScenariosForPaths(report: StoryReport, paths: string[]): ScenarioIndexItem[] {\n return scenariosCoveringPaths(toScenarioIndex(report), paths);\n}\n\nexport function getBehaviorDiff(baseline: StoryReport, current: StoryReport): BehaviorDiff {\n return diffStoryReports(baseline, current);\n}\n\nexport function getScenario(report: StoryReport, idOrTitle: string): ScenarioLookup | undefined {\n for (const feature of report.features) {\n const scenario = feature.scenarios.find(\n (candidate) => candidate.id === idOrTitle || candidate.title === idOrTitle,\n );\n if (scenario) return { feature, scenario };\n }\n return undefined;\n}\n\nexport function getFeatureSummary(report: StoryReport): FeatureSummaryItem[] {\n return report.features.map((feature) => ({\n id: feature.id,\n title: feature.title,\n sourceFile: feature.sourceFile,\n total: feature.summary.total,\n passed: feature.summary.passed,\n failed: feature.summary.failed,\n skipped: feature.summary.skipped,\n pending: feature.summary.pending,\n durationMs: feature.summary.durationMs,\n }));\n}\n\nexport function resolveReportPath(reportPath?: string): string {\n return path.resolve(reportPath ?? \"reports/index.story-report.json\");\n}\n\nexport function getScenarioIndex(report: StoryReport): ScenarioIndex {\n return toScenarioIndex(report);\n}\n\nexport function getBehaviorManifest(report: StoryReport): BehaviorManifest {\n return toBehaviorManifest(report);\n}\n\n/**\n * Single source of truth for the read-only tools, consumed by both the stdio\n * MCP server and the HTTP server so the two transports cannot drift apart.\n * Tools needing extra arguments (get_scenario, run_scenario) are wired up\n * directly in each transport.\n */\nexport interface ReadOnlyTool {\n /** MCP tool name. */\n name: string;\n /** Human-readable MCP tool title. */\n title: string;\n /** Shared description used by both transports. */\n description: string;\n /** HTTP route that exposes the same data. */\n route: string;\n /** Pure projection from a loaded report to its JSON payload. */\n run: (report: StoryReport) => unknown;\n}\n\nexport const readOnlyTools: ReadOnlyTool[] = [\n {\n name: \"get_failing_scenarios\",\n title: \"Get failing scenarios\",\n description: \"List failing executable story scenarios from StoryReport JSON.\",\n route: \"/scenarios/failing\",\n run: getFailingScenarios,\n },\n {\n name: \"get_feature_summary\",\n title: \"Get feature summary\",\n description: \"Summarize features and scenario status counts from StoryReport JSON.\",\n route: \"/features\",\n run: getFeatureSummary,\n },\n {\n name: \"get_scenario_index\",\n title: \"Get scenario index\",\n description:\n \"Return the Storybook-like scenario index artifact (schema v1) derived from StoryReport JSON.\",\n route: \"/scenarios-index\",\n run: getScenarioIndex,\n },\n {\n name: \"get_behavior_manifest\",\n title: \"Get behavior manifest\",\n description:\n \"Return agent-oriented manifest metadata: source files, tags, doc coverage, debugger warnings.\",\n route: \"/manifest\",\n run: getBehaviorManifest,\n },\n];\n\nexport type FocusedRunFramework = \"vitest\" | \"jest\" | \"playwright\" | \"cypress\";\n\nexport interface FocusedRunCommandArgs {\n framework: FocusedRunFramework;\n sourceFile: string;\n scenarioTitle?: string;\n}\n\nexport interface FocusedRunCommand {\n command: string;\n args: string[];\n}\n\nexport interface FocusedRunResult {\n ok: boolean;\n exitCode: number | null;\n command: string;\n args: string[];\n stdout: string;\n stderr: string;\n}\n\n/**\n * One runner per host framework. The seam that keeps `run_scenario` extensible:\n * adding a non-JS framework (go, cargo, pytest, dotnet) later is a single new\n * entry here — no changes to inference, command building, or the transports.\n */\nexport interface RunnerDefinition {\n framework: FocusedRunFramework;\n /** Infer this framework from a source-file path, when unambiguous. */\n detect?: (sourceFile: string) => boolean;\n /** Build the focused-run command for this framework. */\n buildCommand: (args: { sourceFile: string; scenarioTitle?: string }) => FocusedRunCommand;\n}\n\nexport const RUNNERS: Record<FocusedRunFramework, RunnerDefinition> = {\n vitest: {\n framework: \"vitest\",\n buildCommand: ({ sourceFile, scenarioTitle }) => ({\n command: \"pnpm\",\n args: [\"exec\", \"vitest\", \"run\", sourceFile, ...(scenarioTitle ? [\"-t\", scenarioTitle] : [])],\n }),\n },\n jest: {\n framework: \"jest\",\n buildCommand: ({ sourceFile, scenarioTitle }) => ({\n command: \"pnpm\",\n args: [\"exec\", \"jest\", sourceFile, ...(scenarioTitle ? [\"-t\", scenarioTitle] : []), \"--runInBand\"],\n }),\n },\n playwright: {\n framework: \"playwright\",\n detect: (sourceFile) => sourceFile.includes(\".story.spec.\"),\n buildCommand: ({ sourceFile, scenarioTitle }) => ({\n command: \"pnpm\",\n args: [\"exec\", \"playwright\", \"test\", sourceFile, ...(scenarioTitle ? [\"-g\", scenarioTitle] : [])],\n }),\n },\n cypress: {\n framework: \"cypress\",\n detect: (sourceFile) => sourceFile.includes(\".story.cy.\"),\n buildCommand: ({ sourceFile }) => ({\n command: \"pnpm\",\n args: [\"exec\", \"cypress\", \"run\", \"--spec\", sourceFile],\n }),\n },\n};\n\nexport function inferFrameworkFromSourceFile(\n sourceFile: string,\n): FocusedRunFramework | undefined {\n for (const runner of Object.values(RUNNERS)) {\n if (runner.detect?.(sourceFile)) return runner.framework;\n }\n return undefined;\n}\n\nexport function resolveFocusedRunFramework(args: {\n sourceFile: string;\n framework?: FocusedRunFramework;\n}): FocusedRunFramework {\n if (args.framework) return args.framework;\n const inferred = inferFrameworkFromSourceFile(args.sourceFile);\n if (inferred) return inferred;\n throw new Error(\n `Could not infer test framework from ${args.sourceFile}. Pass framework: vitest | jest | playwright | cypress.`,\n );\n}\n\nexport function buildFocusedRunCommand(args: FocusedRunCommandArgs): FocusedRunCommand {\n return RUNNERS[args.framework].buildCommand(args);\n}\n\nexport async function runFocusedScenario(args: FocusedRunCommandArgs & {\n cwd?: string;\n spawnFn?: typeof spawn;\n}): Promise<FocusedRunResult> {\n const command = buildFocusedRunCommand(args);\n const spawnFn = args.spawnFn ?? spawn;\n\n return new Promise((resolve) => {\n const child = spawnFn(command.command, command.args, {\n cwd: args.cwd,\n env: process.env,\n }) as ChildProcessWithoutNullStreams;\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout.on(\"data\", (chunk) => {\n stdout += String(chunk);\n });\n child.stderr.on(\"data\", (chunk) => {\n stderr += String(chunk);\n });\n child.on(\"error\", (error) => {\n resolve({\n ok: false,\n exitCode: null,\n command: command.command,\n args: command.args,\n stdout,\n stderr: stderr + error.message,\n });\n });\n child.on(\"close\", (exitCode) => {\n resolve({\n ok: exitCode === 0,\n exitCode,\n command: command.command,\n args: command.args,\n stdout,\n stderr,\n });\n });\n });\n}\n\nfunction assertStoryReport(\n value: unknown,\n reportPath: string,\n): asserts value is StoryReport {\n if (!value || typeof value !== \"object\") {\n throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected object`);\n }\n const report = value as Partial<StoryReport>;\n if (typeof report.schemaVersion !== \"string\" || !report.schemaVersion.startsWith(\"1.\")) {\n throw new Error(\n `Invalid StoryReport JSON in ${reportPath}: expected schemaVersion 1.x`,\n );\n }\n if (!Array.isArray(report.features)) {\n throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected features array`);\n }\n}\n"],"mappings":"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","names":["resolve","resolve"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
RUNNERS: () => RUNNERS,
|
|
34
|
+
buildFocusedRunCommand: () => buildFocusedRunCommand,
|
|
35
|
+
getBehaviorDiff: () => getBehaviorDiff,
|
|
36
|
+
getBehaviorManifest: () => getBehaviorManifest,
|
|
37
|
+
getFailingScenarios: () => getFailingScenarios,
|
|
38
|
+
getFeatureSummary: () => getFeatureSummary,
|
|
39
|
+
getScenario: () => getScenario,
|
|
40
|
+
getScenarioIndex: () => getScenarioIndex,
|
|
41
|
+
getScenariosForPaths: () => getScenariosForPaths,
|
|
42
|
+
inferFrameworkFromSourceFile: () => inferFrameworkFromSourceFile,
|
|
43
|
+
listScenarios: () => listScenarios,
|
|
44
|
+
loadStoryReport: () => loadStoryReport,
|
|
45
|
+
readOnlyTools: () => readOnlyTools,
|
|
46
|
+
resolveFocusedRunFramework: () => resolveFocusedRunFramework,
|
|
47
|
+
resolveReportPath: () => resolveReportPath,
|
|
48
|
+
runFocusedScenario: () => runFocusedScenario
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(src_exports);
|
|
51
|
+
var fs = __toESM(require("fs"), 1);
|
|
52
|
+
var path = __toESM(require("path"), 1);
|
|
53
|
+
var import_node_child_process = require("child_process");
|
|
54
|
+
var import_executable_stories_formatters = require("executable-stories-formatters");
|
|
55
|
+
function loadStoryReport(reportPath) {
|
|
56
|
+
const absolutePath = path.resolve(reportPath);
|
|
57
|
+
const parsed = JSON.parse(fs.readFileSync(absolutePath, "utf8"));
|
|
58
|
+
assertStoryReport(parsed, absolutePath);
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
function listScenarios(report, filters) {
|
|
62
|
+
return (0, import_executable_stories_formatters.toScenarioIndex)(report, filters).scenarios;
|
|
63
|
+
}
|
|
64
|
+
function getFailingScenarios(report) {
|
|
65
|
+
return listScenarios(report, { statuses: ["failed"] });
|
|
66
|
+
}
|
|
67
|
+
function getScenariosForPaths(report, paths) {
|
|
68
|
+
return (0, import_executable_stories_formatters.scenariosCoveringPaths)((0, import_executable_stories_formatters.toScenarioIndex)(report), paths);
|
|
69
|
+
}
|
|
70
|
+
function getBehaviorDiff(baseline, current) {
|
|
71
|
+
return (0, import_executable_stories_formatters.diffStoryReports)(baseline, current);
|
|
72
|
+
}
|
|
73
|
+
function getScenario(report, idOrTitle) {
|
|
74
|
+
for (const feature of report.features) {
|
|
75
|
+
const scenario = feature.scenarios.find(
|
|
76
|
+
(candidate) => candidate.id === idOrTitle || candidate.title === idOrTitle
|
|
77
|
+
);
|
|
78
|
+
if (scenario) return { feature, scenario };
|
|
79
|
+
}
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
function getFeatureSummary(report) {
|
|
83
|
+
return report.features.map((feature) => ({
|
|
84
|
+
id: feature.id,
|
|
85
|
+
title: feature.title,
|
|
86
|
+
sourceFile: feature.sourceFile,
|
|
87
|
+
total: feature.summary.total,
|
|
88
|
+
passed: feature.summary.passed,
|
|
89
|
+
failed: feature.summary.failed,
|
|
90
|
+
skipped: feature.summary.skipped,
|
|
91
|
+
pending: feature.summary.pending,
|
|
92
|
+
durationMs: feature.summary.durationMs
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
function resolveReportPath(reportPath) {
|
|
96
|
+
return path.resolve(reportPath ?? "reports/index.story-report.json");
|
|
97
|
+
}
|
|
98
|
+
function getScenarioIndex(report) {
|
|
99
|
+
return (0, import_executable_stories_formatters.toScenarioIndex)(report);
|
|
100
|
+
}
|
|
101
|
+
function getBehaviorManifest(report) {
|
|
102
|
+
return (0, import_executable_stories_formatters.toBehaviorManifest)(report);
|
|
103
|
+
}
|
|
104
|
+
var readOnlyTools = [
|
|
105
|
+
{
|
|
106
|
+
name: "get_failing_scenarios",
|
|
107
|
+
title: "Get failing scenarios",
|
|
108
|
+
description: "List failing executable story scenarios from StoryReport JSON.",
|
|
109
|
+
route: "/scenarios/failing",
|
|
110
|
+
run: getFailingScenarios
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "get_feature_summary",
|
|
114
|
+
title: "Get feature summary",
|
|
115
|
+
description: "Summarize features and scenario status counts from StoryReport JSON.",
|
|
116
|
+
route: "/features",
|
|
117
|
+
run: getFeatureSummary
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "get_scenario_index",
|
|
121
|
+
title: "Get scenario index",
|
|
122
|
+
description: "Return the Storybook-like scenario index artifact (schema v1) derived from StoryReport JSON.",
|
|
123
|
+
route: "/scenarios-index",
|
|
124
|
+
run: getScenarioIndex
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "get_behavior_manifest",
|
|
128
|
+
title: "Get behavior manifest",
|
|
129
|
+
description: "Return agent-oriented manifest metadata: source files, tags, doc coverage, debugger warnings.",
|
|
130
|
+
route: "/manifest",
|
|
131
|
+
run: getBehaviorManifest
|
|
132
|
+
}
|
|
133
|
+
];
|
|
134
|
+
var RUNNERS = {
|
|
135
|
+
vitest: {
|
|
136
|
+
framework: "vitest",
|
|
137
|
+
buildCommand: ({ sourceFile, scenarioTitle }) => ({
|
|
138
|
+
command: "pnpm",
|
|
139
|
+
args: ["exec", "vitest", "run", sourceFile, ...scenarioTitle ? ["-t", scenarioTitle] : []]
|
|
140
|
+
})
|
|
141
|
+
},
|
|
142
|
+
jest: {
|
|
143
|
+
framework: "jest",
|
|
144
|
+
buildCommand: ({ sourceFile, scenarioTitle }) => ({
|
|
145
|
+
command: "pnpm",
|
|
146
|
+
args: ["exec", "jest", sourceFile, ...scenarioTitle ? ["-t", scenarioTitle] : [], "--runInBand"]
|
|
147
|
+
})
|
|
148
|
+
},
|
|
149
|
+
playwright: {
|
|
150
|
+
framework: "playwright",
|
|
151
|
+
detect: (sourceFile) => sourceFile.includes(".story.spec."),
|
|
152
|
+
buildCommand: ({ sourceFile, scenarioTitle }) => ({
|
|
153
|
+
command: "pnpm",
|
|
154
|
+
args: ["exec", "playwright", "test", sourceFile, ...scenarioTitle ? ["-g", scenarioTitle] : []]
|
|
155
|
+
})
|
|
156
|
+
},
|
|
157
|
+
cypress: {
|
|
158
|
+
framework: "cypress",
|
|
159
|
+
detect: (sourceFile) => sourceFile.includes(".story.cy."),
|
|
160
|
+
buildCommand: ({ sourceFile }) => ({
|
|
161
|
+
command: "pnpm",
|
|
162
|
+
args: ["exec", "cypress", "run", "--spec", sourceFile]
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
function inferFrameworkFromSourceFile(sourceFile) {
|
|
167
|
+
for (const runner of Object.values(RUNNERS)) {
|
|
168
|
+
if (runner.detect?.(sourceFile)) return runner.framework;
|
|
169
|
+
}
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
function resolveFocusedRunFramework(args) {
|
|
173
|
+
if (args.framework) return args.framework;
|
|
174
|
+
const inferred = inferFrameworkFromSourceFile(args.sourceFile);
|
|
175
|
+
if (inferred) return inferred;
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Could not infer test framework from ${args.sourceFile}. Pass framework: vitest | jest | playwright | cypress.`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
function buildFocusedRunCommand(args) {
|
|
181
|
+
return RUNNERS[args.framework].buildCommand(args);
|
|
182
|
+
}
|
|
183
|
+
async function runFocusedScenario(args) {
|
|
184
|
+
const command = buildFocusedRunCommand(args);
|
|
185
|
+
const spawnFn = args.spawnFn ?? import_node_child_process.spawn;
|
|
186
|
+
return new Promise((resolve2) => {
|
|
187
|
+
const child = spawnFn(command.command, command.args, {
|
|
188
|
+
cwd: args.cwd,
|
|
189
|
+
env: process.env
|
|
190
|
+
});
|
|
191
|
+
let stdout = "";
|
|
192
|
+
let stderr = "";
|
|
193
|
+
child.stdout.on("data", (chunk) => {
|
|
194
|
+
stdout += String(chunk);
|
|
195
|
+
});
|
|
196
|
+
child.stderr.on("data", (chunk) => {
|
|
197
|
+
stderr += String(chunk);
|
|
198
|
+
});
|
|
199
|
+
child.on("error", (error) => {
|
|
200
|
+
resolve2({
|
|
201
|
+
ok: false,
|
|
202
|
+
exitCode: null,
|
|
203
|
+
command: command.command,
|
|
204
|
+
args: command.args,
|
|
205
|
+
stdout,
|
|
206
|
+
stderr: stderr + error.message
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
child.on("close", (exitCode) => {
|
|
210
|
+
resolve2({
|
|
211
|
+
ok: exitCode === 0,
|
|
212
|
+
exitCode,
|
|
213
|
+
command: command.command,
|
|
214
|
+
args: command.args,
|
|
215
|
+
stdout,
|
|
216
|
+
stderr
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function assertStoryReport(value, reportPath) {
|
|
222
|
+
if (!value || typeof value !== "object") {
|
|
223
|
+
throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected object`);
|
|
224
|
+
}
|
|
225
|
+
const report = value;
|
|
226
|
+
if (typeof report.schemaVersion !== "string" || !report.schemaVersion.startsWith("1.")) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Invalid StoryReport JSON in ${reportPath}: expected schemaVersion 1.x`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
if (!Array.isArray(report.features)) {
|
|
232
|
+
throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected features array`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
236
|
+
0 && (module.exports = {
|
|
237
|
+
RUNNERS,
|
|
238
|
+
buildFocusedRunCommand,
|
|
239
|
+
getBehaviorDiff,
|
|
240
|
+
getBehaviorManifest,
|
|
241
|
+
getFailingScenarios,
|
|
242
|
+
getFeatureSummary,
|
|
243
|
+
getScenario,
|
|
244
|
+
getScenarioIndex,
|
|
245
|
+
getScenariosForPaths,
|
|
246
|
+
inferFrameworkFromSourceFile,
|
|
247
|
+
listScenarios,
|
|
248
|
+
loadStoryReport,
|
|
249
|
+
readOnlyTools,
|
|
250
|
+
resolveFocusedRunFramework,
|
|
251
|
+
resolveReportPath,
|
|
252
|
+
runFocusedScenario
|
|
253
|
+
});
|
|
254
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\n\nimport type {\n ReportFeature,\n ReportScenario,\n StoryReport,\n BehaviorManifest,\n BehaviorDiff,\n ScenarioIndex,\n ScenarioIndexFilters,\n ScenarioIndexItem,\n} from \"executable-stories-formatters\";\nimport {\n diffStoryReports,\n scenariosCoveringPaths,\n toBehaviorManifest,\n toScenarioIndex,\n} from \"executable-stories-formatters\";\n\n// Scenario serialization is owned by the formatters package; re-export so MCP\n// consumers get the same shape without a parallel definition to maintain.\nexport type { ScenarioIndexItem, ScenarioIndexFilters };\n\nexport interface FeatureSummaryItem {\n id: string;\n title: string;\n sourceFile: string;\n total: number;\n passed: number;\n failed: number;\n skipped: number;\n pending: number;\n durationMs: number;\n}\n\nexport interface ScenarioLookup {\n feature: ReportFeature;\n scenario: ReportScenario;\n}\n\nexport function loadStoryReport(reportPath: string): StoryReport {\n const absolutePath = path.resolve(reportPath);\n const parsed: unknown = JSON.parse(fs.readFileSync(absolutePath, \"utf8\"));\n assertStoryReport(parsed, absolutePath);\n return parsed;\n}\n\nexport function listScenarios(\n report: StoryReport,\n filters?: ScenarioIndexFilters,\n): ScenarioIndexItem[] {\n return toScenarioIndex(report, filters).scenarios;\n}\n\nexport function getFailingScenarios(report: StoryReport): ScenarioIndexItem[] {\n return listScenarios(report, { statuses: [\"failed\"] });\n}\n\nexport function getScenariosForPaths(report: StoryReport, paths: string[]): ScenarioIndexItem[] {\n return scenariosCoveringPaths(toScenarioIndex(report), paths);\n}\n\nexport function getBehaviorDiff(baseline: StoryReport, current: StoryReport): BehaviorDiff {\n return diffStoryReports(baseline, current);\n}\n\nexport function getScenario(report: StoryReport, idOrTitle: string): ScenarioLookup | undefined {\n for (const feature of report.features) {\n const scenario = feature.scenarios.find(\n (candidate) => candidate.id === idOrTitle || candidate.title === idOrTitle,\n );\n if (scenario) return { feature, scenario };\n }\n return undefined;\n}\n\nexport function getFeatureSummary(report: StoryReport): FeatureSummaryItem[] {\n return report.features.map((feature) => ({\n id: feature.id,\n title: feature.title,\n sourceFile: feature.sourceFile,\n total: feature.summary.total,\n passed: feature.summary.passed,\n failed: feature.summary.failed,\n skipped: feature.summary.skipped,\n pending: feature.summary.pending,\n durationMs: feature.summary.durationMs,\n }));\n}\n\nexport function resolveReportPath(reportPath?: string): string {\n return path.resolve(reportPath ?? \"reports/index.story-report.json\");\n}\n\nexport function getScenarioIndex(report: StoryReport): ScenarioIndex {\n return toScenarioIndex(report);\n}\n\nexport function getBehaviorManifest(report: StoryReport): BehaviorManifest {\n return toBehaviorManifest(report);\n}\n\n/**\n * Single source of truth for the read-only tools, consumed by both the stdio\n * MCP server and the HTTP server so the two transports cannot drift apart.\n * Tools needing extra arguments (get_scenario, run_scenario) are wired up\n * directly in each transport.\n */\nexport interface ReadOnlyTool {\n /** MCP tool name. */\n name: string;\n /** Human-readable MCP tool title. */\n title: string;\n /** Shared description used by both transports. */\n description: string;\n /** HTTP route that exposes the same data. */\n route: string;\n /** Pure projection from a loaded report to its JSON payload. */\n run: (report: StoryReport) => unknown;\n}\n\nexport const readOnlyTools: ReadOnlyTool[] = [\n {\n name: \"get_failing_scenarios\",\n title: \"Get failing scenarios\",\n description: \"List failing executable story scenarios from StoryReport JSON.\",\n route: \"/scenarios/failing\",\n run: getFailingScenarios,\n },\n {\n name: \"get_feature_summary\",\n title: \"Get feature summary\",\n description: \"Summarize features and scenario status counts from StoryReport JSON.\",\n route: \"/features\",\n run: getFeatureSummary,\n },\n {\n name: \"get_scenario_index\",\n title: \"Get scenario index\",\n description:\n \"Return the Storybook-like scenario index artifact (schema v1) derived from StoryReport JSON.\",\n route: \"/scenarios-index\",\n run: getScenarioIndex,\n },\n {\n name: \"get_behavior_manifest\",\n title: \"Get behavior manifest\",\n description:\n \"Return agent-oriented manifest metadata: source files, tags, doc coverage, debugger warnings.\",\n route: \"/manifest\",\n run: getBehaviorManifest,\n },\n];\n\nexport type FocusedRunFramework = \"vitest\" | \"jest\" | \"playwright\" | \"cypress\";\n\nexport interface FocusedRunCommandArgs {\n framework: FocusedRunFramework;\n sourceFile: string;\n scenarioTitle?: string;\n}\n\nexport interface FocusedRunCommand {\n command: string;\n args: string[];\n}\n\nexport interface FocusedRunResult {\n ok: boolean;\n exitCode: number | null;\n command: string;\n args: string[];\n stdout: string;\n stderr: string;\n}\n\n/**\n * One runner per host framework. The seam that keeps `run_scenario` extensible:\n * adding a non-JS framework (go, cargo, pytest, dotnet) later is a single new\n * entry here — no changes to inference, command building, or the transports.\n */\nexport interface RunnerDefinition {\n framework: FocusedRunFramework;\n /** Infer this framework from a source-file path, when unambiguous. */\n detect?: (sourceFile: string) => boolean;\n /** Build the focused-run command for this framework. */\n buildCommand: (args: { sourceFile: string; scenarioTitle?: string }) => FocusedRunCommand;\n}\n\nexport const RUNNERS: Record<FocusedRunFramework, RunnerDefinition> = {\n vitest: {\n framework: \"vitest\",\n buildCommand: ({ sourceFile, scenarioTitle }) => ({\n command: \"pnpm\",\n args: [\"exec\", \"vitest\", \"run\", sourceFile, ...(scenarioTitle ? [\"-t\", scenarioTitle] : [])],\n }),\n },\n jest: {\n framework: \"jest\",\n buildCommand: ({ sourceFile, scenarioTitle }) => ({\n command: \"pnpm\",\n args: [\"exec\", \"jest\", sourceFile, ...(scenarioTitle ? [\"-t\", scenarioTitle] : []), \"--runInBand\"],\n }),\n },\n playwright: {\n framework: \"playwright\",\n detect: (sourceFile) => sourceFile.includes(\".story.spec.\"),\n buildCommand: ({ sourceFile, scenarioTitle }) => ({\n command: \"pnpm\",\n args: [\"exec\", \"playwright\", \"test\", sourceFile, ...(scenarioTitle ? [\"-g\", scenarioTitle] : [])],\n }),\n },\n cypress: {\n framework: \"cypress\",\n detect: (sourceFile) => sourceFile.includes(\".story.cy.\"),\n buildCommand: ({ sourceFile }) => ({\n command: \"pnpm\",\n args: [\"exec\", \"cypress\", \"run\", \"--spec\", sourceFile],\n }),\n },\n};\n\nexport function inferFrameworkFromSourceFile(\n sourceFile: string,\n): FocusedRunFramework | undefined {\n for (const runner of Object.values(RUNNERS)) {\n if (runner.detect?.(sourceFile)) return runner.framework;\n }\n return undefined;\n}\n\nexport function resolveFocusedRunFramework(args: {\n sourceFile: string;\n framework?: FocusedRunFramework;\n}): FocusedRunFramework {\n if (args.framework) return args.framework;\n const inferred = inferFrameworkFromSourceFile(args.sourceFile);\n if (inferred) return inferred;\n throw new Error(\n `Could not infer test framework from ${args.sourceFile}. Pass framework: vitest | jest | playwright | cypress.`,\n );\n}\n\nexport function buildFocusedRunCommand(args: FocusedRunCommandArgs): FocusedRunCommand {\n return RUNNERS[args.framework].buildCommand(args);\n}\n\nexport async function runFocusedScenario(args: FocusedRunCommandArgs & {\n cwd?: string;\n spawnFn?: typeof spawn;\n}): Promise<FocusedRunResult> {\n const command = buildFocusedRunCommand(args);\n const spawnFn = args.spawnFn ?? spawn;\n\n return new Promise((resolve) => {\n const child = spawnFn(command.command, command.args, {\n cwd: args.cwd,\n env: process.env,\n }) as ChildProcessWithoutNullStreams;\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout.on(\"data\", (chunk) => {\n stdout += String(chunk);\n });\n child.stderr.on(\"data\", (chunk) => {\n stderr += String(chunk);\n });\n child.on(\"error\", (error) => {\n resolve({\n ok: false,\n exitCode: null,\n command: command.command,\n args: command.args,\n stdout,\n stderr: stderr + error.message,\n });\n });\n child.on(\"close\", (exitCode) => {\n resolve({\n ok: exitCode === 0,\n exitCode,\n command: command.command,\n args: command.args,\n stdout,\n stderr,\n });\n });\n });\n}\n\nfunction assertStoryReport(\n value: unknown,\n reportPath: string,\n): asserts value is StoryReport {\n if (!value || typeof value !== \"object\") {\n throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected object`);\n }\n const report = value as Partial<StoryReport>;\n if (typeof report.schemaVersion !== \"string\" || !report.schemaVersion.startsWith(\"1.\")) {\n throw new Error(\n `Invalid StoryReport JSON in ${reportPath}: expected schemaVersion 1.x`,\n );\n }\n if (!Array.isArray(report.features)) {\n throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected features array`);\n }\n}\n"],"mappings":"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","names":["resolve"]}
|