executable-stories-mcp 0.3.0 → 0.3.2
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/dist/http.cjs.map +1 -1
- package/dist/http.js +3 -1
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +10 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/server.js +37 -1
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/http.cjs.map
CHANGED
|
@@ -1 +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","http","resolve"]}
|
|
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 DeploymentStatus,\n EnvironmentDrift,\n} from \"executable-stories-formatters\";\nimport {\n diffStoryReports,\n scenariosCoveringPaths,\n toBehaviorManifest,\n toScenarioIndex,\n getDeploymentStatus as getDeploymentStatusCore,\n getEnvironmentDrift as getEnvironmentDriftCore,\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\nexport interface DeploymentQueryResult {\n status: DeploymentStatus;\n ledgerPath: string;\n}\n\nexport function getDeploymentStatus(ledgerPath?: string): DeploymentStatus {\n return getDeploymentStatusCore(ledgerPath ?? \".executable-stories/deployments.json\");\n}\n\nexport function getEnvironmentDrift(envA: string, envB: string, ledgerPath?: string): EnvironmentDrift {\n return getEnvironmentDriftCore(ledgerPath ?? \".executable-stories/deployments.json\", envA, envB);\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","http","resolve"]}
|
package/dist/http.js
CHANGED
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
diffStoryReports,
|
|
10
10
|
scenariosCoveringPaths,
|
|
11
11
|
toBehaviorManifest,
|
|
12
|
-
toScenarioIndex
|
|
12
|
+
toScenarioIndex,
|
|
13
|
+
getDeploymentStatus as getDeploymentStatusCore,
|
|
14
|
+
getEnvironmentDrift as getEnvironmentDriftCore
|
|
13
15
|
} from "executable-stories-formatters";
|
|
14
16
|
function loadStoryReport(reportPath) {
|
|
15
17
|
const absolutePath = path.resolve(reportPath);
|
package/dist/http.js.map
CHANGED
|
@@ -1 +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":";AAAA,OAAO,UAAU;;;ACAjB,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;AAuBO,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;;;AD/RA,IAAM,iBAAiB,IAAI,IAAI,cAAc,IAAI,CAAC,SAAS,CAAC,KAAK,OAAO,KAAK,GAAG,CAAC,CAAC;AAGlF,SAAS,aAAa,QAA+C;AACnE,QAAM,WAAW,OAAO,OAAO,QAAQ;AACvC,QAAM,OAAO,OAAO,OAAO,KAAK;AAChC,QAAM,cAAc,OAAO,OAAO,YAAY;AAC9C,SAAO;AAAA,IACL,UAAU,SAAS,SAAU,WAAgD;AAAA,IAC7E,MAAM,KAAK,SAAS,OAAO;AAAA,IAC3B,aAAa,YAAY,SAAS,cAAc;AAAA,EAClD;AACF;AAEO,SAAS,iBAAiB,UAA6B,CAAC,GAAgB;AAC7E,SAAO,KAAK,aAAa,OAAO,SAAS,aAAa;AACpD,QAAI;AACF,UAAI,CAAC,QAAQ,KAAK;AAChB,iBAAS,UAAU,KAAK,EAAE,OAAO,cAAc,CAAC;AAChD;AAAA,MACF;AAEA,YAAM,MAAM,IAAI,IAAI,QAAQ,KAAK,kBAAkB;AACnD,YAAM,aAAa,IAAI,aAAa,IAAI,YAAY,KAAK,QAAQ;AAEjE,UAAI,QAAQ,WAAW,SAAS,IAAI,aAAa,WAAW;AAC1D,iBAAS,UAAU,KAAK,EAAE,IAAI,MAAM,MAAM,yBAAyB,CAAC;AACpE;AAAA,MACF;AAGA,UAAI,QAAQ,WAAW,SAAS,IAAI,aAAa,SAAS;AACxD,cAAM,WAAW;AAAA,UACf,kBAAkB,IAAI,aAAa,IAAI,UAAU,KAAK,MAAS;AAAA,QACjE;AACA,cAAM,UAAU;AAAA,UACd,kBAAkB,IAAI,aAAa,IAAI,SAAS,KAAK,cAAc,MAAS;AAAA,QAC9E;AACA,iBAAS,UAAU,KAAK,gBAAgB,UAAU,OAAO,CAAC;AAC1D;AAAA,MACF;AAEA,UAAI,QAAQ,WAAW,OAAO;AAC5B,cAAM,SAAS,gBAAgB,kBAAkB,UAAU,CAAC;AAI5D,YAAI,IAAI,aAAa,cAAc;AACjC,mBAAS,UAAU,KAAK,cAAc,QAAQ,aAAa,IAAI,YAAY,CAAC,CAAC;AAC7E;AAAA,QACF;AACA,YAAI,IAAI,aAAa,uBAAuB;AAC1C,mBAAS,UAAU,KAAK,qBAAqB,QAAQ,IAAI,aAAa,OAAO,MAAM,CAAC,CAAC;AACrF;AAAA,QACF;AAIA,cAAM,UAAU,eAAe,IAAI,IAAI,QAAQ;AAC/C,YAAI,SAAS;AACX,mBAAS,UAAU,KAAK,QAAQ,MAAM,CAAC;AACvC;AAAA,QACF;AACA,YAAI,IAAI,SAAS,WAAW,aAAa,GAAG;AAC1C,gBAAM,KAAK,mBAAmB,IAAI,SAAS,MAAM,cAAc,MAAM,CAAC;AACtE,gBAAM,WAAW,YAAY,QAAQ,EAAE;AACvC,mBAAS,UAAU,WAAW,MAAM,KAAK,YAAY,EAAE,OAAO,uBAAuB,EAAE,GAAG,CAAC;AAC3F;AAAA,QACF;AAAA,MACF;AAEA,UAAI,QAAQ,WAAW,UAAU,IAAI,aAAa,kBAAkB;AAClE,cAAM,OAAO,MAAM,aAAa,OAAO;AACvC,cAAM,SAAS,MAAM,mBAAmB;AAAA,UACtC,WAAW,KAAK;AAAA,UAChB,YAAY,OAAO,KAAK,UAAU;AAAA,UAClC,eAAe,OAAO,KAAK,kBAAkB,WAAW,KAAK,gBAAgB;AAAA,UAC7E,KAAK,OAAO,KAAK,QAAQ,WAAW,KAAK,MAAM;AAAA,QACjD,CAAC;AACD,iBAAS,UAAU,OAAO,KAAK,MAAM,KAAK,MAAM;AAChD;AAAA,MACF;AAEA,eAAS,UAAU,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IAChD,SAAS,OAAO;AACd,eAAS,UAAU,KAAK,EAAE,OAAQ,MAAgB,QAAQ,CAAC;AAAA,IAC7D;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,gBAAgB,UAA6B,CAAC,GAAyB;AAC3F,QAAM,SAAS,iBAAiB,OAAO;AACvC,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,IAAI,QAAc,CAACC,aAAY;AACnC,WAAO,OAAO,MAAM,MAAMA,QAAO;AAAA,EACnC,CAAC;AACD,SAAO;AACT;AAEA,SAAS,SAAS,UAA+B,YAAoB,OAAsB;AACzF,WAAS,UAAU,YAAY;AAAA,IAC7B,gBAAgB;AAAA,IAChB,+BAA+B;AAAA,EACjC,CAAC;AACD,WAAS,IAAI,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAC7C;AAEA,eAAe,aAAa,SAAiE;AAC3F,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,SAAS;AACjC,WAAO,KAAK,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK,CAAC;AAAA,EACjE;AACA,SAAO,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,KAAK,IAAI;AAClE;","names":["resolve","resolve"]}
|
|
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 DeploymentStatus,\n EnvironmentDrift,\n} from \"executable-stories-formatters\";\nimport {\n diffStoryReports,\n scenariosCoveringPaths,\n toBehaviorManifest,\n toScenarioIndex,\n getDeploymentStatus as getDeploymentStatusCore,\n getEnvironmentDrift as getEnvironmentDriftCore,\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\nexport interface DeploymentQueryResult {\n status: DeploymentStatus;\n ledgerPath: string;\n}\n\nexport function getDeploymentStatus(ledgerPath?: string): DeploymentStatus {\n return getDeploymentStatusCore(ledgerPath ?? \".executable-stories/deployments.json\");\n}\n\nexport function getEnvironmentDrift(envA: string, envB: string, ledgerPath?: string): EnvironmentDrift {\n return getEnvironmentDriftCore(ledgerPath ?? \".executable-stories/deployments.json\", envA, envB);\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
CHANGED
|
@@ -34,6 +34,8 @@ __export(src_exports, {
|
|
|
34
34
|
buildFocusedRunCommand: () => buildFocusedRunCommand,
|
|
35
35
|
getBehaviorDiff: () => getBehaviorDiff,
|
|
36
36
|
getBehaviorManifest: () => getBehaviorManifest,
|
|
37
|
+
getDeploymentStatus: () => getDeploymentStatus,
|
|
38
|
+
getEnvironmentDrift: () => getEnvironmentDrift,
|
|
37
39
|
getFailingScenarios: () => getFailingScenarios,
|
|
38
40
|
getFeatureSummary: () => getFeatureSummary,
|
|
39
41
|
getScenario: () => getScenario,
|
|
@@ -101,6 +103,12 @@ function getScenarioIndex(report) {
|
|
|
101
103
|
function getBehaviorManifest(report) {
|
|
102
104
|
return (0, import_executable_stories_formatters.toBehaviorManifest)(report);
|
|
103
105
|
}
|
|
106
|
+
function getDeploymentStatus(ledgerPath) {
|
|
107
|
+
return (0, import_executable_stories_formatters.getDeploymentStatus)(ledgerPath ?? ".executable-stories/deployments.json");
|
|
108
|
+
}
|
|
109
|
+
function getEnvironmentDrift(envA, envB, ledgerPath) {
|
|
110
|
+
return (0, import_executable_stories_formatters.getEnvironmentDrift)(ledgerPath ?? ".executable-stories/deployments.json", envA, envB);
|
|
111
|
+
}
|
|
104
112
|
var readOnlyTools = [
|
|
105
113
|
{
|
|
106
114
|
name: "get_failing_scenarios",
|
|
@@ -238,6 +246,8 @@ function assertStoryReport(value, reportPath) {
|
|
|
238
246
|
buildFocusedRunCommand,
|
|
239
247
|
getBehaviorDiff,
|
|
240
248
|
getBehaviorManifest,
|
|
249
|
+
getDeploymentStatus,
|
|
250
|
+
getEnvironmentDrift,
|
|
241
251
|
getFailingScenarios,
|
|
242
252
|
getFeatureSummary,
|
|
243
253
|
getScenario,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
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 DeploymentStatus,\n EnvironmentDrift,\n} from \"executable-stories-formatters\";\nimport {\n diffStoryReports,\n scenariosCoveringPaths,\n toBehaviorManifest,\n toScenarioIndex,\n getDeploymentStatus as getDeploymentStatusCore,\n getEnvironmentDrift as getEnvironmentDriftCore,\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\nexport interface DeploymentQueryResult {\n status: DeploymentStatus;\n ledgerPath: string;\n}\n\nexport function getDeploymentStatus(ledgerPath?: string): DeploymentStatus {\n return getDeploymentStatusCore(ledgerPath ?? \".executable-stories/deployments.json\");\n}\n\nexport function getEnvironmentDrift(envA: string, envB: string, ledgerPath?: string): EnvironmentDrift {\n return getEnvironmentDriftCore(ledgerPath ?? \".executable-stories/deployments.json\", envA, envB);\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":["getDeploymentStatusCore","getEnvironmentDriftCore","resolve"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { StoryReport, ReportFeature, ReportScenario, BehaviorDiff, BehaviorManifest, ScenarioIndexItem, ScenarioIndex, ScenarioIndexFilters } from 'executable-stories-formatters';
|
|
2
|
+
import { DeploymentStatus, StoryReport, ReportFeature, ReportScenario, BehaviorDiff, BehaviorManifest, EnvironmentDrift, ScenarioIndexItem, ScenarioIndex, ScenarioIndexFilters } from 'executable-stories-formatters';
|
|
3
3
|
export { ScenarioIndexFilters, ScenarioIndexItem } from 'executable-stories-formatters';
|
|
4
4
|
|
|
5
5
|
interface FeatureSummaryItem {
|
|
@@ -27,6 +27,12 @@ declare function getFeatureSummary(report: StoryReport): FeatureSummaryItem[];
|
|
|
27
27
|
declare function resolveReportPath(reportPath?: string): string;
|
|
28
28
|
declare function getScenarioIndex(report: StoryReport): ScenarioIndex;
|
|
29
29
|
declare function getBehaviorManifest(report: StoryReport): BehaviorManifest;
|
|
30
|
+
interface DeploymentQueryResult {
|
|
31
|
+
status: DeploymentStatus;
|
|
32
|
+
ledgerPath: string;
|
|
33
|
+
}
|
|
34
|
+
declare function getDeploymentStatus(ledgerPath?: string): DeploymentStatus;
|
|
35
|
+
declare function getEnvironmentDrift(envA: string, envB: string, ledgerPath?: string): EnvironmentDrift;
|
|
30
36
|
/**
|
|
31
37
|
* Single source of truth for the read-only tools, consumed by both the stdio
|
|
32
38
|
* MCP server and the HTTP server so the two transports cannot drift apart.
|
|
@@ -91,4 +97,4 @@ declare function runFocusedScenario(args: FocusedRunCommandArgs & {
|
|
|
91
97
|
spawnFn?: typeof spawn;
|
|
92
98
|
}): Promise<FocusedRunResult>;
|
|
93
99
|
|
|
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 };
|
|
100
|
+
export { type DeploymentQueryResult, type FeatureSummaryItem, type FocusedRunCommand, type FocusedRunCommandArgs, type FocusedRunFramework, type FocusedRunResult, RUNNERS, type ReadOnlyTool, type RunnerDefinition, type ScenarioLookup, buildFocusedRunCommand, getBehaviorDiff, getBehaviorManifest, getDeploymentStatus, getEnvironmentDrift, getFailingScenarios, getFeatureSummary, getScenario, getScenarioIndex, getScenariosForPaths, inferFrameworkFromSourceFile, listScenarios, loadStoryReport, readOnlyTools, resolveFocusedRunFramework, resolveReportPath, runFocusedScenario };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { StoryReport, ReportFeature, ReportScenario, BehaviorDiff, BehaviorManifest, ScenarioIndexItem, ScenarioIndex, ScenarioIndexFilters } from 'executable-stories-formatters';
|
|
2
|
+
import { DeploymentStatus, StoryReport, ReportFeature, ReportScenario, BehaviorDiff, BehaviorManifest, EnvironmentDrift, ScenarioIndexItem, ScenarioIndex, ScenarioIndexFilters } from 'executable-stories-formatters';
|
|
3
3
|
export { ScenarioIndexFilters, ScenarioIndexItem } from 'executable-stories-formatters';
|
|
4
4
|
|
|
5
5
|
interface FeatureSummaryItem {
|
|
@@ -27,6 +27,12 @@ declare function getFeatureSummary(report: StoryReport): FeatureSummaryItem[];
|
|
|
27
27
|
declare function resolveReportPath(reportPath?: string): string;
|
|
28
28
|
declare function getScenarioIndex(report: StoryReport): ScenarioIndex;
|
|
29
29
|
declare function getBehaviorManifest(report: StoryReport): BehaviorManifest;
|
|
30
|
+
interface DeploymentQueryResult {
|
|
31
|
+
status: DeploymentStatus;
|
|
32
|
+
ledgerPath: string;
|
|
33
|
+
}
|
|
34
|
+
declare function getDeploymentStatus(ledgerPath?: string): DeploymentStatus;
|
|
35
|
+
declare function getEnvironmentDrift(envA: string, envB: string, ledgerPath?: string): EnvironmentDrift;
|
|
30
36
|
/**
|
|
31
37
|
* Single source of truth for the read-only tools, consumed by both the stdio
|
|
32
38
|
* MCP server and the HTTP server so the two transports cannot drift apart.
|
|
@@ -91,4 +97,4 @@ declare function runFocusedScenario(args: FocusedRunCommandArgs & {
|
|
|
91
97
|
spawnFn?: typeof spawn;
|
|
92
98
|
}): Promise<FocusedRunResult>;
|
|
93
99
|
|
|
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 };
|
|
100
|
+
export { type DeploymentQueryResult, type FeatureSummaryItem, type FocusedRunCommand, type FocusedRunCommandArgs, type FocusedRunFramework, type FocusedRunResult, RUNNERS, type ReadOnlyTool, type RunnerDefinition, type ScenarioLookup, buildFocusedRunCommand, getBehaviorDiff, getBehaviorManifest, getDeploymentStatus, getEnvironmentDrift, getFailingScenarios, getFeatureSummary, getScenario, getScenarioIndex, getScenariosForPaths, inferFrameworkFromSourceFile, listScenarios, loadStoryReport, readOnlyTools, resolveFocusedRunFramework, resolveReportPath, runFocusedScenario };
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
diffStoryReports,
|
|
7
7
|
scenariosCoveringPaths,
|
|
8
8
|
toBehaviorManifest,
|
|
9
|
-
toScenarioIndex
|
|
9
|
+
toScenarioIndex,
|
|
10
|
+
getDeploymentStatus as getDeploymentStatusCore,
|
|
11
|
+
getEnvironmentDrift as getEnvironmentDriftCore
|
|
10
12
|
} from "executable-stories-formatters";
|
|
11
13
|
function loadStoryReport(reportPath) {
|
|
12
14
|
const absolutePath = path.resolve(reportPath);
|
|
@@ -57,6 +59,12 @@ function getScenarioIndex(report) {
|
|
|
57
59
|
function getBehaviorManifest(report) {
|
|
58
60
|
return toBehaviorManifest(report);
|
|
59
61
|
}
|
|
62
|
+
function getDeploymentStatus(ledgerPath) {
|
|
63
|
+
return getDeploymentStatusCore(ledgerPath ?? ".executable-stories/deployments.json");
|
|
64
|
+
}
|
|
65
|
+
function getEnvironmentDrift(envA, envB, ledgerPath) {
|
|
66
|
+
return getEnvironmentDriftCore(ledgerPath ?? ".executable-stories/deployments.json", envA, envB);
|
|
67
|
+
}
|
|
60
68
|
var readOnlyTools = [
|
|
61
69
|
{
|
|
62
70
|
name: "get_failing_scenarios",
|
|
@@ -193,6 +201,8 @@ export {
|
|
|
193
201
|
buildFocusedRunCommand,
|
|
194
202
|
getBehaviorDiff,
|
|
195
203
|
getBehaviorManifest,
|
|
204
|
+
getDeploymentStatus,
|
|
205
|
+
getEnvironmentDrift,
|
|
196
206
|
getFailingScenarios,
|
|
197
207
|
getFeatureSummary,
|
|
198
208
|
getScenario,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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 DeploymentStatus,\n EnvironmentDrift,\n} from \"executable-stories-formatters\";\nimport {\n diffStoryReports,\n scenariosCoveringPaths,\n toBehaviorManifest,\n toScenarioIndex,\n getDeploymentStatus as getDeploymentStatusCore,\n getEnvironmentDrift as getEnvironmentDriftCore,\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\nexport interface DeploymentQueryResult {\n status: DeploymentStatus;\n ledgerPath: string;\n}\n\nexport function getDeploymentStatus(ledgerPath?: string): DeploymentStatus {\n return getDeploymentStatusCore(ledgerPath ?? \".executable-stories/deployments.json\");\n}\n\nexport function getEnvironmentDrift(envA: string, envB: string, ledgerPath?: string): EnvironmentDrift {\n return getEnvironmentDriftCore(ledgerPath ?? \".executable-stories/deployments.json\", envA, envB);\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"]}
|
package/dist/server.js
CHANGED
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
diffStoryReports,
|
|
14
14
|
scenariosCoveringPaths,
|
|
15
15
|
toBehaviorManifest,
|
|
16
|
-
toScenarioIndex
|
|
16
|
+
toScenarioIndex,
|
|
17
|
+
getDeploymentStatus as getDeploymentStatusCore,
|
|
18
|
+
getEnvironmentDrift as getEnvironmentDriftCore
|
|
17
19
|
} from "executable-stories-formatters";
|
|
18
20
|
function loadStoryReport(reportPath) {
|
|
19
21
|
const absolutePath = path.resolve(reportPath);
|
|
@@ -64,6 +66,12 @@ function getScenarioIndex(report) {
|
|
|
64
66
|
function getBehaviorManifest(report) {
|
|
65
67
|
return toBehaviorManifest(report);
|
|
66
68
|
}
|
|
69
|
+
function getDeploymentStatus(ledgerPath) {
|
|
70
|
+
return getDeploymentStatusCore(ledgerPath ?? ".executable-stories/deployments.json");
|
|
71
|
+
}
|
|
72
|
+
function getEnvironmentDrift(envA, envB, ledgerPath) {
|
|
73
|
+
return getEnvironmentDriftCore(ledgerPath ?? ".executable-stories/deployments.json", envA, envB);
|
|
74
|
+
}
|
|
67
75
|
var readOnlyTools = [
|
|
68
76
|
{
|
|
69
77
|
name: "get_failing_scenarios",
|
|
@@ -322,6 +330,34 @@ server.registerTool(
|
|
|
322
330
|
});
|
|
323
331
|
}
|
|
324
332
|
);
|
|
333
|
+
server.registerTool(
|
|
334
|
+
"get_deployment_status",
|
|
335
|
+
{
|
|
336
|
+
title: "Get deployment status",
|
|
337
|
+
description: "Show the latest deployment for each environment from the deployment ledger. Which scenarios are deployed to dev, staging, and production?",
|
|
338
|
+
inputSchema: {
|
|
339
|
+
ledgerPath: z.string().optional().describe(
|
|
340
|
+
"Path to the deployment ledger JSON. Defaults to .executable-stories/deployments.json."
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
async ({ ledgerPath }) => json(getDeploymentStatus(ledgerPath))
|
|
345
|
+
);
|
|
346
|
+
server.registerTool(
|
|
347
|
+
"get_environment_drift",
|
|
348
|
+
{
|
|
349
|
+
title: "Get environment drift",
|
|
350
|
+
description: "Compare two environments to find which scenarios exist in one but not the other. Use this to detect configuration/code drift between dev and prod.",
|
|
351
|
+
inputSchema: {
|
|
352
|
+
envA: z.string().describe("First environment name (e.g. dev)."),
|
|
353
|
+
envB: z.string().describe("Second environment name (e.g. production)."),
|
|
354
|
+
ledgerPath: z.string().optional().describe(
|
|
355
|
+
"Path to the deployment ledger JSON. Defaults to .executable-stories/deployments.json."
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
async ({ envA, envB, ledgerPath }) => json(getEnvironmentDrift(envA, envB, ledgerPath))
|
|
360
|
+
);
|
|
325
361
|
var transport = new StdioServerTransport();
|
|
326
362
|
await server.connect(transport);
|
|
327
363
|
function json(value) {
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/index.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\n\nimport {\n getBehaviorDiff,\n getScenario,\n getScenariosForPaths,\n listScenarios,\n loadStoryReport,\n readOnlyTools,\n resolveFocusedRunFramework,\n resolveReportPath,\n runFocusedScenario,\n} from \"./index.js\";\n\nconst reportPathSchema = {\n reportPath: z\n .string()\n .optional()\n .describe(\"Path to StoryReport JSON. Defaults to reports/index.story-report.json.\"),\n};\n\nconst filterSchema = {\n ...reportPathSchema,\n statuses: z\n .array(z.enum([\"passed\", \"failed\", \"skipped\", \"pending\"]))\n .optional()\n .describe(\"Filter by scenario status (any match).\"),\n tags: z.array(z.string()).optional().describe(\"Filter by tag (any match).\"),\n sourceFiles: z\n .array(z.string())\n .optional()\n .describe(\"Filter by source file substring/glob (any match).\"),\n};\n\nconst frameworkSchema = z\n .enum([\"vitest\", \"jest\", \"playwright\", \"cypress\"])\n .optional()\n .describe(\"Host test framework. Inferred from source file when possible.\");\n\nconst server = new McpServer({\n name: \"executable-stories\",\n version: \"0.2.0\",\n});\n\nfor (const tool of readOnlyTools) {\n server.registerTool(\n tool.name,\n {\n title: tool.title,\n description: tool.description,\n inputSchema: reportPathSchema,\n },\n async ({ reportPath }) =>\n json(tool.run(loadStoryReport(resolveReportPath(reportPath)))),\n );\n}\n\nserver.registerTool(\n \"list_scenarios\",\n {\n title: \"List scenarios\",\n description: \"List executable story scenarios, optionally filtered by status, tag, or source file.\",\n inputSchema: filterSchema,\n },\n async ({ reportPath, statuses, tags, sourceFiles }) =>\n json(\n listScenarios(loadStoryReport(resolveReportPath(reportPath)), {\n statuses,\n tags,\n sourceFiles,\n }),\n ),\n);\n\nserver.registerTool(\n \"get_scenarios_for_paths\",\n {\n title: \"Get scenarios for paths\",\n description:\n \"Find scenarios whose declared `covers` globs match the given product-code paths (e.g. a changed-file list).\",\n inputSchema: {\n ...reportPathSchema,\n paths: z.array(z.string()).min(1).describe(\"Product-code paths or globs to look up.\"),\n },\n },\n async ({ reportPath, paths }) =>\n json(getScenariosForPaths(loadStoryReport(resolveReportPath(reportPath)), paths)),\n);\n\nserver.registerTool(\n \"get_behavior_diff\",\n {\n title: \"Get behavior diff\",\n description:\n \"Compare two StoryReports by scenario id: regressed, fixed, added, removed, changed, unchanged.\",\n inputSchema: {\n baselineReportPath: z.string().describe(\"Path to the baseline StoryReport JSON.\"),\n currentReportPath: z\n .string()\n .optional()\n .describe(\"Path to the current StoryReport JSON. Defaults to the standard report path.\"),\n },\n },\n async ({ baselineReportPath, currentReportPath }) =>\n json(\n getBehaviorDiff(\n loadStoryReport(resolveReportPath(baselineReportPath)),\n loadStoryReport(resolveReportPath(currentReportPath)),\n ),\n ),\n);\n\nserver.registerTool(\n \"get_scenario\",\n {\n title: \"Get scenario\",\n description: \"Get one scenario by StoryReport scenario id or exact title.\",\n inputSchema: {\n ...reportPathSchema,\n idOrTitle: z.string().describe(\"Scenario id or exact scenario title.\"),\n },\n },\n async ({ reportPath, idOrTitle }) => {\n const report = loadStoryReport(resolveReportPath(reportPath));\n const lookup = getScenario(report, idOrTitle);\n if (!lookup) {\n return json({ error: `Scenario not found: ${idOrTitle}` });\n }\n return json(lookup);\n },\n);\n\nserver.registerTool(\n \"run_scenario\",\n {\n title: \"Run scenario\",\n description:\n \"Run one scenario through the host test framework (vitest, jest, playwright, or cypress). Executes real tests.\",\n inputSchema: {\n ...reportPathSchema,\n idOrTitle: z.string().describe(\"Scenario id or exact scenario title.\"),\n framework: frameworkSchema,\n cwd: z\n .string()\n .optional()\n .describe(\"Working directory for the test command. Defaults to process.cwd().\"),\n },\n },\n async ({ reportPath, idOrTitle, framework, cwd }) => {\n const report = loadStoryReport(resolveReportPath(reportPath));\n const lookup = getScenario(report, idOrTitle);\n if (!lookup) {\n return json({ error: `Scenario not found: ${idOrTitle}` });\n }\n\n const sourceFile = lookup.feature.sourceFile;\n const resolvedFramework = resolveFocusedRunFramework({ sourceFile, framework });\n const result = await runFocusedScenario({\n framework: resolvedFramework,\n sourceFile,\n scenarioTitle: lookup.scenario.title,\n cwd: cwd ?? process.cwd(),\n });\n\n return json({\n scenario: {\n id: lookup.scenario.id,\n title: lookup.scenario.title,\n sourceFile,\n },\n framework: resolvedFramework,\n ...result,\n });\n },\n);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n\nfunction json(value: unknown): {\n content: Array<{ type: \"text\"; text: string }>;\n} {\n return {\n content: [{ type: \"text\", text: JSON.stringify(value, null, 2) }],\n };\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/index.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\n\nimport {\n getBehaviorDiff,\n getDeploymentStatus as getDeploymentStatusFn,\n getEnvironmentDrift as getEnvironmentDriftFn,\n getScenario,\n getScenariosForPaths,\n listScenarios,\n loadStoryReport,\n readOnlyTools,\n resolveFocusedRunFramework,\n resolveReportPath,\n runFocusedScenario,\n} from \"./index.js\";\n\nconst reportPathSchema = {\n reportPath: z\n .string()\n .optional()\n .describe(\"Path to StoryReport JSON. Defaults to reports/index.story-report.json.\"),\n};\n\nconst filterSchema = {\n ...reportPathSchema,\n statuses: z\n .array(z.enum([\"passed\", \"failed\", \"skipped\", \"pending\"]))\n .optional()\n .describe(\"Filter by scenario status (any match).\"),\n tags: z.array(z.string()).optional().describe(\"Filter by tag (any match).\"),\n sourceFiles: z\n .array(z.string())\n .optional()\n .describe(\"Filter by source file substring/glob (any match).\"),\n};\n\nconst frameworkSchema = z\n .enum([\"vitest\", \"jest\", \"playwright\", \"cypress\"])\n .optional()\n .describe(\"Host test framework. Inferred from source file when possible.\");\n\nconst server = new McpServer({\n name: \"executable-stories\",\n version: \"0.2.0\",\n});\n\nfor (const tool of readOnlyTools) {\n server.registerTool(\n tool.name,\n {\n title: tool.title,\n description: tool.description,\n inputSchema: reportPathSchema,\n },\n async ({ reportPath }) =>\n json(tool.run(loadStoryReport(resolveReportPath(reportPath)))),\n );\n}\n\nserver.registerTool(\n \"list_scenarios\",\n {\n title: \"List scenarios\",\n description: \"List executable story scenarios, optionally filtered by status, tag, or source file.\",\n inputSchema: filterSchema,\n },\n async ({ reportPath, statuses, tags, sourceFiles }) =>\n json(\n listScenarios(loadStoryReport(resolveReportPath(reportPath)), {\n statuses,\n tags,\n sourceFiles,\n }),\n ),\n);\n\nserver.registerTool(\n \"get_scenarios_for_paths\",\n {\n title: \"Get scenarios for paths\",\n description:\n \"Find scenarios whose declared `covers` globs match the given product-code paths (e.g. a changed-file list).\",\n inputSchema: {\n ...reportPathSchema,\n paths: z.array(z.string()).min(1).describe(\"Product-code paths or globs to look up.\"),\n },\n },\n async ({ reportPath, paths }) =>\n json(getScenariosForPaths(loadStoryReport(resolveReportPath(reportPath)), paths)),\n);\n\nserver.registerTool(\n \"get_behavior_diff\",\n {\n title: \"Get behavior diff\",\n description:\n \"Compare two StoryReports by scenario id: regressed, fixed, added, removed, changed, unchanged.\",\n inputSchema: {\n baselineReportPath: z.string().describe(\"Path to the baseline StoryReport JSON.\"),\n currentReportPath: z\n .string()\n .optional()\n .describe(\"Path to the current StoryReport JSON. Defaults to the standard report path.\"),\n },\n },\n async ({ baselineReportPath, currentReportPath }) =>\n json(\n getBehaviorDiff(\n loadStoryReport(resolveReportPath(baselineReportPath)),\n loadStoryReport(resolveReportPath(currentReportPath)),\n ),\n ),\n);\n\nserver.registerTool(\n \"get_scenario\",\n {\n title: \"Get scenario\",\n description: \"Get one scenario by StoryReport scenario id or exact title.\",\n inputSchema: {\n ...reportPathSchema,\n idOrTitle: z.string().describe(\"Scenario id or exact scenario title.\"),\n },\n },\n async ({ reportPath, idOrTitle }) => {\n const report = loadStoryReport(resolveReportPath(reportPath));\n const lookup = getScenario(report, idOrTitle);\n if (!lookup) {\n return json({ error: `Scenario not found: ${idOrTitle}` });\n }\n return json(lookup);\n },\n);\n\nserver.registerTool(\n \"run_scenario\",\n {\n title: \"Run scenario\",\n description:\n \"Run one scenario through the host test framework (vitest, jest, playwright, or cypress). Executes real tests.\",\n inputSchema: {\n ...reportPathSchema,\n idOrTitle: z.string().describe(\"Scenario id or exact scenario title.\"),\n framework: frameworkSchema,\n cwd: z\n .string()\n .optional()\n .describe(\"Working directory for the test command. Defaults to process.cwd().\"),\n },\n },\n async ({ reportPath, idOrTitle, framework, cwd }) => {\n const report = loadStoryReport(resolveReportPath(reportPath));\n const lookup = getScenario(report, idOrTitle);\n if (!lookup) {\n return json({ error: `Scenario not found: ${idOrTitle}` });\n }\n\n const sourceFile = lookup.feature.sourceFile;\n const resolvedFramework = resolveFocusedRunFramework({ sourceFile, framework });\n const result = await runFocusedScenario({\n framework: resolvedFramework,\n sourceFile,\n scenarioTitle: lookup.scenario.title,\n cwd: cwd ?? process.cwd(),\n });\n\n return json({\n scenario: {\n id: lookup.scenario.id,\n title: lookup.scenario.title,\n sourceFile,\n },\n framework: resolvedFramework,\n ...result,\n });\n },\n);\n\nserver.registerTool(\n \"get_deployment_status\",\n {\n title: \"Get deployment status\",\n description:\n \"Show the latest deployment for each environment from the deployment ledger. Which scenarios are deployed to dev, staging, and production?\",\n inputSchema: {\n ledgerPath: z\n .string()\n .optional()\n .describe(\n \"Path to the deployment ledger JSON. Defaults to .executable-stories/deployments.json.\",\n ),\n },\n },\n async ({ ledgerPath }) => json(getDeploymentStatusFn(ledgerPath)),\n);\n\nserver.registerTool(\n \"get_environment_drift\",\n {\n title: \"Get environment drift\",\n description:\n \"Compare two environments to find which scenarios exist in one but not the other. Use this to detect configuration/code drift between dev and prod.\",\n inputSchema: {\n envA: z.string().describe(\"First environment name (e.g. dev).\"),\n envB: z.string().describe(\"Second environment name (e.g. production).\"),\n ledgerPath: z\n .string()\n .optional()\n .describe(\n \"Path to the deployment ledger JSON. Defaults to .executable-stories/deployments.json.\",\n ),\n },\n },\n async ({ envA, envB, ledgerPath }) => json(getEnvironmentDriftFn(envA, envB, ledgerPath)),\n);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n\nfunction json(value: unknown): {\n content: Array<{ type: \"text\"; text: string }>;\n} {\n return {\n content: [{ type: \"text\", text: JSON.stringify(value, null, 2) }],\n };\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 DeploymentStatus,\n EnvironmentDrift,\n} from \"executable-stories-formatters\";\nimport {\n diffStoryReports,\n scenariosCoveringPaths,\n toBehaviorManifest,\n toScenarioIndex,\n getDeploymentStatus as getDeploymentStatusCore,\n getEnvironmentDrift as getEnvironmentDriftCore,\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\nexport interface DeploymentQueryResult {\n status: DeploymentStatus;\n ledgerPath: string;\n}\n\nexport function getDeploymentStatus(ledgerPath?: string): DeploymentStatus {\n return getDeploymentStatusCore(ledgerPath ?? \".executable-stories/deployments.json\");\n}\n\nexport function getEnvironmentDrift(envA: string, envB: string, ledgerPath?: string): EnvironmentDrift {\n return getEnvironmentDriftCore(ledgerPath ?? \".executable-stories/deployments.json\", envA, envB);\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "executable-stories-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Read-only MCP server for executable-stories StoryReport JSON behavior catalogs.",
|
|
5
5
|
"author": "Jag Reehal <jag@jagreehal.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
36
36
|
"zod": "^4.0.0",
|
|
37
|
-
"executable-stories-formatters": "0.
|
|
37
|
+
"executable-stories-formatters": "0.11.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^25.6.0",
|