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.
@@ -0,0 +1,94 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { StoryReport, ReportFeature, ReportScenario, BehaviorDiff, BehaviorManifest, ScenarioIndexItem, ScenarioIndex, ScenarioIndexFilters } from 'executable-stories-formatters';
3
+ export { ScenarioIndexFilters, ScenarioIndexItem } from 'executable-stories-formatters';
4
+
5
+ interface FeatureSummaryItem {
6
+ id: string;
7
+ title: string;
8
+ sourceFile: string;
9
+ total: number;
10
+ passed: number;
11
+ failed: number;
12
+ skipped: number;
13
+ pending: number;
14
+ durationMs: number;
15
+ }
16
+ interface ScenarioLookup {
17
+ feature: ReportFeature;
18
+ scenario: ReportScenario;
19
+ }
20
+ declare function loadStoryReport(reportPath: string): StoryReport;
21
+ declare function listScenarios(report: StoryReport, filters?: ScenarioIndexFilters): ScenarioIndexItem[];
22
+ declare function getFailingScenarios(report: StoryReport): ScenarioIndexItem[];
23
+ declare function getScenariosForPaths(report: StoryReport, paths: string[]): ScenarioIndexItem[];
24
+ declare function getBehaviorDiff(baseline: StoryReport, current: StoryReport): BehaviorDiff;
25
+ declare function getScenario(report: StoryReport, idOrTitle: string): ScenarioLookup | undefined;
26
+ declare function getFeatureSummary(report: StoryReport): FeatureSummaryItem[];
27
+ declare function resolveReportPath(reportPath?: string): string;
28
+ declare function getScenarioIndex(report: StoryReport): ScenarioIndex;
29
+ declare function getBehaviorManifest(report: StoryReport): BehaviorManifest;
30
+ /**
31
+ * Single source of truth for the read-only tools, consumed by both the stdio
32
+ * MCP server and the HTTP server so the two transports cannot drift apart.
33
+ * Tools needing extra arguments (get_scenario, run_scenario) are wired up
34
+ * directly in each transport.
35
+ */
36
+ interface ReadOnlyTool {
37
+ /** MCP tool name. */
38
+ name: string;
39
+ /** Human-readable MCP tool title. */
40
+ title: string;
41
+ /** Shared description used by both transports. */
42
+ description: string;
43
+ /** HTTP route that exposes the same data. */
44
+ route: string;
45
+ /** Pure projection from a loaded report to its JSON payload. */
46
+ run: (report: StoryReport) => unknown;
47
+ }
48
+ declare const readOnlyTools: ReadOnlyTool[];
49
+ type FocusedRunFramework = "vitest" | "jest" | "playwright" | "cypress";
50
+ interface FocusedRunCommandArgs {
51
+ framework: FocusedRunFramework;
52
+ sourceFile: string;
53
+ scenarioTitle?: string;
54
+ }
55
+ interface FocusedRunCommand {
56
+ command: string;
57
+ args: string[];
58
+ }
59
+ interface FocusedRunResult {
60
+ ok: boolean;
61
+ exitCode: number | null;
62
+ command: string;
63
+ args: string[];
64
+ stdout: string;
65
+ stderr: string;
66
+ }
67
+ /**
68
+ * One runner per host framework. The seam that keeps `run_scenario` extensible:
69
+ * adding a non-JS framework (go, cargo, pytest, dotnet) later is a single new
70
+ * entry here — no changes to inference, command building, or the transports.
71
+ */
72
+ interface RunnerDefinition {
73
+ framework: FocusedRunFramework;
74
+ /** Infer this framework from a source-file path, when unambiguous. */
75
+ detect?: (sourceFile: string) => boolean;
76
+ /** Build the focused-run command for this framework. */
77
+ buildCommand: (args: {
78
+ sourceFile: string;
79
+ scenarioTitle?: string;
80
+ }) => FocusedRunCommand;
81
+ }
82
+ declare const RUNNERS: Record<FocusedRunFramework, RunnerDefinition>;
83
+ declare function inferFrameworkFromSourceFile(sourceFile: string): FocusedRunFramework | undefined;
84
+ declare function resolveFocusedRunFramework(args: {
85
+ sourceFile: string;
86
+ framework?: FocusedRunFramework;
87
+ }): FocusedRunFramework;
88
+ declare function buildFocusedRunCommand(args: FocusedRunCommandArgs): FocusedRunCommand;
89
+ declare function runFocusedScenario(args: FocusedRunCommandArgs & {
90
+ cwd?: string;
91
+ spawnFn?: typeof spawn;
92
+ }): Promise<FocusedRunResult>;
93
+
94
+ export { type FeatureSummaryItem, type FocusedRunCommand, type FocusedRunCommandArgs, type FocusedRunFramework, type FocusedRunResult, RUNNERS, type ReadOnlyTool, type RunnerDefinition, type ScenarioLookup, buildFocusedRunCommand, getBehaviorDiff, getBehaviorManifest, getFailingScenarios, getFeatureSummary, getScenario, getScenarioIndex, getScenariosForPaths, inferFrameworkFromSourceFile, listScenarios, loadStoryReport, readOnlyTools, resolveFocusedRunFramework, resolveReportPath, runFocusedScenario };
@@ -0,0 +1,94 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { StoryReport, ReportFeature, ReportScenario, BehaviorDiff, BehaviorManifest, ScenarioIndexItem, ScenarioIndex, ScenarioIndexFilters } from 'executable-stories-formatters';
3
+ export { ScenarioIndexFilters, ScenarioIndexItem } from 'executable-stories-formatters';
4
+
5
+ interface FeatureSummaryItem {
6
+ id: string;
7
+ title: string;
8
+ sourceFile: string;
9
+ total: number;
10
+ passed: number;
11
+ failed: number;
12
+ skipped: number;
13
+ pending: number;
14
+ durationMs: number;
15
+ }
16
+ interface ScenarioLookup {
17
+ feature: ReportFeature;
18
+ scenario: ReportScenario;
19
+ }
20
+ declare function loadStoryReport(reportPath: string): StoryReport;
21
+ declare function listScenarios(report: StoryReport, filters?: ScenarioIndexFilters): ScenarioIndexItem[];
22
+ declare function getFailingScenarios(report: StoryReport): ScenarioIndexItem[];
23
+ declare function getScenariosForPaths(report: StoryReport, paths: string[]): ScenarioIndexItem[];
24
+ declare function getBehaviorDiff(baseline: StoryReport, current: StoryReport): BehaviorDiff;
25
+ declare function getScenario(report: StoryReport, idOrTitle: string): ScenarioLookup | undefined;
26
+ declare function getFeatureSummary(report: StoryReport): FeatureSummaryItem[];
27
+ declare function resolveReportPath(reportPath?: string): string;
28
+ declare function getScenarioIndex(report: StoryReport): ScenarioIndex;
29
+ declare function getBehaviorManifest(report: StoryReport): BehaviorManifest;
30
+ /**
31
+ * Single source of truth for the read-only tools, consumed by both the stdio
32
+ * MCP server and the HTTP server so the two transports cannot drift apart.
33
+ * Tools needing extra arguments (get_scenario, run_scenario) are wired up
34
+ * directly in each transport.
35
+ */
36
+ interface ReadOnlyTool {
37
+ /** MCP tool name. */
38
+ name: string;
39
+ /** Human-readable MCP tool title. */
40
+ title: string;
41
+ /** Shared description used by both transports. */
42
+ description: string;
43
+ /** HTTP route that exposes the same data. */
44
+ route: string;
45
+ /** Pure projection from a loaded report to its JSON payload. */
46
+ run: (report: StoryReport) => unknown;
47
+ }
48
+ declare const readOnlyTools: ReadOnlyTool[];
49
+ type FocusedRunFramework = "vitest" | "jest" | "playwright" | "cypress";
50
+ interface FocusedRunCommandArgs {
51
+ framework: FocusedRunFramework;
52
+ sourceFile: string;
53
+ scenarioTitle?: string;
54
+ }
55
+ interface FocusedRunCommand {
56
+ command: string;
57
+ args: string[];
58
+ }
59
+ interface FocusedRunResult {
60
+ ok: boolean;
61
+ exitCode: number | null;
62
+ command: string;
63
+ args: string[];
64
+ stdout: string;
65
+ stderr: string;
66
+ }
67
+ /**
68
+ * One runner per host framework. The seam that keeps `run_scenario` extensible:
69
+ * adding a non-JS framework (go, cargo, pytest, dotnet) later is a single new
70
+ * entry here — no changes to inference, command building, or the transports.
71
+ */
72
+ interface RunnerDefinition {
73
+ framework: FocusedRunFramework;
74
+ /** Infer this framework from a source-file path, when unambiguous. */
75
+ detect?: (sourceFile: string) => boolean;
76
+ /** Build the focused-run command for this framework. */
77
+ buildCommand: (args: {
78
+ sourceFile: string;
79
+ scenarioTitle?: string;
80
+ }) => FocusedRunCommand;
81
+ }
82
+ declare const RUNNERS: Record<FocusedRunFramework, RunnerDefinition>;
83
+ declare function inferFrameworkFromSourceFile(sourceFile: string): FocusedRunFramework | undefined;
84
+ declare function resolveFocusedRunFramework(args: {
85
+ sourceFile: string;
86
+ framework?: FocusedRunFramework;
87
+ }): FocusedRunFramework;
88
+ declare function buildFocusedRunCommand(args: FocusedRunCommandArgs): FocusedRunCommand;
89
+ declare function runFocusedScenario(args: FocusedRunCommandArgs & {
90
+ cwd?: string;
91
+ spawnFn?: typeof spawn;
92
+ }): Promise<FocusedRunResult>;
93
+
94
+ export { type FeatureSummaryItem, type FocusedRunCommand, type FocusedRunCommandArgs, type FocusedRunFramework, type FocusedRunResult, RUNNERS, type ReadOnlyTool, type RunnerDefinition, type ScenarioLookup, buildFocusedRunCommand, getBehaviorDiff, getBehaviorManifest, getFailingScenarios, getFeatureSummary, getScenario, getScenarioIndex, getScenariosForPaths, inferFrameworkFromSourceFile, listScenarios, loadStoryReport, readOnlyTools, resolveFocusedRunFramework, resolveReportPath, runFocusedScenario };
package/dist/index.js ADDED
@@ -0,0 +1,209 @@
1
+ // src/index.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { spawn } from "child_process";
5
+ import {
6
+ diffStoryReports,
7
+ scenariosCoveringPaths,
8
+ toBehaviorManifest,
9
+ toScenarioIndex
10
+ } from "executable-stories-formatters";
11
+ function loadStoryReport(reportPath) {
12
+ const absolutePath = path.resolve(reportPath);
13
+ const parsed = JSON.parse(fs.readFileSync(absolutePath, "utf8"));
14
+ assertStoryReport(parsed, absolutePath);
15
+ return parsed;
16
+ }
17
+ function listScenarios(report, filters) {
18
+ return toScenarioIndex(report, filters).scenarios;
19
+ }
20
+ function getFailingScenarios(report) {
21
+ return listScenarios(report, { statuses: ["failed"] });
22
+ }
23
+ function getScenariosForPaths(report, paths) {
24
+ return scenariosCoveringPaths(toScenarioIndex(report), paths);
25
+ }
26
+ function getBehaviorDiff(baseline, current) {
27
+ return diffStoryReports(baseline, current);
28
+ }
29
+ function getScenario(report, idOrTitle) {
30
+ for (const feature of report.features) {
31
+ const scenario = feature.scenarios.find(
32
+ (candidate) => candidate.id === idOrTitle || candidate.title === idOrTitle
33
+ );
34
+ if (scenario) return { feature, scenario };
35
+ }
36
+ return void 0;
37
+ }
38
+ function getFeatureSummary(report) {
39
+ return report.features.map((feature) => ({
40
+ id: feature.id,
41
+ title: feature.title,
42
+ sourceFile: feature.sourceFile,
43
+ total: feature.summary.total,
44
+ passed: feature.summary.passed,
45
+ failed: feature.summary.failed,
46
+ skipped: feature.summary.skipped,
47
+ pending: feature.summary.pending,
48
+ durationMs: feature.summary.durationMs
49
+ }));
50
+ }
51
+ function resolveReportPath(reportPath) {
52
+ return path.resolve(reportPath ?? "reports/index.story-report.json");
53
+ }
54
+ function getScenarioIndex(report) {
55
+ return toScenarioIndex(report);
56
+ }
57
+ function getBehaviorManifest(report) {
58
+ return toBehaviorManifest(report);
59
+ }
60
+ var readOnlyTools = [
61
+ {
62
+ name: "get_failing_scenarios",
63
+ title: "Get failing scenarios",
64
+ description: "List failing executable story scenarios from StoryReport JSON.",
65
+ route: "/scenarios/failing",
66
+ run: getFailingScenarios
67
+ },
68
+ {
69
+ name: "get_feature_summary",
70
+ title: "Get feature summary",
71
+ description: "Summarize features and scenario status counts from StoryReport JSON.",
72
+ route: "/features",
73
+ run: getFeatureSummary
74
+ },
75
+ {
76
+ name: "get_scenario_index",
77
+ title: "Get scenario index",
78
+ description: "Return the Storybook-like scenario index artifact (schema v1) derived from StoryReport JSON.",
79
+ route: "/scenarios-index",
80
+ run: getScenarioIndex
81
+ },
82
+ {
83
+ name: "get_behavior_manifest",
84
+ title: "Get behavior manifest",
85
+ description: "Return agent-oriented manifest metadata: source files, tags, doc coverage, debugger warnings.",
86
+ route: "/manifest",
87
+ run: getBehaviorManifest
88
+ }
89
+ ];
90
+ var RUNNERS = {
91
+ vitest: {
92
+ framework: "vitest",
93
+ buildCommand: ({ sourceFile, scenarioTitle }) => ({
94
+ command: "pnpm",
95
+ args: ["exec", "vitest", "run", sourceFile, ...scenarioTitle ? ["-t", scenarioTitle] : []]
96
+ })
97
+ },
98
+ jest: {
99
+ framework: "jest",
100
+ buildCommand: ({ sourceFile, scenarioTitle }) => ({
101
+ command: "pnpm",
102
+ args: ["exec", "jest", sourceFile, ...scenarioTitle ? ["-t", scenarioTitle] : [], "--runInBand"]
103
+ })
104
+ },
105
+ playwright: {
106
+ framework: "playwright",
107
+ detect: (sourceFile) => sourceFile.includes(".story.spec."),
108
+ buildCommand: ({ sourceFile, scenarioTitle }) => ({
109
+ command: "pnpm",
110
+ args: ["exec", "playwright", "test", sourceFile, ...scenarioTitle ? ["-g", scenarioTitle] : []]
111
+ })
112
+ },
113
+ cypress: {
114
+ framework: "cypress",
115
+ detect: (sourceFile) => sourceFile.includes(".story.cy."),
116
+ buildCommand: ({ sourceFile }) => ({
117
+ command: "pnpm",
118
+ args: ["exec", "cypress", "run", "--spec", sourceFile]
119
+ })
120
+ }
121
+ };
122
+ function inferFrameworkFromSourceFile(sourceFile) {
123
+ for (const runner of Object.values(RUNNERS)) {
124
+ if (runner.detect?.(sourceFile)) return runner.framework;
125
+ }
126
+ return void 0;
127
+ }
128
+ function resolveFocusedRunFramework(args) {
129
+ if (args.framework) return args.framework;
130
+ const inferred = inferFrameworkFromSourceFile(args.sourceFile);
131
+ if (inferred) return inferred;
132
+ throw new Error(
133
+ `Could not infer test framework from ${args.sourceFile}. Pass framework: vitest | jest | playwright | cypress.`
134
+ );
135
+ }
136
+ function buildFocusedRunCommand(args) {
137
+ return RUNNERS[args.framework].buildCommand(args);
138
+ }
139
+ async function runFocusedScenario(args) {
140
+ const command = buildFocusedRunCommand(args);
141
+ const spawnFn = args.spawnFn ?? spawn;
142
+ return new Promise((resolve2) => {
143
+ const child = spawnFn(command.command, command.args, {
144
+ cwd: args.cwd,
145
+ env: process.env
146
+ });
147
+ let stdout = "";
148
+ let stderr = "";
149
+ child.stdout.on("data", (chunk) => {
150
+ stdout += String(chunk);
151
+ });
152
+ child.stderr.on("data", (chunk) => {
153
+ stderr += String(chunk);
154
+ });
155
+ child.on("error", (error) => {
156
+ resolve2({
157
+ ok: false,
158
+ exitCode: null,
159
+ command: command.command,
160
+ args: command.args,
161
+ stdout,
162
+ stderr: stderr + error.message
163
+ });
164
+ });
165
+ child.on("close", (exitCode) => {
166
+ resolve2({
167
+ ok: exitCode === 0,
168
+ exitCode,
169
+ command: command.command,
170
+ args: command.args,
171
+ stdout,
172
+ stderr
173
+ });
174
+ });
175
+ });
176
+ }
177
+ function assertStoryReport(value, reportPath) {
178
+ if (!value || typeof value !== "object") {
179
+ throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected object`);
180
+ }
181
+ const report = value;
182
+ if (typeof report.schemaVersion !== "string" || !report.schemaVersion.startsWith("1.")) {
183
+ throw new Error(
184
+ `Invalid StoryReport JSON in ${reportPath}: expected schemaVersion 1.x`
185
+ );
186
+ }
187
+ if (!Array.isArray(report.features)) {
188
+ throw new Error(`Invalid StoryReport JSON in ${reportPath}: expected features array`);
189
+ }
190
+ }
191
+ export {
192
+ RUNNERS,
193
+ buildFocusedRunCommand,
194
+ getBehaviorDiff,
195
+ getBehaviorManifest,
196
+ getFailingScenarios,
197
+ getFeatureSummary,
198
+ getScenario,
199
+ getScenarioIndex,
200
+ getScenariosForPaths,
201
+ inferFrameworkFromSourceFile,
202
+ listScenarios,
203
+ loadStoryReport,
204
+ readOnlyTools,
205
+ resolveFocusedRunFramework,
206
+ resolveReportPath,
207
+ runFocusedScenario
208
+ };
209
+ //# sourceMappingURL=index.js.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":";AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,SAAS,aAAkD;AAY3D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAuBA,SAAS,gBAAgB,YAAiC;AAC/D,QAAM,eAAoB,aAAQ,UAAU;AAC5C,QAAM,SAAkB,KAAK,MAAS,gBAAa,cAAc,MAAM,CAAC;AACxE,oBAAkB,QAAQ,YAAY;AACtC,SAAO;AACT;AAEO,SAAS,cACd,QACA,SACqB;AACrB,SAAO,gBAAgB,QAAQ,OAAO,EAAE;AAC1C;AAEO,SAAS,oBAAoB,QAA0C;AAC5E,SAAO,cAAc,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,CAAC;AACvD;AAEO,SAAS,qBAAqB,QAAqB,OAAsC;AAC9F,SAAO,uBAAuB,gBAAgB,MAAM,GAAG,KAAK;AAC9D;AAEO,SAAS,gBAAgB,UAAuB,SAAoC;AACzF,SAAO,iBAAiB,UAAU,OAAO;AAC3C;AAEO,SAAS,YAAY,QAAqB,WAA+C;AAC9F,aAAW,WAAW,OAAO,UAAU;AACrC,UAAM,WAAW,QAAQ,UAAU;AAAA,MACjC,CAAC,cAAc,UAAU,OAAO,aAAa,UAAU,UAAU;AAAA,IACnE;AACA,QAAI,SAAU,QAAO,EAAE,SAAS,SAAS;AAAA,EAC3C;AACA,SAAO;AACT;AAEO,SAAS,kBAAkB,QAA2C;AAC3E,SAAO,OAAO,SAAS,IAAI,CAAC,aAAa;AAAA,IACvC,IAAI,QAAQ;AAAA,IACZ,OAAO,QAAQ;AAAA,IACf,YAAY,QAAQ;AAAA,IACpB,OAAO,QAAQ,QAAQ;AAAA,IACvB,QAAQ,QAAQ,QAAQ;AAAA,IACxB,QAAQ,QAAQ,QAAQ;AAAA,IACxB,SAAS,QAAQ,QAAQ;AAAA,IACzB,SAAS,QAAQ,QAAQ;AAAA,IACzB,YAAY,QAAQ,QAAQ;AAAA,EAC9B,EAAE;AACJ;AAEO,SAAS,kBAAkB,YAA6B;AAC7D,SAAY,aAAQ,cAAc,iCAAiC;AACrE;AAEO,SAAS,iBAAiB,QAAoC;AACnE,SAAO,gBAAgB,MAAM;AAC/B;AAEO,SAAS,oBAAoB,QAAuC;AACzE,SAAO,mBAAmB,MAAM;AAClC;AAqBO,IAAM,gBAAgC;AAAA,EAC3C;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE;AAAA,IACF,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE;AAAA,IACF,OAAO;AAAA,IACP,KAAK;AAAA,EACP;AACF;AAqCO,IAAM,UAAyD;AAAA,EACpE,QAAQ;AAAA,IACN,WAAW;AAAA,IACX,cAAc,CAAC,EAAE,YAAY,cAAc,OAAO;AAAA,MAChD,SAAS;AAAA,MACT,MAAM,CAAC,QAAQ,UAAU,OAAO,YAAY,GAAI,gBAAgB,CAAC,MAAM,aAAa,IAAI,CAAC,CAAE;AAAA,IAC7F;AAAA,EACF;AAAA,EACA,MAAM;AAAA,IACJ,WAAW;AAAA,IACX,cAAc,CAAC,EAAE,YAAY,cAAc,OAAO;AAAA,MAChD,SAAS;AAAA,MACT,MAAM,CAAC,QAAQ,QAAQ,YAAY,GAAI,gBAAgB,CAAC,MAAM,aAAa,IAAI,CAAC,GAAI,aAAa;AAAA,IACnG;AAAA,EACF;AAAA,EACA,YAAY;AAAA,IACV,WAAW;AAAA,IACX,QAAQ,CAAC,eAAe,WAAW,SAAS,cAAc;AAAA,IAC1D,cAAc,CAAC,EAAE,YAAY,cAAc,OAAO;AAAA,MAChD,SAAS;AAAA,MACT,MAAM,CAAC,QAAQ,cAAc,QAAQ,YAAY,GAAI,gBAAgB,CAAC,MAAM,aAAa,IAAI,CAAC,CAAE;AAAA,IAClG;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,WAAW;AAAA,IACX,QAAQ,CAAC,eAAe,WAAW,SAAS,YAAY;AAAA,IACxD,cAAc,CAAC,EAAE,WAAW,OAAO;AAAA,MACjC,SAAS;AAAA,MACT,MAAM,CAAC,QAAQ,WAAW,OAAO,UAAU,UAAU;AAAA,IACvD;AAAA,EACF;AACF;AAEO,SAAS,6BACd,YACiC;AACjC,aAAW,UAAU,OAAO,OAAO,OAAO,GAAG;AAC3C,QAAI,OAAO,SAAS,UAAU,EAAG,QAAO,OAAO;AAAA,EACjD;AACA,SAAO;AACT;AAEO,SAAS,2BAA2B,MAGnB;AACtB,MAAI,KAAK,UAAW,QAAO,KAAK;AAChC,QAAM,WAAW,6BAA6B,KAAK,UAAU;AAC7D,MAAI,SAAU,QAAO;AACrB,QAAM,IAAI;AAAA,IACR,uCAAuC,KAAK,UAAU;AAAA,EACxD;AACF;AAEO,SAAS,uBAAuB,MAAgD;AACrF,SAAO,QAAQ,KAAK,SAAS,EAAE,aAAa,IAAI;AAClD;AAEA,eAAsB,mBAAmB,MAGX;AAC5B,QAAM,UAAU,uBAAuB,IAAI;AAC3C,QAAM,UAAU,KAAK,WAAW;AAEhC,SAAO,IAAI,QAAQ,CAACA,aAAY;AAC9B,UAAM,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,MAAM;AAAA,MACnD,KAAK,KAAK;AAAA,MACV,KAAK,QAAQ;AAAA,IACf,CAAC;AACD,QAAI,SAAS;AACb,QAAI,SAAS;AAEb,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AACjC,gBAAU,OAAO,KAAK;AAAA,IACxB,CAAC;AACD,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AACjC,gBAAU,OAAO,KAAK;AAAA,IACxB,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,UAAU;AAC3B,MAAAA,SAAQ;AAAA,QACN,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,SAAS,QAAQ;AAAA,QACjB,MAAM,QAAQ;AAAA,QACd;AAAA,QACA,QAAQ,SAAS,MAAM;AAAA,MACzB,CAAC;AAAA,IACH,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,aAAa;AAC9B,MAAAA,SAAQ;AAAA,QACN,IAAI,aAAa;AAAA,QACjB;AAAA,QACA,SAAS,QAAQ;AAAA,QACjB,MAAM,QAAQ;AAAA,QACd;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,kBACP,OACA,YAC8B;AAC9B,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,UAAM,IAAI,MAAM,+BAA+B,UAAU,mBAAmB;AAAA,EAC9E;AACA,QAAM,SAAS;AACf,MAAI,OAAO,OAAO,kBAAkB,YAAY,CAAC,OAAO,cAAc,WAAW,IAAI,GAAG;AACtF,UAAM,IAAI;AAAA,MACR,+BAA+B,UAAU;AAAA,IAC3C;AAAA,EACF;AACA,MAAI,CAAC,MAAM,QAAQ,OAAO,QAAQ,GAAG;AACnC,UAAM,IAAI,MAAM,+BAA+B,UAAU,2BAA2B;AAAA,EACtF;AACF;","names":["resolve"]}