executable-stories-vitest 8.8.0 → 8.8.1

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/story-api.ts","../src/types.ts","../src/reporter.ts"],"sourcesContent":["/**\n * executable-stories-vitest: Native Vitest story/given/when/then with Markdown doc generation.\n *\n * Uses native Vitest describe/it/test for full IDE support:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n *\n * story.given('two numbers 5 and 3');\n * const a = 5, b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n *\n * In vitest.config, use createStoryReporter factory from \"executable-stories-vitest/reporter\":\n *\n * @example\n * ```ts\n * import { defineConfig } from \"vitest/config\";\n * import { createStoryReporter } from \"executable-stories-vitest/reporter\";\n *\n * export default defineConfig({\n * test: {\n * reporters: [\"default\", createStoryReporter()],\n * },\n * });\n * ```\n *\n * The factory provides type-safe reporter creation with no type casts.\n */\n\n// Core API\nexport { story, type Story } from './story-api';\n\n// Types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n FeatureInput,\n GlossaryTerm,\n StepKeyword,\n StepMode,\n DocPhase,\n NormalizedTicket,\n TicketInput,\n StoryDocs,\n StoryOptions,\n VitestTask,\n VitestSuite,\n} from './types';\n\nexport { STORY_META_KEY } from './types';\n\n// Reporter types and factory (actual reporter is in /reporter subpath)\nexport type {\n StoryReporterOptions,\n StoryReporterProtocol,\n VitestContext,\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from './reporter';\nexport { createStoryReporter } from './reporter';\n\nconst STORY_REPORTER_GUARD_MSG =\n 'Do not import StoryReporter from \"executable-stories-vitest\". In vitest.config, import it from \"executable-stories-vitest/reporter\".';\n\n/** @internal Guard: throws if used. Import StoryReporter from \"executable-stories-vitest/reporter\" in vitest.config. */\nexport class StoryReporter {\n static __isGuard = true;\n constructor() {\n throw new Error(STORY_REPORTER_GUARD_MSG);\n }\n}\n","/**\n * story.* API for executable-stories-vitest.\n *\n * Uses native Vitest describe/it/test with opt-in documentation:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n *\n * story.given('two numbers 5 and 3');\n * const a = 5;\n * const b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\n\nimport { expect, onTestFinished } from 'vitest';\nimport { createRequire } from 'node:module';\nimport { tryGetActiveOtelContext, resolveTraceUrl } from 'executable-stories-core/utils/otel-detect';\nimport { buildHtmlDocEntry } from 'executable-stories-core/utils/doc-builders';\nimport type {\n DocEntry,\n FeatureInput,\n NormalizedTicket,\n StepKeyword,\n StoryDocs,\n StoryMeta,\n StoryOptions,\n StoryStep,\n TicketInput,\n VitestSuite,\n} from './types';\n\n// ============================================================================\n// Task Interface (compatible with Vitest's actual task type)\n// ============================================================================\n\n/**\n * Minimal task interface compatible with Vitest's Test type.\n * The meta property accepts any object type to be compatible with Vitest's TaskMeta.\n */\ninterface TaskLike {\n name: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n meta: any;\n suite?: VitestSuite;\n file?: { name?: string };\n}\n\n// ============================================================================\n// Story Context\n// ============================================================================\n\n/** Attachment options for story.attach() */\nexport interface AttachmentOptions {\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n}\n\n/** Internal: attachment with step scope */\ninterface ScopedAttachment extends AttachmentOptions {\n stepIndex?: number;\n stepId?: string;\n}\n\n/** Internal timer entry */\ninterface TimerEntry {\n start: number;\n stepIndex?: number;\n stepId?: string;\n consumed: boolean;\n}\n\ninterface StoryContext {\n /** The story metadata being built */\n meta: StoryMeta;\n /** The current step (for attaching docs) */\n currentStep: StoryStep | null;\n /** Reference to task.meta for updates */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n taskMeta: any;\n /** Deterministic step counter (resets per test case) */\n stepCounter: number;\n /** Collected attachments with step scope */\n attachments: ScopedAttachment[];\n /** Active timers keyed by token */\n activeTimers: Map<number, TimerEntry>;\n /** Monotonic timer token counter */\n timerCounter: number;\n /** Trace-link URL template, captured at init for later use by attachSpans. */\n traceUrlTemplate?: string;\n /**\n * Marker step awaiting its assertion count.\n *\n * A marker states the claim and the assertion follows it, so the count is\n * only known once the next step starts or the test ends.\n */\n pendingStep: StoryStep | null;\n /** Assertion counter reading taken when {@link pendingStep} was created. */\n pendingFrom?: number;\n}\n\n/** Active story context - set by story.init() */\nlet activeContext: StoryContext | null = null;\n\n/** Counter to track source order of stories (increments on each story.init call) */\nlet sourceOrderCounter = 0;\n\n/**\n * Get the current story context. Throws if story.init() wasn't called.\n */\nfunction getContext(): StoryContext {\n if (!activeContext) {\n throw new Error(\n \"story.init(task) must be called first. Use: it('name', ({ task }) => { story.init(task); ... });\",\n );\n }\n return activeContext;\n}\n\n/** Re-attach current meta to task.meta.story so reporter sees steps and docs (e.g. story.note). */\nfunction syncMetaToTask(): void {\n if (activeContext?.taskMeta) {\n activeContext.taskMeta.story = activeContext.meta;\n }\n}\n\n/**\n * Bridge an OTel trace into the story's meta + docs: structured `otel` meta, a\n * \"Trace ID\" key-value, and a \"View Trace\" link when the template resolves.\n * Idempotent — once a traceId is recorded it is not overwritten, so the\n * active-span path in init() and the explicit path in attachSpans() compose\n * without duplicating entries.\n */\nfunction applyTraceToMeta(\n meta: StoryMeta,\n traceId: string,\n spanId: string | undefined,\n template: string | undefined,\n): void {\n const existing = (meta.meta as { otel?: { traceId?: string } } | undefined)\n ?.otel;\n if (existing?.traceId) return;\n\n meta.meta = { ...meta.meta, otel: { traceId, spanId } };\n meta.docs = meta.docs ?? [];\n meta.docs.push({\n kind: \"kv\",\n label: \"Trace ID\",\n value: traceId,\n phase: \"runtime\",\n });\n const url = resolveTraceUrl(template, traceId);\n if (url) {\n meta.docs.push({ kind: \"link\", label: \"View Trace\", url, phase: \"runtime\" });\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Check if a name looks like a file path (to filter out from suite paths).\n */\nfunction looksLikeFilePath(name: string): boolean {\n if (name.includes('/') || name.includes('\\\\')) return true;\n if (name.includes('.spec.') || name.includes('.test.')) return true;\n if (/\\.(spec|test)\\.(ts|js|mjs|cjs)$/.test(name)) return true;\n if (/\\.(ts|js|mjs|cjs)$/.test(name)) return true;\n return false;\n}\n\n/**\n * Extract the suite path (parent describe names) from a Vitest task object.\n */\nfunction extractSuitePath(task: TaskLike): string[] | undefined {\n const path: string[] = [];\n const fileName = task.file?.name;\n let current: VitestSuite | undefined = task.suite;\n\n while (current) {\n const name = current.name;\n if (\n name &&\n name.trim() !== '' &&\n name !== '<root>' &&\n name !== fileName &&\n !looksLikeFilePath(name)\n ) {\n path.unshift(name);\n }\n current = current.suite;\n }\n\n return path.length > 0 ? path : undefined;\n}\n\n/**\n * Normalize ticket option to array of NormalizedTicket objects.\n */\nfunction normalizeTickets(\n ticket: TicketInput | TicketInput[] | undefined,\n): NormalizedTicket[] | undefined {\n if (!ticket) return undefined;\n const arr = Array.isArray(ticket) ? ticket : [ticket];\n return arr.map((t) => (typeof t === 'string' ? { id: t } : t));\n}\n\n/**\n * Convert StoryDocs inline options to DocEntry array.\n * Matches the standalone DocApi method signatures.\n */\nfunction convertStoryDocsToEntries(docs: StoryDocs): DocEntry[] {\n const entries: DocEntry[] = [];\n\n // note(text)\n if (docs.note) {\n entries.push({ kind: 'note', text: docs.note, phase: 'runtime' });\n }\n\n // tag(name | names)\n if (docs.tag) {\n const names = Array.isArray(docs.tag) ? docs.tag : [docs.tag];\n entries.push({ kind: 'tag', names, phase: 'runtime' });\n }\n\n // kv(label, value) - multiple pairs via Record\n if (docs.kv) {\n for (const [label, value] of Object.entries(docs.kv)) {\n entries.push({ kind: 'kv', label, value, phase: 'runtime' });\n }\n }\n\n // code(label, content, lang?)\n if (docs.code) {\n entries.push({\n kind: 'code',\n label: docs.code.label,\n content: docs.code.content,\n lang: docs.code.lang,\n phase: 'runtime',\n });\n }\n\n // json(label, value)\n if (docs.json) {\n entries.push({\n kind: 'code',\n label: docs.json.label,\n content: JSON.stringify(docs.json.value, null, 2),\n lang: 'json',\n phase: 'runtime',\n });\n }\n\n // state(label?, value)\n if (docs.state) {\n warnIfLargeState(docs.state.label, docs.state.value);\n entries.push({\n kind: 'state',\n label: docs.state.label,\n value: docs.state.value,\n phase: 'runtime',\n });\n }\n\n // table(label, columns, rows)\n if (docs.table) {\n entries.push({\n kind: 'table',\n label: docs.table.label,\n columns: docs.table.columns,\n rows: docs.table.rows,\n phase: 'runtime',\n });\n }\n\n // link(label, url)\n if (docs.link) {\n entries.push({\n kind: 'link',\n label: docs.link.label,\n url: docs.link.url,\n phase: 'runtime',\n });\n }\n\n // section(title, markdown)\n if (docs.section) {\n entries.push({\n kind: 'section',\n title: docs.section.title,\n markdown: docs.section.markdown,\n phase: 'runtime',\n });\n }\n\n // mermaid(code, title?)\n if (docs.mermaid) {\n entries.push({\n kind: 'mermaid',\n code: docs.mermaid.code,\n title: docs.mermaid.title,\n phase: 'runtime',\n });\n }\n\n // screenshot(path, alt?)\n if (docs.screenshot) {\n entries.push({\n kind: 'screenshot',\n path: docs.screenshot.path,\n alt: docs.screenshot.alt,\n phase: 'runtime',\n });\n }\n\n // video(path, caption?, poster?)\n if (docs.video) {\n entries.push({\n kind: 'video',\n path: docs.video.path,\n caption: docs.video.caption,\n poster: docs.video.poster,\n phase: 'runtime',\n });\n }\n\n // html(path | url | content, title?, height?)\n if (docs.html) {\n entries.push(buildHtmlDocEntry(docs.html));\n }\n\n // custom(type, data)\n if (docs.custom) {\n entries.push({\n kind: 'custom',\n type: docs.custom.type,\n data: docs.custom.data,\n phase: 'runtime',\n });\n }\n\n return entries;\n}\n\n// ============================================================================\n// story.init()\n// ============================================================================\n\n/**\n * Initialize a story for the current test.\n * Must be called at the start of each test that wants documentation.\n *\n * @param task - The Vitest task object from ({ task }) => { ... }\n * @param options - Optional story configuration (tags, ticket, meta)\n *\n * @example\n * ```ts\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n * // ... rest of test\n * });\n *\n * // With options:\n * it('admin deletes user', ({ task }) => {\n * story.init(task, {\n * tags: ['admin', 'destructive'],\n * ticket: 'JIRA-456'\n * });\n * });\n * ```\n */\n/**\n * Declare what the file's scenarios are for, before any of them run.\n *\n * Scenarios say what the system does. This says why the feature exists and who\n * it serves, so a reader meets the intent before the examples. Call it once per\n * test file, at module scope or at the top of the outermost `describe`.\n *\n * @example\n * ```ts\n * story.feature({\n * kind: 'ability',\n * title: 'Employees can secure their passwords',\n * narrative: `\n * Weak passwords are the way most accounts get taken over, so the signup\n * form rejects them outright rather than warning and letting them through.\n * `,\n * glossary: [\n * { term: 'password strength', definition: 'A score from weak to strong, from length and dictionary matches.' },\n * ],\n * });\n * ```\n */\nfunction feature(input: FeatureInput): void {\n declaredFeature = { input };\n}\n\n/**\n * The declaration waiting to be attached to the next test that runs.\n *\n * `file` is filled in by the first `init` that claims it. With Vitest's default\n * isolation each file gets a fresh module, so this is always that file's own\n * declaration. Without isolation the module is shared, and the recorded file is\n * what stops one file's declaration from leaking into the next.\n */\nlet declaredFeature: { input: FeatureInput; file?: string } | null = null;\n\nfunction attachFeature(task: TaskLike): void {\n if (!declaredFeature) return;\n\n const file = task.file?.name;\n declaredFeature.file ??= file;\n if (declaredFeature.file !== file) return;\n\n task.meta.storyFeature = { ...declaredFeature.input, sourceFile: file };\n}\n\nfunction init(task: TaskLike, options?: StoryOptions): void {\n const meta: StoryMeta = {\n scenario: task.name,\n steps: [],\n suitePath: extractSuitePath(task),\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n covers: options?.covers,\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // OTel bridge: detect active span, flow data bidirectionally\n const traceUrlTemplate =\n options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n const otelCtx = tryGetActiveOtelContext();\n if (otelCtx) {\n // OTel -> Story: capture traceId + docs from the active span.\n applyTraceToMeta(meta, otelCtx.traceId, otelCtx.spanId, traceUrlTemplate);\n\n // Story -> OTel: enrich active span with story attributes\n try {\n const reqUrl = import.meta.url\n ?? (typeof __filename !== 'undefined' ? `file://${__filename}` : undefined);\n const req = createRequire(reqUrl!);\n const api = req('@opentelemetry/api');\n const span = api.trace?.getActiveSpan?.();\n if (span) {\n span.setAttribute('story.scenario', task.name);\n if (options?.tags?.length) span.setAttribute('story.tags', options.tags);\n if (options?.ticket) {\n const tickets = Array.isArray(options.ticket) ? options.ticket : [options.ticket];\n span.setAttribute('story.tickets', tickets.map((t) => typeof t === 'string' ? t : t.id));\n }\n }\n } catch { /* OTel not available */ }\n }\n\n // Attach to task.meta so reporter can find it\n task.meta.story = meta;\n attachFeature(task);\n\n // Set active context\n activeContext = {\n meta,\n currentStep: null,\n taskMeta: task.meta,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n traceUrlTemplate,\n pendingStep: null,\n };\n\n // The last marker step's assertions land after every step call, so the count\n // is only final at the end of the test. Registered first, so LIFO ordering\n // runs it after any cleanup the test adds, and the reporter reads the total.\n const ctx = activeContext;\n try {\n onTestFinished(() => {\n flushPendingAssertions(ctx);\n task.meta.story = ctx.meta;\n });\n } catch {\n // Outside a running test (or a framework without the hook) there is nothing\n // to flush against; wrapped steps still measure themselves precisely.\n }\n}\n\n/**\n * Close off the marker step waiting on a count.\n *\n * Left undefined when the counter was unreadable at either end: an unobserved\n * step must not be reported as a step that asserted nothing.\n */\nfunction flushPendingAssertions(ctx: StoryContext): void {\n const step = ctx.pendingStep;\n const before = ctx.pendingFrom;\n ctx.pendingStep = null;\n ctx.pendingFrom = undefined;\n if (!step || before === undefined) return;\n const after = readAssertionCount();\n if (after === undefined) return;\n step.assertions = after - before;\n}\n\n// ============================================================================\n// Step Markers\n// ============================================================================\n\n/**\n * Vitest's live per-test assertion counter, or undefined when it cannot be read.\n *\n * Undefined is not zero: it means we could not observe, and the report must not\n * report an unobserved step as one that asserted nothing.\n */\nfunction readAssertionCount(): number | undefined {\n try {\n const state = (expect as unknown as {\n getState?: () => { assertionCalls?: number };\n }).getState?.();\n return typeof state?.assertionCalls === 'number' ? state.assertionCalls : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Record how many assertions ran between `before` and now. */\nfunction recordAssertions(step: StoryStep, before: number | undefined): void {\n if (before === undefined) return;\n const after = readAssertionCount();\n if (after === undefined) return;\n step.assertions = after - before;\n}\n\n/**\n * Create a step marker function for a given keyword.\n */\nfunction createStepMarker(keyword: StepKeyword) {\n function stepMarker(text: string, docs?: StoryDocs): void;\n function stepMarker(text: string, children: DocEntry[]): void;\n function stepMarker<T>(text: string, body: () => T): T;\n function stepMarker<T>(text: string, docsOrBody?: StoryDocs | DocEntry[] | (() => T)): T | void {\n const ctx = getContext();\n const isCallback = typeof docsOrBody === 'function';\n const isChildrenArray = Array.isArray(docsOrBody);\n\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n let stepDocs: DocEntry[] = [];\n if (!isCallback && !isChildrenArray && docsOrBody) {\n stepDocs = convertStoryDocsToEntries(docsOrBody as StoryDocs);\n }\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: stepDocs,\n ...(isCallback ? { wrapped: true } : {}),\n };\n\n // Close the previous marker before this step's own assertions begin.\n flushPendingAssertions(ctx);\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n if (!isCallback) {\n ctx.pendingStep = step;\n ctx.pendingFrom = readAssertionCount();\n }\n syncMetaToTask();\n\n // Handle DocEntry[] children: attach as step docs and deduplicate from story-level\n if (isChildrenArray) {\n const children = docsOrBody as DocEntry[];\n if (children.length > 0) {\n const childSet = new Set<DocEntry>(children);\n // Deduplicate from story-level docs\n ctx.meta.docs = (ctx.meta.docs ?? []).filter((d) => !childSet.has(d));\n // Deduplicate from step docs of earlier steps\n for (const prevStep of ctx.meta.steps) {\n if (prevStep !== step && prevStep.docs) {\n prevStep.docs = prevStep.docs.filter((d) => !childSet.has(d));\n }\n }\n step.docs = [...(step.docs ?? []), ...children];\n }\n syncMetaToTask();\n return;\n }\n\n if (!isCallback) return;\n\n const body = docsOrBody as () => T;\n const start = performance.now();\n const assertionsBefore = readAssertionCount();\n\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; recordAssertions(step, assertionsBefore); syncMetaToTask(); return val; },\n (err) => { step.durationMs = performance.now() - start; recordAssertions(step, assertionsBefore); syncMetaToTask(); throw err; },\n ) as T;\n }\n step.durationMs = performance.now() - start;\n recordAssertions(step, assertionsBefore);\n syncMetaToTask();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n recordAssertions(step, assertionsBefore);\n syncMetaToTask();\n throw err;\n }\n }\n return stepMarker;\n}\n\n// ============================================================================\n// Doc Methods (Standalone)\n// ============================================================================\n\n/**\n * Add a free-text note to the current step or story-level if before any step.\n */\nfunction note(text: string, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'note', text, phase: 'runtime' }, children);\n}\n\n// ============================================================================\n// Doc Method Types (shared between standalone and inline)\n// ============================================================================\n\n/** Options for kv() - key-value pair */\ninterface KvOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for json() - JSON code block */\ninterface JsonOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for state() - data snapshot of the world at this step */\ninterface StateOptions {\n label?: string;\n value: unknown;\n}\n\n/** Options for code() - code block with optional language */\ninterface CodeOptions {\n label: string;\n content: string;\n lang?: string;\n}\n\n/** Options for table() - markdown table */\ninterface TableOptions {\n label: string;\n columns: string[];\n rows: string[][];\n}\n\n/** Options for link() - hyperlink */\ninterface LinkOptions {\n label: string;\n url: string;\n}\n\n/** Options for section() - titled markdown section */\ninterface SectionOptions {\n title: string;\n markdown: string;\n}\n\n/** Options for mermaid() - Mermaid diagram */\ninterface MermaidOptions {\n code: string;\n title?: string;\n}\n\n/** Options for screenshot() - screenshot reference */\ninterface ScreenshotOptions {\n path: string;\n alt?: string;\n}\n\n/** Options for video() - video reference */\ninterface VideoOptions {\n path: string;\n caption?: string;\n poster?: string;\n}\n\n/** Options for html() - HTML embedded in a sandboxed iframe */\ninterface HtmlOptions {\n /** Local HTML file path (inlined into the report by default) */\n path?: string;\n /** Remote URL rendered via iframe src */\n url?: string;\n /** Inline HTML content rendered via iframe srcdoc */\n content?: string;\n title?: string;\n /** Iframe height: number → px, string passed through (e.g. '60vh'). Default 400px. */\n height?: number | string;\n}\n\n/** Options for custom() - custom doc entry */\ninterface CustomOptions {\n type: string;\n data: unknown;\n}\n\n\n// ============================================================================\n// Helper to attach doc entry to current step or story-level\n// ============================================================================\n\nfunction attachDoc(entry: DocEntry, children?: DocEntry[]): DocEntry {\n const ctx = getContext();\n if (children && children.length > 0) {\n entry.children = children;\n const childSet = new Set<DocEntry>(children);\n const filterDocs = (docs: DocEntry[]) => docs.filter((d) => !childSet.has(d));\n // Remove children from ALL containers (story-level + every step)\n ctx.meta.docs = filterDocs(ctx.meta.docs ?? []);\n for (const step of ctx.meta.steps) {\n if (step.docs) step.docs = filterDocs(step.docs);\n }\n }\n if (ctx.currentStep) {\n ctx.currentStep.docs ??= [];\n ctx.currentStep.docs.push(entry);\n } else {\n ctx.meta.docs ??= [];\n ctx.meta.docs.push(entry);\n }\n syncMetaToTask();\n return entry;\n}\n\n// ============================================================================\n// Doc Methods (Standalone) - same shape as inline docs\n// ============================================================================\n\n/**\n * Add a key-value pair to the current step or story-level.\n * @example story.kv({ label: 'Payment ID', value: 'pay_123' })\n */\nfunction kv(options: KvOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'kv',\n label: options.label,\n value: options.value,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a JSON code block to the current step or story-level.\n * @example story.json({ label: 'Order', value: { id: 123 } })\n */\nfunction json(options: JsonOptions, children?: DocEntry[]): DocEntry {\n const content = JSON.stringify(options.value, null, 2);\n return attachDoc({\n kind: 'code',\n label: options.label,\n content,\n lang: 'json',\n phase: 'runtime',\n }, children);\n}\n\n/** Warn (non-fatal) when a state snapshot is suspiciously large. */\nfunction warnIfLargeState(label: string | undefined, value: unknown): void {\n try {\n const length = JSON.stringify(value)?.length ?? 0;\n if (length > 100_000) {\n console.warn(\n `[executable-stories] state \"${label ?? 'state'}\" is ${Math.round(length / 1024)}KB — consider capturing a projection of the entity instead of the whole thing`,\n );\n }\n } catch {\n /* non-serializable value; still recorded */\n }\n}\n\n/**\n * Capture what the world looks like at this step — a data snapshot that\n * becomes a storyboard frame in generated docs. Consecutive snapshots with\n * the same label get diffed downstream. The value must be JSON-serializable.\n * @example\n * ```ts\n * story.given('an empty basket');\n * story.state({ label: 'Basket', value: { items: [], total: 0 } });\n * story.when('a widget is added');\n * story.state({ label: 'Basket', value: { items: ['widget'], total: 49.99 } });\n * ```\n */\nfunction state(options: StateOptions, children?: DocEntry[]): DocEntry {\n warnIfLargeState(options.label, options.value);\n return attachDoc({\n kind: 'state',\n label: options.label,\n value: options.value,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a code block with optional language to the current step or story-level.\n * @example story.code({ label: 'Config', content: 'port: 3000', lang: 'yaml' })\n */\nfunction code(options: CodeOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'code',\n label: options.label,\n content: options.content,\n lang: options.lang,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a markdown table to the current step or story-level.\n * @example story.table({ label: 'Users', columns: ['Name', 'Role'], rows: [['Alice', 'Admin']] })\n */\nfunction table(options: TableOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'table',\n label: options.label,\n columns: options.columns,\n rows: options.rows,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a hyperlink to the current step or story-level.\n * @example story.link({ label: 'API Docs', url: 'https://docs.example.com' })\n */\nfunction link(options: LinkOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'link',\n label: options.label,\n url: options.url,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a titled section with markdown content to the current step or story-level.\n * @example story.section({ title: 'Details', markdown: 'This is **important**' })\n */\nfunction section(options: SectionOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'section',\n title: options.title,\n markdown: options.markdown,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a Mermaid diagram to the current step or story-level.\n * @example story.mermaid({ code: 'graph LR; A-->B', title: 'Flow' })\n */\nfunction mermaid(options: MermaidOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'mermaid',\n code: options.code,\n title: options.title,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a screenshot reference to the current step or story-level.\n * @example story.screenshot({ path: '/screenshots/result.png', alt: 'Final result' })\n */\nfunction screenshot(options: ScreenshotOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'screenshot',\n path: options.path,\n alt: options.alt,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a video reference to the current step or story-level.\n * @example story.video({ path: '/videos/run.mp4', caption: 'Full run', poster: '/videos/run.jpg' })\n */\nfunction video(options: VideoOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'video',\n path: options.path,\n caption: options.caption,\n poster: options.poster,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Embed HTML in a sandboxed iframe in the current step or story-level docs.\n * Exactly one of path/url/content is required. Local files are inlined into\n * the report by default so it stays self-contained — the HTML must therefore\n * be self-contained too (use your tool's single-file mode); relative\n * sub-asset references won't resolve.\n * @example story.html({ path: './coverage/index.html', title: 'Coverage' })\n * @example story.html({ url: 'https://dash.example.com/run/42', height: 600 })\n * @example story.html({ content: chartHtml, title: 'Latency chart' })\n */\nfunction html(options: HtmlOptions, children?: DocEntry[]): DocEntry {\n return attachDoc(buildHtmlDocEntry(options), children);\n}\n\n/**\n * Add tag(s) to the current step or story-level.\n * @example story.tag('admin') or story.tag(['admin', 'security'])\n */\nfunction tag(name: string | string[], children?: DocEntry[]): DocEntry {\n const names = Array.isArray(name) ? name : [name];\n return attachDoc({ kind: 'tag', names, phase: 'runtime' }, children);\n}\n\n/**\n * Add a custom documentation entry for use with custom renderers.\n * @example story.custom({ type: 'myType', data: { foo: 'bar' } })\n */\nfunction custom(options: CustomOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'custom',\n type: options.type,\n data: options.data,\n phase: 'runtime',\n }, children);\n}\n\n// ============================================================================\n// Attachments\n// ============================================================================\n\n/**\n * Attach a file or inline content to the current step or test case.\n * @example story.attach({ name: 'screenshot', mediaType: 'image/png', path: '/tmp/screenshot.png' })\n */\nfunction attach(options: AttachmentOptions): void {\n const ctx = getContext();\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.attachments.push({\n ...options,\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n });\n // Store attachments on task.meta so reporter can read them\n if (ctx.taskMeta) {\n ctx.taskMeta.storyAttachments = ctx.attachments;\n }\n}\n\n// ============================================================================\n// Step Timing\n// ============================================================================\n\n/**\n * Start a timer for the current step. Returns a token to pass to endTimer().\n */\nfunction startTimer(): number {\n const ctx = getContext();\n const token = ctx.timerCounter++;\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.activeTimers.set(token, {\n start: performance.now(),\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n consumed: false,\n });\n return token;\n}\n\n/**\n * End a timer and record duration on the step that was active when startTimer() was called.\n */\nfunction endTimer(token: number): void {\n const ctx = getContext();\n const entry = ctx.activeTimers.get(token);\n if (!entry || entry.consumed) return;\n\n entry.consumed = true;\n const durationMs = performance.now() - entry.start;\n\n let step: StoryStep | undefined;\n if (entry.stepId) {\n step = ctx.meta.steps.find((s) => s.id === entry.stepId);\n }\n if (!step && entry.stepIndex !== undefined) {\n step = ctx.meta.steps[entry.stepIndex];\n }\n\n if (step) {\n step.durationMs = durationMs;\n syncMetaToTask();\n }\n}\n\n// ============================================================================\n// Step Wrappers: story.fn() and story.expect()\n// ============================================================================\n\n/**\n * Wrap a function body as a step. Records the step with timing and `wrapped: true`.\n * Supports both sync and async functions. Returns whatever the function returns.\n *\n * @param keyword - The BDD keyword (Given, When, Then, And, But)\n * @param text - Step description\n * @param body - The function to execute\n * @returns The return value of body (or a Promise of it if body is async)\n *\n * @example\n * ```ts\n * const data = story.fn('Given', 'setup data', () => ({ a: 5, b: 3 }));\n * const result = await story.fn('When', 'call API', async () => fetch('/api'));\n * ```\n */\nfunction fn<T>(keyword: StepKeyword, text: string, body: () => T): T {\n const ctx = getContext();\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: [],\n wrapped: true,\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncMetaToTask();\n\n const start = performance.now();\n\n try {\n const result = body();\n\n // Handle async functions\n if (result instanceof Promise) {\n return result.then(\n (val) => {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n return val;\n },\n (err) => {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n throw err;\n },\n ) as T;\n }\n\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n throw err;\n }\n}\n\n/**\n * Wrap an assertion as a Then step. Shorthand for `story.fn('Then', text, body)`.\n *\n * @param text - Step description\n * @param body - The assertion function to execute\n *\n * @example\n * ```ts\n * story.expect('the result is 8', () => { expect(result).toBe(8); });\n * await story.expect('async check', async () => { ... });\n * ```\n */\nfunction storyExpect<T>(text: string, body: () => T): T {\n return fn('Then', text, body);\n}\n\n// ============================================================================\n// Span attachment: story.attachSpans()\n// ============================================================================\n\n/**\n * Attach OTel spans to the current test so the StoryReporter renders them\n * as a trace waterfall in HTML reports.\n *\n * Accepts any array of objects with at least `spanId` and `name` fields.\n * Structurally compatible with autotel's `SerializedSpan` and the\n * `OtelSpan` type from executable-stories-formatters.\n *\n * Pass `traceId` (and optionally `spanId`) when the trace was captured *after*\n * `story.init()` ran — e.g. the test wraps the work in its own root span. The\n * init-time OTel bridge can't see a trace that doesn't exist yet, so this is\n * where the \"View Trace\" link and trace badge get wired (using the\n * `traceUrlTemplate` from init).\n *\n * @example\n * ```ts\n * import { serializeSpan } from 'autotel/test-span-collector';\n *\n * // After running code that creates spans:\n * const spans = exporter.getFinishedSpans().map(serializeSpan);\n * story.attachSpans(spans, { traceId, spanId });\n * ```\n */\nfunction attachSpans(\n spans: ReadonlyArray<Record<string, unknown>>,\n options?: { traceId?: string; spanId?: string },\n): void {\n const ctx = getContext();\n if (ctx.taskMeta) {\n ctx.taskMeta.otelSpans = spans;\n }\n if (options?.traceId) {\n applyTraceToMeta(\n ctx.meta,\n options.traceId,\n options.spanId,\n ctx.traceUrlTemplate,\n );\n syncMetaToTask();\n }\n}\n\n// ============================================================================\n// Export story object\n// ============================================================================\n\n/**\n * The main story API object.\n *\n * Use with native Vitest describe/it/test for full IDE support:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n *\n * story.given('two numbers 5 and 3');\n * const a = 5, b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\nexport const story = {\n // Core\n init,\n feature,\n\n // BDD step markers\n given: createStepMarker('Given'),\n when: createStepMarker('When'),\n then: createStepMarker('Then'),\n and: createStepMarker('And'),\n but: createStepMarker('But'),\n\n // AAA pattern aliases\n arrange: createStepMarker('Given'),\n act: createStepMarker('When'),\n assert: createStepMarker('Then'),\n\n // Additional aliases\n setup: createStepMarker('Given'),\n context: createStepMarker('Given'),\n execute: createStepMarker('When'),\n action: createStepMarker('When'),\n verify: createStepMarker('Then'),\n\n // Standalone doc methods\n note,\n kv,\n json,\n state,\n code,\n table,\n link,\n section,\n mermaid,\n screenshot,\n video,\n html,\n tag,\n custom,\n\n // Attachments\n attach,\n\n // OTel span attachment\n attachSpans,\n\n // Step wrappers\n fn,\n expect: storyExpect,\n\n // Step timing\n startTimer,\n endTimer,\n};\n\nexport type Story = typeof story;\n","/**\n * Type definitions for executable-stories-vitest.\n *\n * Shared story types (StepKeyword, DocEntry, StoryStep, StoryMeta, etc.)\n * are imported from executable-stories-core — the single source of truth.\n * This module re-exports them and adds Vitest-specific types.\n */\n\n// Re-export shared story types from core\nexport type {\n StepKeyword,\n StepMode,\n DocPhase,\n DocEntry,\n StoryStep,\n StoryMeta,\n NormalizedTicket,\n} from 'executable-stories-core/types/story';\n\nexport { STORY_META_KEY } from 'executable-stories-core/types/story';\n\n// ============================================================================\n// Ticket Input Types\n// ============================================================================\n\n/** A ticket reference: either a plain string ID or an object with id and optional url */\nexport type TicketInput = string | { id: string; url?: string };\n\n// ============================================================================\n// Vitest-specific Types\n// ============================================================================\n\n/**\n * Inline documentation options for step markers.\n * Pass to story.given(), story.when(), story.then() as second argument.\n *\n * @example\n * ```ts\n * story.given('valid credentials', {\n * json: { label: 'Credentials', value: { email: 'test@example.com', password: '***' } },\n * note: 'Password is masked for security'\n * });\n * ```\n */\nexport interface StoryDocs {\n /** Add a free-text note */\n note?: string;\n /** Add tag(s) for categorization */\n tag?: string | string[];\n /** Add key-value pairs */\n kv?: Record<string, unknown>;\n /** Add a code block with label and optional language */\n code?: { label: string; content: string; lang?: string };\n /** Add a JSON data block with label */\n json?: { label: string; value: unknown };\n /** Capture a data snapshot of the world at this step (storyboard frame) */\n state?: { label?: string; value: unknown };\n /** Add a markdown table with label */\n table?: { label: string; columns: string[]; rows: string[][] };\n /** Add a hyperlink */\n link?: { label: string; url: string };\n /** Add a titled section with markdown content */\n section?: { title: string; markdown: string };\n /** Add a Mermaid diagram with optional title */\n mermaid?: { code: string; title?: string };\n /** Add a screenshot reference */\n screenshot?: { path: string; alt?: string };\n /** Add a video reference with optional caption and poster image */\n video?: { path: string; caption?: string; poster?: string };\n /** Embed HTML in a sandboxed iframe. Exactly one of path/url/content. */\n html?: { path?: string; url?: string; content?: string; title?: string; height?: number | string };\n /** Add a custom documentation entry */\n custom?: { type: string; data: unknown };\n}\n\n/**\n * Options for configuring a story via story.init().\n *\n * @example\n * ```ts\n * it('admin deletes user', ({ task }) => {\n * story.init(task, {\n * tags: ['admin', 'destructive'],\n * ticket: 'JIRA-456'\n * });\n * });\n * ```\n */\n/** One entry in a feature's glossary. */\nexport interface GlossaryTerm {\n /** The term as it appears in scenario and step text. */\n term: string;\n /** What it means, in one or two sentences. */\n definition: string;\n}\n\n/** What {@link Story.feature} accepts. */\nexport interface FeatureInput {\n /** Heading for the feature. */\n title: string;\n /**\n * How to introduce it. `ability` frames the feature as something a person can\n * now do; `business-need` covers cross-cutting concerns like security or\n * performance that no single user asks for. Defaults to `feature`.\n */\n kind?: \"feature\" | \"ability\" | \"business-need\";\n /** Markdown explaining why the feature exists and who it serves. */\n narrative?: string;\n /** Tags applied to every scenario in the file. */\n tags?: string[];\n /** Terms this feature defines, linked wherever they appear in the report. */\n glossary?: GlossaryTerm[];\n}\n\nexport interface StoryOptions {\n /** Tags for filtering and categorizing stories */\n tags?: string[];\n /** Ticket/issue reference(s) for requirements traceability */\n ticket?: TicketInput | TicketInput[];\n /** Product-code paths/globs this scenario exercises (project-root-relative). */\n covers?: string[];\n /** Arbitrary user-defined metadata */\n meta?: Record<string, unknown>;\n /** URL template for OTel trace links. Uses {traceId} placeholder. Also settable via OTEL_TRACE_URL_TEMPLATE env var. */\n traceUrlTemplate?: string;\n}\n\n// ============================================================================\n// Vitest Task Type (minimal interface)\n// ============================================================================\n\n/** Minimal Vitest suite interface for suite path extraction */\nexport interface VitestSuite {\n /** Suite name */\n name?: string;\n /** Parent suite (optional) */\n suite?: VitestSuite;\n}\n\n/**\n * Minimal Vitest task interface for story.init().\n * This is the { task } from it('name', ({ task }) => { ... }).\n *\n * Uses generic type parameter to be compatible with Vitest's actual TaskMeta type.\n */\nexport interface VitestTask<TMeta = Record<string, unknown>> {\n /** The test/task name */\n name: string;\n /** Task metadata object where we store story data */\n meta: TMeta;\n /** Parent suite (optional) */\n suite?: VitestSuite;\n /** The test file (optional) */\n file?: { name?: string };\n}\n","/**\n * Vitest reporter that reads task.meta.story from tests and writes reports\n * using the executable-stories-formatters package.\n *\n * Do not add value imports from \"vitest\" or \"./story-api.js\" here; this entry is loaded in vitest.config\n * before Vitest is ready. Use only `import type` from those modules.\n *\n * Type strategy: this reporter is duck-typed against vitest's runner contract,\n * not nominally typed via `implements Reporter`. That keeps it compatible with\n * any vitest-compatible runner (including forks like vite-plus) where the\n * `Vitest` class type carries different private fields and would otherwise\n * fail nominal assignability checks at the consumer's call site.\n *\n * Vitest types are imported only for parameter typing of internal helpers,\n * never as `implements`. Anything we touch on `ctx` is captured by the local\n * `VitestContext` structural type below.\n */\nimport type {\n SerializedError,\n TestModule,\n TestCase,\n TestRunEndReason,\n} from \"vitest/node\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"./types\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n sendNotifications,\n toCIInfo,\n loadHistory,\n updateHistory,\n saveHistory,\n type RawFeature,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n type CoverageSummary,\n type OtelSpan,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Structural runner context\n// ----------------------------------------------------------------------------\n// We only read `ctx.config.root`. Avoid importing the full `Vitest` class as\n// a parameter type — its private fields (`_clearScreenPending`, etc.) make\n// nominal assignability fail across vitest forks. A structural type lets the\n// reporter accept any vitest-compatible runner.\n// ============================================================================\n\nexport type VitestContext = {\n config?: {\n root?: string;\n /**\n * Set by `vitest -t` / `--testNamePattern`. Vitest reports only the tests\n * that match, so a run carrying one cannot speak for the rest of a file.\n */\n testNamePattern?: string | RegExp;\n };\n};\n\n// ============================================================================\n// Reporter Protocol (duck-typed interface for reporters)\n// ============================================================================\n// Exported protocol that captures the reporter contract. This allows\n// StoryReporter to be properly typed without nominal `implements Reporter`,\n// enabling duck-typing compatibility across vitest forks while maintaining\n// type safety at the consumer call site.\n// ============================================================================\n\n// Public-facing protocol uses loose params on runner-supplied callbacks so the\n// returned reporter is structurally assignable to any vitest-compatible runner's\n// `Reporter` type (vitest, vite-plus, future forks). The concrete StoryReporter\n// class still uses strict vitest types internally — method bivariance lets the\n// narrower class satisfy this looser protocol.\nexport interface StoryReporterProtocol {\n onInit(ctx: VitestContext): void;\n onCoverage(coverage: unknown): void;\n onTestRunEnd(\n testModules: readonly unknown[],\n unhandledErrors: readonly unknown[],\n reason: unknown,\n ): Promise<void>;\n}\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** When GITHUB_ACTIONS, append report to job summary via @actions/core. Default: true */\n enableGithubActionsSummary?: boolean;\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Coverage Types\n// ============================================================================\n\ntype CoverageMetric = { total: number; covered: number; pct: number };\ntype CoverageData = {\n statements: CoverageMetric;\n branches: CoverageMetric;\n functions: CoverageMetric;\n lines?: CoverageMetric;\n};\ntype CoverageFile = {\n s: Record<string, number>;\n f: Record<string, number>;\n b: Record<string, number[]>;\n l?: Record<string, number>;\n};\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Normalize Vitest onCoverage payload to coverage data.\n */\nfunction normalizeCoveragePayload(\n coverage: unknown\n): Record<string, CoverageFile> | undefined {\n if (!coverage || typeof coverage !== \"object\" || Array.isArray(coverage))\n return undefined;\n const raw = coverage as Record<string, unknown>;\n const data: Record<string, CoverageFile> = {};\n for (const [filePath, file] of Object.entries(raw)) {\n if (\n file &&\n typeof file === \"object\" &&\n \"s\" in file &&\n \"f\" in file &&\n \"b\" in file\n ) {\n data[filePath] = file as CoverageFile;\n }\n }\n if (Object.keys(data).length === 0) return undefined;\n return data;\n}\n\n/**\n * Summarize coverage data.\n */\nfunction summarizeCoverage(\n data: Record<string, CoverageFile>\n): CoverageData | undefined {\n let statementsTotal = 0;\n let statementsCovered = 0;\n let functionsTotal = 0;\n let functionsCovered = 0;\n let branchesTotal = 0;\n let branchesCovered = 0;\n let linesTotal = 0;\n let linesCovered = 0;\n let hasLines = false;\n\n for (const file of Object.values(data)) {\n for (const count of Object.values(file.s)) {\n statementsTotal += 1;\n if (count > 0) statementsCovered += 1;\n }\n for (const count of Object.values(file.f)) {\n functionsTotal += 1;\n if (count > 0) functionsCovered += 1;\n }\n for (const counts of Object.values(file.b)) {\n for (const count of counts) {\n branchesTotal += 1;\n if (count > 0) branchesCovered += 1;\n }\n }\n if (file.l) {\n hasLines = true;\n for (const count of Object.values(file.l)) {\n linesTotal += 1;\n if (count > 0) linesCovered += 1;\n }\n }\n }\n\n if (\n statementsTotal === 0 &&\n functionsTotal === 0 &&\n branchesTotal === 0 &&\n !hasLines\n ) {\n return undefined;\n }\n\n const metric = (covered: number, total: number): CoverageMetric => ({\n total,\n covered,\n pct: total === 0 ? 100 : Math.round((covered / total) * 100),\n });\n\n const summary: CoverageData = {\n statements: metric(statementsCovered, statementsTotal),\n branches: metric(branchesCovered, branchesTotal),\n functions: metric(functionsCovered, functionsTotal),\n };\n if (hasLines) {\n summary.lines = metric(linesCovered, linesTotal);\n }\n return summary;\n}\n\n/**\n * Convert internal coverage data to formatters CoverageSummary.\n */\nfunction toCoverageSummary(\n data: CoverageData | undefined\n): CoverageSummary | undefined {\n if (!data) return undefined;\n return {\n statementsPct: data.statements.pct,\n branchesPct: data.branches.pct,\n functionsPct: data.functions.pct,\n linesPct: data.lines?.pct,\n };\n}\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\n/**\n * Vitest reporter that generates reports using executable-stories-formatters.\n *\n * Reads `task.meta.story` from each test and generates reports in configured formats.\n * Supports output routing (aggregated/colocated) and multiple output formats.\n *\n * Implements StoryReporterProtocol for type-safe duck-typing across vitest forks.\n */\nexport default class StoryReporter implements StoryReporterProtocol {\n private options: StoryReporterOptions;\n private ctx: VitestContext | undefined;\n private startTime: number = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n /**\n * Left unknown until onInit sees a config. Claiming full coverage without\n * having looked would let a later merge retire scenarios on a guess.\n */\n private runScope: \"full\" | \"filtered\" | undefined;\n private coverageByFile: Record<string, CoverageFile> = {};\n\n constructor(options: StoryReporterOptions = {}) {\n this.options = options;\n }\n\n onInit(ctx: VitestContext): void {\n this.ctx = ctx;\n this.startTime = Date.now();\n const root = ctx.config?.root ?? process.cwd();\n // `vitest -t` reports the matching tests and nothing else, so consumers\n // must not read this run as the full contents of the files it touches.\n // Vitest hands us the pattern either way, so the answer is never a guess.\n if (ctx.config) {\n this.runScope = ctx.config.testNamePattern != null ? \"filtered\" : \"full\";\n }\n\n // Read metadata if needed\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(root);\n this.gitSha = readGitSha(root);\n }\n }\n\n onCoverage(coverage: unknown): void {\n const data = normalizeCoveragePayload(coverage);\n if (data) {\n this.coverageByFile = { ...this.coverageByFile, ...data };\n }\n }\n\n async onTestRunEnd(\n testModules: ReadonlyArray<TestModule>,\n _unhandledErrors: ReadonlyArray<SerializedError>,\n reason: TestRunEndReason\n ): Promise<void> {\n if (reason === \"interrupted\") return;\n\n const root = this.ctx?.config?.root ?? process.cwd();\n\n // Collect test cases\n const rawTestCases = this.collectTestCases(testModules, root);\n const coveredSourceFiles = this.collectCoveredSourceFiles(testModules, root);\n const incompleteSourceFiles = this.collectIncompleteSourceFiles(testModules, root);\n const features = collectFeatures(testModules, root);\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n ...(features.length > 0 ? { features } : {}),\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: root,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n ...(this.runScope ? { runScope: this.runScope } : {}),\n ...(coveredSourceFiles.length > 0 ? { coveredSourceFiles } : {}),\n ...(incompleteSourceFiles.length > 0 ? { incompleteSourceFiles } : {}),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n try {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(root, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n } catch (err) {\n console.error(\"Failed to write raw run JSON:\", err);\n }\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Add coverage if available\n const coverageData = summarizeCoverage(this.coverageByFile);\n if (coverageData) {\n canonicalRun.coverage = toCoverageSummary(coverageData);\n }\n\n // 1. Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n const results = await generator.generate(canonicalRun);\n\n // Append to GitHub Actions summary if enabled\n const enableGithubSummary = this.options.enableGithubActionsSummary ?? true;\n if (process.env.GITHUB_ACTIONS === \"true\" && enableGithubSummary) {\n const markdownPaths = results.get(\"markdown\") ?? [];\n if (markdownPaths.length > 0) {\n const firstPath = markdownPaths[0];\n const content = fs.readFileSync(firstPath, \"utf8\");\n await this.appendGithubSummary(content).catch(() => {});\n }\n }\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n\n // 2. Update history (independent of report generation)\n try {\n const histOpts = this.options.history;\n if (histOpts?.filePath) {\n const historyPath = path.isAbsolute(histOpts.filePath)\n ? histOpts.filePath\n : path.join(root, histOpts.filePath);\n const store = loadHistory(\n { filePath: historyPath },\n {\n readFile: (p: string) => { try { return fs.readFileSync(p, \"utf8\"); } catch { return undefined; } },\n logger: console,\n },\n );\n const updated = updateHistory({ store, run: canonicalRun, maxRuns: histOpts.maxRuns ?? 10 });\n const dir = path.dirname(historyPath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n saveHistory(\n { filePath: historyPath, store: updated },\n { writeFile: (p: string, c: string) => fs.writeFileSync(p, c, \"utf8\") },\n );\n }\n } catch (err) {\n console.error(\"Failed to update history:\", err);\n }\n\n // 3. Send notifications (independent of both above)\n try {\n if (this.options.notification) {\n await sendNotifications(\n { run: canonicalRun, notification: this.options.notification },\n { fetch: globalThis.fetch, logger: console, toCIInfo },\n );\n }\n } catch (err) {\n console.error(\"Failed to send notifications:\", err);\n }\n }\n\n /**\n * Collect test cases from Vitest test modules.\n */\n /**\n * Every module vitest ran, whether or not it produced a story.\n *\n * The collected test cases only name modules that produced something, so a\n * file whose last story was deleted would otherwise look like a file that did\n * not run and keep its old scenarios for good.\n */\n private collectCoveredSourceFiles(\n testModules: ReadonlyArray<TestModule>,\n root: string\n ): string[] {\n const files = new Set<string>();\n for (const mod of testModules) {\n const moduleId =\n mod.moduleId ?? (mod as { relativeModuleId?: string }).relativeModuleId ?? \"\";\n if (!moduleId) continue;\n const absolute = path.isAbsolute(moduleId) ? moduleId : path.resolve(root, moduleId);\n files.add(toRelativePosix(absolute, root));\n }\n return [...files].sort();\n }\n\n /**\n * Files whose scenarios could not be collected in full.\n *\n * A module that errored, or a test that failed without ever declaring a\n * story, is missing scenarios because the run broke. Treating that as\n * deletion would throw away documentation at the exact moment the suite is\n * unhealthy. Checked per test, not per file: one healthy story in a module\n * does not vouch for its broken siblings.\n */\n private collectIncompleteSourceFiles(\n testModules: ReadonlyArray<TestModule>,\n root: string\n ): string[] {\n const incomplete = new Set<string>();\n for (const mod of testModules) {\n const moduleId =\n mod.moduleId ?? (mod as { relativeModuleId?: string }).relativeModuleId ?? \"\";\n if (!moduleId) continue;\n const absolute = path.isAbsolute(moduleId) ? moduleId : path.resolve(root, moduleId);\n const sourceFile = toRelativePosix(absolute, root);\n\n const errors = (mod as { errors?: () => unknown[] }).errors?.() ?? [];\n const collection = mod.children;\n if (errors.length > 0 || !collection) {\n const state = (mod as { state?: () => string }).state?.();\n if (errors.length > 0 || state === \"failed\") incomplete.add(sourceFile);\n continue;\n }\n\n for (const test of collection.allTests()) {\n const meta = this.getStoryMeta(test);\n if (meta?.scenario && Array.isArray(meta.steps)) continue;\n if (test.options?.mode === \"todo\") continue;\n if (test.result?.()?.state === \"failed\") {\n incomplete.add(sourceFile);\n break;\n }\n }\n }\n return [...incomplete].sort();\n }\n\n private collectTestCases(\n testModules: ReadonlyArray<TestModule>,\n root: string\n ): RawTestCase[] {\n const testCases: RawTestCase[] = [];\n\n for (const mod of testModules) {\n const collection = mod.children;\n if (!collection) continue;\n\n const moduleId =\n mod.moduleId ??\n (mod as { relativeModuleId?: string }).relativeModuleId ??\n \"\";\n const absoluteModuleId = path.isAbsolute(moduleId)\n ? moduleId\n : path.resolve(root, moduleId);\n const sourceFile = toRelativePosix(absoluteModuleId, root);\n\n // Bodyless `it.todo(...)` never runs `story.init()`, so those tests have\n // no story meta. Collect them separately and emit them as planned\n // scenarios — but only for modules that contain story tests, so plain\n // (non-story) suites full of todos don't leak into the generated docs.\n const plannedTests: TestCase[] = [];\n let moduleHasStoryTests = false;\n\n for (const test of collection.allTests()) {\n const meta = this.getStoryMeta(test);\n if (!meta?.scenario || !Array.isArray(meta.steps)) {\n if (test.options?.mode === \"todo\") plannedTests.push(test);\n continue;\n }\n moduleHasStoryTests = true;\n\n const result = test.result?.();\n const state = result?.state ?? \"pending\";\n const durationMs =\n typeof (result as { duration?: number } | undefined)?.duration ===\n \"number\"\n ? (result as unknown as { duration: number }).duration\n : 0;\n\n // Get error details\n let errorMessage: string | undefined;\n let errorStack: string | undefined;\n if (state === \"failed\" && result) {\n const errors = (result as { errors?: SerializedError[] }).errors;\n if (errors?.length) {\n const err = errors[0];\n errorMessage = err.message;\n errorStack = err.stack;\n }\n }\n\n // Map Vitest state to raw status\n const statusMap: Record<string, string> = {\n passed: \"pass\",\n failed: \"fail\",\n skipped: \"skip\",\n pending: \"pending\",\n todo: \"pending\",\n };\n\n // Extract attachments from task.meta.storyAttachments\n const taskMeta = test.meta() as Record<string, unknown>;\n const scopedAttachments = (taskMeta?.storyAttachments ?? []) as Array<{\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n stepIndex?: number;\n stepId?: string;\n }>;\n const attachments: RawAttachment[] = scopedAttachments.map((a) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: a.body,\n encoding: a.encoding,\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing) from story steps\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number }, i: number) => ({\n index: i,\n title: (s as { text: string }).text,\n durationMs: s.durationMs,\n }));\n\n // Read autotel OTel spans from task.meta.otelSpans\n const otelSpans = taskMeta?.otelSpans;\n if (Array.isArray(otelSpans) && otelSpans.length > 0) {\n const valid = otelSpans.filter(\n (s: unknown): s is OtelSpan =>\n s != null &&\n typeof s === \"object\" &&\n typeof (s as Record<string, unknown>).spanId === \"string\" &&\n typeof (s as Record<string, unknown>).name === \"string\",\n );\n if (valid.length > 0) {\n meta.otelSpans = valid;\n }\n }\n\n // Retry info from Vitest result\n const retryCount = (result as { retryCount?: number } | undefined)?.retryCount ?? 0;\n const configuredRetries = Math.max(\n retryCount,\n (test as { retries?: number }).retries ??\n (test as { options?: { retry?: number } }).options?.retry ??\n 0,\n );\n\n testCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath\n ? [...meta.suitePath, meta.scenario]\n : [meta.scenario],\n story: meta,\n sourceFile,\n sourceLine: Math.max(1, meta.sourceOrder ?? 1),\n status: (statusMap[state] ?? \"unknown\") as RawTestCase[\"status\"],\n durationMs,\n error: errorMessage\n ? { message: errorMessage, stack: errorStack }\n : undefined,\n attachments: attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: retryCount,\n retries: configuredRetries,\n });\n }\n\n if (moduleHasStoryTests) {\n for (const test of plannedTests) {\n const titlePath = test.fullName.split(\" > \");\n testCases.push({\n title: test.name,\n titlePath,\n story: {\n scenario: test.name,\n steps: [],\n suitePath: titlePath.slice(0, -1),\n },\n sourceFile,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n });\n }\n }\n }\n\n return testCases;\n }\n\n private getStoryMeta(test: TestCase): StoryMeta | undefined {\n const meta = test.meta() as Record<string, unknown>;\n return meta?.[\"story\"] as StoryMeta | undefined;\n }\n\n private async appendGithubSummary(reportText: string): Promise<void> {\n try {\n const { summary } = await import(\"@actions/core\");\n summary.addRaw(reportText);\n await summary.write();\n } catch {\n // @actions/core not available or not in Actions\n }\n }\n}\n\n// ============================================================================\n// Factory Function (recommended API)\n// ============================================================================\n// Type-safe factory for creating story reporters. Returns a properly-typed\n// instance that implements StoryReporterProtocol, enabling clean usage in\n// vite.config.ts without type casts.\n//\n// Usage:\n// import { createStoryReporter } from 'executable-stories-vitest/reporter';\n// export default defineConfig({\n// test: {\n// reporters: [\n// 'default',\n// createStoryReporter({\n// formats: ['html', 'markdown'],\n// outputDir: 'reports',\n// }),\n// ],\n// },\n// });\n// ============================================================================\n\nexport function createStoryReporter(\n options?: StoryReporterOptions\n): StoryReporterProtocol {\n return new StoryReporter(options);\n}\n\nexport { StoryReporter };\n\n/**\n * Read the feature declarations tests attached to their metadata.\n *\n * `story.feature(...)` runs in the worker, so the declaration travels on\n * `task.meta` like everything else. Every test in a file carries the same one;\n * keying by the module's path collapses them back to one per file.\n */\nfunction collectFeatures(\n testModules: ReadonlyArray<TestModule>,\n root: string\n): RawFeature[] {\n const byFile = new Map<string, RawFeature>();\n\n for (const mod of testModules) {\n const collection = mod.children;\n if (!collection) continue;\n\n const moduleId =\n mod.moduleId ?? (mod as { relativeModuleId?: string }).relativeModuleId ?? \"\";\n const absoluteModuleId = path.isAbsolute(moduleId)\n ? moduleId\n : path.resolve(root, moduleId);\n const sourceFile = toRelativePosix(absoluteModuleId, root);\n\n for (const test of collection.allTests()) {\n const declared = (test.meta() as { storyFeature?: RawFeature } | undefined)?.storyFeature;\n if (declared?.title) {\n byFile.set(sourceFile, { ...declared, sourceFile });\n break;\n }\n }\n }\n\n return [...byFile.values()];\n}\n"],"mappings":"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","names":["StoryReporter","path","state","state","StoryReporter"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/story-api.ts","../src/types.ts","../src/reporter.ts"],"sourcesContent":["/**\n * executable-stories-vitest: Native Vitest story/given/when/then with Markdown doc generation.\n *\n * Uses native Vitest describe/it/test for full IDE support:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n *\n * story.given('two numbers 5 and 3');\n * const a = 5, b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n *\n * In vitest.config, use createStoryReporter factory from \"executable-stories-vitest/reporter\":\n *\n * @example\n * ```ts\n * import { defineConfig } from \"vitest/config\";\n * import { createStoryReporter } from \"executable-stories-vitest/reporter\";\n *\n * export default defineConfig({\n * test: {\n * reporters: [\"default\", createStoryReporter()],\n * },\n * });\n * ```\n *\n * The factory provides type-safe reporter creation with no type casts.\n */\n\n// Core API\nexport { story, type Story } from './story-api';\n\n// Types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n FeatureInput,\n GlossaryTerm,\n StepKeyword,\n StepMode,\n DocPhase,\n NormalizedTicket,\n TicketInput,\n StoryDocs,\n StoryOptions,\n VitestTask,\n VitestSuite,\n} from './types';\n\nexport { STORY_META_KEY } from './types';\n\n// Reporter types and factory (actual reporter is in /reporter subpath)\nexport type {\n StoryReporterOptions,\n StoryReporterProtocol,\n VitestContext,\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from './reporter';\nexport { createStoryReporter } from './reporter';\n\nconst STORY_REPORTER_GUARD_MSG =\n 'Do not import StoryReporter from \"executable-stories-vitest\". In vitest.config, import it from \"executable-stories-vitest/reporter\".';\n\n/** @internal Guard: throws if used. Import StoryReporter from \"executable-stories-vitest/reporter\" in vitest.config. */\nexport class StoryReporter {\n static __isGuard = true;\n constructor() {\n throw new Error(STORY_REPORTER_GUARD_MSG);\n }\n}\n","/**\n * story.* API for executable-stories-vitest.\n *\n * Uses native Vitest describe/it/test with opt-in documentation:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n *\n * story.given('two numbers 5 and 3');\n * const a = 5;\n * const b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\n\nimport { expect, onTestFinished } from 'vitest';\nimport { createRequire } from 'node:module';\nimport { tryGetActiveOtelContext, resolveTraceUrl } from 'executable-stories-core/utils/otel-detect';\nimport { buildHtmlDocEntry } from 'executable-stories-core/utils/doc-builders';\nimport type {\n DocEntry,\n FeatureInput,\n NormalizedTicket,\n StepKeyword,\n StoryDocs,\n StoryMeta,\n StoryOptions,\n StoryStep,\n TicketInput,\n VitestSuite,\n} from './types';\n\n// ============================================================================\n// Task Interface (compatible with Vitest's actual task type)\n// ============================================================================\n\n/**\n * Minimal task interface compatible with Vitest's Test type.\n * The meta property accepts any object type to be compatible with Vitest's TaskMeta.\n */\ninterface TaskLike {\n name: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n meta: any;\n suite?: VitestSuite;\n file?: { name?: string };\n}\n\n// ============================================================================\n// Story Context\n// ============================================================================\n\n/** Attachment options for story.attach() */\nexport interface AttachmentOptions {\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n}\n\n/** Internal: attachment with step scope */\ninterface ScopedAttachment extends AttachmentOptions {\n stepIndex?: number;\n stepId?: string;\n}\n\n/** Internal timer entry */\ninterface TimerEntry {\n start: number;\n stepIndex?: number;\n stepId?: string;\n consumed: boolean;\n}\n\ninterface StoryContext {\n /** The story metadata being built */\n meta: StoryMeta;\n /** The current step (for attaching docs) */\n currentStep: StoryStep | null;\n /** Reference to task.meta for updates */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n taskMeta: any;\n /** Deterministic step counter (resets per test case) */\n stepCounter: number;\n /** Collected attachments with step scope */\n attachments: ScopedAttachment[];\n /** Active timers keyed by token */\n activeTimers: Map<number, TimerEntry>;\n /** Monotonic timer token counter */\n timerCounter: number;\n /** Trace-link URL template, captured at init for later use by attachSpans. */\n traceUrlTemplate?: string;\n /**\n * Marker step awaiting its assertion count.\n *\n * A marker states the claim and the assertion follows it, so the count is\n * only known once the next step starts or the test ends.\n */\n pendingStep: StoryStep | null;\n /** Assertion counter reading taken when {@link pendingStep} was created. */\n pendingFrom?: number;\n}\n\n/** Active story context - set by story.init() */\nlet activeContext: StoryContext | null = null;\n\n/** Counter to track source order of stories (increments on each story.init call) */\nlet sourceOrderCounter = 0;\n\n/**\n * Get the current story context. Throws if story.init() wasn't called.\n */\nfunction getContext(): StoryContext {\n if (!activeContext) {\n throw new Error(\n \"story.init(task) must be called first. Use: it('name', ({ task }) => { story.init(task); ... });\",\n );\n }\n return activeContext;\n}\n\n/** Re-attach current meta to task.meta.story so reporter sees steps and docs (e.g. story.note). */\nfunction syncMetaToTask(): void {\n if (activeContext?.taskMeta) {\n activeContext.taskMeta.story = activeContext.meta;\n }\n}\n\n/**\n * Bridge an OTel trace into the story's meta + docs: structured `otel` meta, a\n * \"Trace ID\" key-value, and a \"View Trace\" link when the template resolves.\n * Idempotent — once a traceId is recorded it is not overwritten, so the\n * active-span path in init() and the explicit path in attachSpans() compose\n * without duplicating entries.\n */\nfunction applyTraceToMeta(\n meta: StoryMeta,\n traceId: string,\n spanId: string | undefined,\n template: string | undefined,\n): void {\n const existing = (meta.meta as { otel?: { traceId?: string } } | undefined)\n ?.otel;\n if (existing?.traceId) return;\n\n meta.meta = { ...meta.meta, otel: { traceId, spanId } };\n meta.docs = meta.docs ?? [];\n meta.docs.push({\n kind: \"kv\",\n label: \"Trace ID\",\n value: traceId,\n phase: \"runtime\",\n });\n const url = resolveTraceUrl(template, traceId);\n if (url) {\n meta.docs.push({ kind: \"link\", label: \"View Trace\", url, phase: \"runtime\" });\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Check if a name looks like a file path (to filter out from suite paths).\n */\nfunction looksLikeFilePath(name: string): boolean {\n if (name.includes('/') || name.includes('\\\\')) return true;\n if (name.includes('.spec.') || name.includes('.test.')) return true;\n if (/\\.(spec|test)\\.(ts|js|mjs|cjs)$/.test(name)) return true;\n if (/\\.(ts|js|mjs|cjs)$/.test(name)) return true;\n return false;\n}\n\n/**\n * Extract the suite path (parent describe names) from a Vitest task object.\n */\nfunction extractSuitePath(task: TaskLike): string[] | undefined {\n const path: string[] = [];\n const fileName = task.file?.name;\n let current: VitestSuite | undefined = task.suite;\n\n while (current) {\n const name = current.name;\n if (\n name &&\n name.trim() !== '' &&\n name !== '<root>' &&\n name !== fileName &&\n !looksLikeFilePath(name)\n ) {\n path.unshift(name);\n }\n current = current.suite;\n }\n\n return path.length > 0 ? path : undefined;\n}\n\n/**\n * Normalize ticket option to array of NormalizedTicket objects.\n */\nfunction normalizeTickets(\n ticket: TicketInput | TicketInput[] | undefined,\n): NormalizedTicket[] | undefined {\n if (!ticket) return undefined;\n const arr = Array.isArray(ticket) ? ticket : [ticket];\n return arr.map((t) => (typeof t === 'string' ? { id: t } : t));\n}\n\n/**\n * Convert StoryDocs inline options to DocEntry array.\n * Matches the standalone DocApi method signatures.\n */\nfunction convertStoryDocsToEntries(docs: StoryDocs): DocEntry[] {\n const entries: DocEntry[] = [];\n\n // note(text)\n if (docs.note) {\n entries.push({ kind: 'note', text: docs.note, phase: 'runtime' });\n }\n\n // tag(name | names)\n if (docs.tag) {\n const names = Array.isArray(docs.tag) ? docs.tag : [docs.tag];\n entries.push({ kind: 'tag', names, phase: 'runtime' });\n }\n\n // kv(label, value) - multiple pairs via Record\n if (docs.kv) {\n for (const [label, value] of Object.entries(docs.kv)) {\n entries.push({ kind: 'kv', label, value, phase: 'runtime' });\n }\n }\n\n // code(label, content, lang?)\n if (docs.code) {\n entries.push({\n kind: 'code',\n label: docs.code.label,\n content: docs.code.content,\n lang: docs.code.lang,\n phase: 'runtime',\n });\n }\n\n // json(label, value)\n if (docs.json) {\n entries.push({\n kind: 'code',\n label: docs.json.label,\n content: JSON.stringify(docs.json.value, null, 2),\n lang: 'json',\n phase: 'runtime',\n });\n }\n\n // state(label?, value)\n if (docs.state) {\n warnIfLargeState(docs.state.label, docs.state.value);\n entries.push({\n kind: 'state',\n label: docs.state.label,\n value: docs.state.value,\n phase: 'runtime',\n });\n }\n\n // table(label, columns, rows)\n if (docs.table) {\n entries.push({\n kind: 'table',\n label: docs.table.label,\n columns: docs.table.columns,\n rows: docs.table.rows,\n phase: 'runtime',\n });\n }\n\n // link(label, url)\n if (docs.link) {\n entries.push({\n kind: 'link',\n label: docs.link.label,\n url: docs.link.url,\n phase: 'runtime',\n });\n }\n\n // section(title, markdown)\n if (docs.section) {\n entries.push({\n kind: 'section',\n title: docs.section.title,\n markdown: docs.section.markdown,\n phase: 'runtime',\n });\n }\n\n // mermaid(code, title?)\n if (docs.mermaid) {\n entries.push({\n kind: 'mermaid',\n code: docs.mermaid.code,\n title: docs.mermaid.title,\n phase: 'runtime',\n });\n }\n\n // screenshot(path, alt?)\n if (docs.screenshot) {\n entries.push({\n kind: 'screenshot',\n path: docs.screenshot.path,\n alt: docs.screenshot.alt,\n phase: 'runtime',\n });\n }\n\n // video(path, caption?, poster?)\n if (docs.video) {\n entries.push({\n kind: 'video',\n path: docs.video.path,\n caption: docs.video.caption,\n poster: docs.video.poster,\n phase: 'runtime',\n });\n }\n\n // html(path | url | content, title?, height?)\n if (docs.html) {\n entries.push(buildHtmlDocEntry(docs.html));\n }\n\n // custom(type, data)\n if (docs.custom) {\n entries.push({\n kind: 'custom',\n type: docs.custom.type,\n data: docs.custom.data,\n phase: 'runtime',\n });\n }\n\n return entries;\n}\n\n// ============================================================================\n// story.init()\n// ============================================================================\n\n/**\n * Initialize a story for the current test.\n * Must be called at the start of each test that wants documentation.\n *\n * @param task - The Vitest task object from ({ task }) => { ... }\n * @param options - Optional story configuration (tags, ticket, meta)\n *\n * @example\n * ```ts\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n * // ... rest of test\n * });\n *\n * // With options:\n * it('admin deletes user', ({ task }) => {\n * story.init(task, {\n * tags: ['admin', 'destructive'],\n * ticket: 'JIRA-456'\n * });\n * });\n * ```\n */\n/**\n * Declare what the file's scenarios are for, before any of them run.\n *\n * Scenarios say what the system does. This says why the feature exists and who\n * it serves, so a reader meets the intent before the examples. Call it once per\n * test file, at module scope or at the top of the outermost `describe`.\n *\n * @example\n * ```ts\n * story.feature({\n * kind: 'ability',\n * title: 'Employees can secure their passwords',\n * narrative: `\n * Weak passwords are the way most accounts get taken over, so the signup\n * form rejects them outright rather than warning and letting them through.\n * `,\n * glossary: [\n * { term: 'password strength', definition: 'A score from weak to strong, from length and dictionary matches.' },\n * ],\n * });\n * ```\n */\nfunction feature(input: FeatureInput): void {\n declaredFeature = { input };\n}\n\n/**\n * The declaration waiting to be attached to the next test that runs.\n *\n * `file` is filled in by the first `init` that claims it. With Vitest's default\n * isolation each file gets a fresh module, so this is always that file's own\n * declaration. Without isolation the module is shared, and the recorded file is\n * what stops one file's declaration from leaking into the next.\n */\nlet declaredFeature: { input: FeatureInput; file?: string } | null = null;\n\nfunction attachFeature(task: TaskLike): void {\n if (!declaredFeature) return;\n\n const file = task.file?.name;\n declaredFeature.file ??= file;\n if (declaredFeature.file !== file) return;\n\n task.meta.storyFeature = { ...declaredFeature.input, sourceFile: file };\n}\n\nfunction init(task: TaskLike, options?: StoryOptions): void {\n const meta: StoryMeta = {\n scenario: task.name,\n steps: [],\n suitePath: extractSuitePath(task),\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n covers: options?.covers,\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // OTel bridge: detect active span, flow data bidirectionally\n const traceUrlTemplate =\n options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n const otelCtx = tryGetActiveOtelContext();\n if (otelCtx) {\n // OTel -> Story: capture traceId + docs from the active span.\n applyTraceToMeta(meta, otelCtx.traceId, otelCtx.spanId, traceUrlTemplate);\n\n // Story -> OTel: enrich active span with story attributes\n try {\n const reqUrl = import.meta.url\n ?? (typeof __filename !== 'undefined' ? `file://${__filename}` : undefined);\n const req = createRequire(reqUrl!);\n const api = req('@opentelemetry/api');\n const span = api.trace?.getActiveSpan?.();\n if (span) {\n span.setAttribute('story.scenario', task.name);\n if (options?.tags?.length) span.setAttribute('story.tags', options.tags);\n if (options?.ticket) {\n const tickets = Array.isArray(options.ticket) ? options.ticket : [options.ticket];\n span.setAttribute('story.tickets', tickets.map((t) => typeof t === 'string' ? t : t.id));\n }\n }\n } catch { /* OTel not available */ }\n }\n\n // Attach to task.meta so reporter can find it\n task.meta.story = meta;\n attachFeature(task);\n\n // Set active context\n activeContext = {\n meta,\n currentStep: null,\n taskMeta: task.meta,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n traceUrlTemplate,\n pendingStep: null,\n };\n\n // The last marker step's assertions land after every step call, so the count\n // is only final at the end of the test. Registered first, so LIFO ordering\n // runs it after any cleanup the test adds, and the reporter reads the total.\n const ctx = activeContext;\n try {\n onTestFinished(() => {\n flushPendingAssertions(ctx);\n task.meta.story = ctx.meta;\n });\n } catch {\n // Outside a running test (or a framework without the hook) there is nothing\n // to flush against; wrapped steps still measure themselves precisely.\n }\n}\n\n/**\n * Close off the marker step waiting on a count.\n *\n * Left undefined when the counter was unreadable at either end: an unobserved\n * step must not be reported as a step that asserted nothing.\n */\nfunction flushPendingAssertions(ctx: StoryContext): void {\n const step = ctx.pendingStep;\n const before = ctx.pendingFrom;\n ctx.pendingStep = null;\n ctx.pendingFrom = undefined;\n if (!step || before === undefined) return;\n const after = readAssertionCount();\n if (after === undefined) return;\n step.assertions = after - before;\n}\n\n// ============================================================================\n// Step Markers\n// ============================================================================\n\n/**\n * Vitest's live per-test assertion counter, or undefined when it cannot be read.\n *\n * Undefined is not zero: it means we could not observe, and the report must not\n * report an unobserved step as one that asserted nothing.\n */\nfunction readAssertionCount(): number | undefined {\n try {\n const state = (expect as unknown as {\n getState?: () => { assertionCalls?: number };\n }).getState?.();\n return typeof state?.assertionCalls === 'number' ? state.assertionCalls : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Record how many assertions ran between `before` and now. */\nfunction recordAssertions(step: StoryStep, before: number | undefined): void {\n if (before === undefined) return;\n const after = readAssertionCount();\n if (after === undefined) return;\n step.assertions = after - before;\n}\n\n/**\n * Create a step marker function for a given keyword.\n */\nfunction createStepMarker(keyword: StepKeyword) {\n function stepMarker(text: string, docs?: StoryDocs): void;\n function stepMarker(text: string, children: DocEntry[]): void;\n function stepMarker<T>(text: string, body: () => T): T;\n function stepMarker<T>(text: string, docsOrBody?: StoryDocs | DocEntry[] | (() => T)): T | void {\n const ctx = getContext();\n const isCallback = typeof docsOrBody === 'function';\n const isChildrenArray = Array.isArray(docsOrBody);\n\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n let stepDocs: DocEntry[] = [];\n if (!isCallback && !isChildrenArray && docsOrBody) {\n stepDocs = convertStoryDocsToEntries(docsOrBody as StoryDocs);\n }\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: stepDocs,\n ...(isCallback ? { wrapped: true } : {}),\n };\n\n // Close the previous marker before this step's own assertions begin.\n flushPendingAssertions(ctx);\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n if (!isCallback) {\n ctx.pendingStep = step;\n ctx.pendingFrom = readAssertionCount();\n }\n syncMetaToTask();\n\n // Handle DocEntry[] children: attach as step docs and deduplicate from story-level\n if (isChildrenArray) {\n const children = docsOrBody as DocEntry[];\n if (children.length > 0) {\n const childSet = new Set<DocEntry>(children);\n // Deduplicate from story-level docs\n ctx.meta.docs = (ctx.meta.docs ?? []).filter((d) => !childSet.has(d));\n // Deduplicate from step docs of earlier steps\n for (const prevStep of ctx.meta.steps) {\n if (prevStep !== step && prevStep.docs) {\n prevStep.docs = prevStep.docs.filter((d) => !childSet.has(d));\n }\n }\n step.docs = [...(step.docs ?? []), ...children];\n }\n syncMetaToTask();\n return;\n }\n\n if (!isCallback) return;\n\n const body = docsOrBody as () => T;\n const start = performance.now();\n const assertionsBefore = readAssertionCount();\n\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; recordAssertions(step, assertionsBefore); syncMetaToTask(); return val; },\n (err) => { step.durationMs = performance.now() - start; recordAssertions(step, assertionsBefore); syncMetaToTask(); throw err; },\n ) as T;\n }\n step.durationMs = performance.now() - start;\n recordAssertions(step, assertionsBefore);\n syncMetaToTask();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n recordAssertions(step, assertionsBefore);\n syncMetaToTask();\n throw err;\n }\n }\n return stepMarker;\n}\n\n// ============================================================================\n// Doc Methods (Standalone)\n// ============================================================================\n\n/**\n * Add a free-text note to the current step or story-level if before any step.\n */\nfunction note(text: string, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'note', text, phase: 'runtime' }, children);\n}\n\n// ============================================================================\n// Doc Method Types (shared between standalone and inline)\n// ============================================================================\n\n/** Options for kv() - key-value pair */\ninterface KvOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for json() - JSON code block */\ninterface JsonOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for state() - data snapshot of the world at this step */\ninterface StateOptions {\n label?: string;\n value: unknown;\n}\n\n/** Options for code() - code block with optional language */\ninterface CodeOptions {\n label: string;\n content: string;\n lang?: string;\n}\n\n/** Options for table() - markdown table */\ninterface TableOptions {\n label: string;\n columns: string[];\n rows: string[][];\n}\n\n/** Options for link() - hyperlink */\ninterface LinkOptions {\n label: string;\n url: string;\n}\n\n/** Options for section() - titled markdown section */\ninterface SectionOptions {\n title: string;\n markdown: string;\n}\n\n/** Options for mermaid() - Mermaid diagram */\ninterface MermaidOptions {\n code: string;\n title?: string;\n}\n\n/** Options for screenshot() - screenshot reference */\ninterface ScreenshotOptions {\n path: string;\n alt?: string;\n}\n\n/** Options for video() - video reference */\ninterface VideoOptions {\n path: string;\n caption?: string;\n poster?: string;\n}\n\n/** Options for html() - HTML embedded in a sandboxed iframe */\ninterface HtmlOptions {\n /** Local HTML file path (inlined into the report by default) */\n path?: string;\n /** Remote URL rendered via iframe src */\n url?: string;\n /** Inline HTML content rendered via iframe srcdoc */\n content?: string;\n title?: string;\n /** Iframe height: number → px, string passed through (e.g. '60vh'). Default 400px. */\n height?: number | string;\n}\n\n/** Options for custom() - custom doc entry */\ninterface CustomOptions {\n type: string;\n data: unknown;\n}\n\n\n// ============================================================================\n// Helper to attach doc entry to current step or story-level\n// ============================================================================\n\nfunction attachDoc(entry: DocEntry, children?: DocEntry[]): DocEntry {\n const ctx = getContext();\n if (children && children.length > 0) {\n entry.children = children;\n const childSet = new Set<DocEntry>(children);\n const filterDocs = (docs: DocEntry[]) => docs.filter((d) => !childSet.has(d));\n // Remove children from ALL containers (story-level + every step)\n ctx.meta.docs = filterDocs(ctx.meta.docs ?? []);\n for (const step of ctx.meta.steps) {\n if (step.docs) step.docs = filterDocs(step.docs);\n }\n }\n if (ctx.currentStep) {\n ctx.currentStep.docs ??= [];\n ctx.currentStep.docs.push(entry);\n } else {\n ctx.meta.docs ??= [];\n ctx.meta.docs.push(entry);\n }\n syncMetaToTask();\n return entry;\n}\n\n// ============================================================================\n// Doc Methods (Standalone) - same shape as inline docs\n// ============================================================================\n\n/**\n * Add a key-value pair to the current step or story-level.\n * @example story.kv({ label: 'Payment ID', value: 'pay_123' })\n */\nfunction kv(options: KvOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'kv',\n label: options.label,\n value: options.value,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a JSON code block to the current step or story-level.\n * @example story.json({ label: 'Order', value: { id: 123 } })\n */\nfunction json(options: JsonOptions, children?: DocEntry[]): DocEntry {\n const content = JSON.stringify(options.value, null, 2);\n return attachDoc({\n kind: 'code',\n label: options.label,\n content,\n lang: 'json',\n phase: 'runtime',\n }, children);\n}\n\n/** Warn (non-fatal) when a state snapshot is suspiciously large. */\nfunction warnIfLargeState(label: string | undefined, value: unknown): void {\n try {\n const length = JSON.stringify(value)?.length ?? 0;\n if (length > 100_000) {\n console.warn(\n `[executable-stories] state \"${label ?? 'state'}\" is ${Math.round(length / 1024)}KB — consider capturing a projection of the entity instead of the whole thing`,\n );\n }\n } catch {\n /* non-serializable value; still recorded */\n }\n}\n\n/**\n * Capture what the world looks like at this step — a data snapshot that\n * becomes a storyboard frame in generated docs. Consecutive snapshots with\n * the same label get diffed downstream. The value must be JSON-serializable.\n * @example\n * ```ts\n * story.given('an empty basket');\n * story.state({ label: 'Basket', value: { items: [], total: 0 } });\n * story.when('a widget is added');\n * story.state({ label: 'Basket', value: { items: ['widget'], total: 49.99 } });\n * ```\n */\nfunction state(options: StateOptions, children?: DocEntry[]): DocEntry {\n warnIfLargeState(options.label, options.value);\n return attachDoc({\n kind: 'state',\n label: options.label,\n value: options.value,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a code block with optional language to the current step or story-level.\n * @example story.code({ label: 'Config', content: 'port: 3000', lang: 'yaml' })\n */\nfunction code(options: CodeOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'code',\n label: options.label,\n content: options.content,\n lang: options.lang,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a markdown table to the current step or story-level.\n * @example story.table({ label: 'Users', columns: ['Name', 'Role'], rows: [['Alice', 'Admin']] })\n */\nfunction table(options: TableOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'table',\n label: options.label,\n columns: options.columns,\n rows: options.rows,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a hyperlink to the current step or story-level.\n * @example story.link({ label: 'API Docs', url: 'https://docs.example.com' })\n */\nfunction link(options: LinkOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'link',\n label: options.label,\n url: options.url,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a titled section with markdown content to the current step or story-level.\n * @example story.section({ title: 'Details', markdown: 'This is **important**' })\n */\nfunction section(options: SectionOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'section',\n title: options.title,\n markdown: options.markdown,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a Mermaid diagram to the current step or story-level.\n * @example story.mermaid({ code: 'graph LR; A-->B', title: 'Flow' })\n */\nfunction mermaid(options: MermaidOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'mermaid',\n code: options.code,\n title: options.title,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a screenshot reference to the current step or story-level.\n * @example story.screenshot({ path: '/screenshots/result.png', alt: 'Final result' })\n */\nfunction screenshot(options: ScreenshotOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'screenshot',\n path: options.path,\n alt: options.alt,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a video reference to the current step or story-level.\n * @example story.video({ path: '/videos/run.mp4', caption: 'Full run', poster: '/videos/run.jpg' })\n */\nfunction video(options: VideoOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'video',\n path: options.path,\n caption: options.caption,\n poster: options.poster,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Embed HTML in a sandboxed iframe in the current step or story-level docs.\n * Exactly one of path/url/content is required. Local files are inlined into\n * the report by default so it stays self-contained — the HTML must therefore\n * be self-contained too (use your tool's single-file mode); relative\n * sub-asset references won't resolve.\n * @example story.html({ path: './coverage/index.html', title: 'Coverage' })\n * @example story.html({ url: 'https://dash.example.com/run/42', height: 600 })\n * @example story.html({ content: chartHtml, title: 'Latency chart' })\n */\nfunction html(options: HtmlOptions, children?: DocEntry[]): DocEntry {\n return attachDoc(buildHtmlDocEntry(options), children);\n}\n\n/**\n * Add tag(s) to the current step or story-level.\n * @example story.tag('admin') or story.tag(['admin', 'security'])\n */\nfunction tag(name: string | string[], children?: DocEntry[]): DocEntry {\n const names = Array.isArray(name) ? name : [name];\n return attachDoc({ kind: 'tag', names, phase: 'runtime' }, children);\n}\n\n/**\n * Add a custom documentation entry for use with custom renderers.\n * @example story.custom({ type: 'myType', data: { foo: 'bar' } })\n */\nfunction custom(options: CustomOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'custom',\n type: options.type,\n data: options.data,\n phase: 'runtime',\n }, children);\n}\n\n// ============================================================================\n// Attachments\n// ============================================================================\n\n/**\n * Attach a file or inline content to the current step or test case.\n * @example story.attach({ name: 'screenshot', mediaType: 'image/png', path: '/tmp/screenshot.png' })\n */\nfunction attach(options: AttachmentOptions): void {\n const ctx = getContext();\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.attachments.push({\n ...options,\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n });\n // Store attachments on task.meta so reporter can read them\n if (ctx.taskMeta) {\n ctx.taskMeta.storyAttachments = ctx.attachments;\n }\n}\n\n// ============================================================================\n// Step Timing\n// ============================================================================\n\n/**\n * Start a timer for the current step. Returns a token to pass to endTimer().\n */\nfunction startTimer(): number {\n const ctx = getContext();\n const token = ctx.timerCounter++;\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.activeTimers.set(token, {\n start: performance.now(),\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n consumed: false,\n });\n return token;\n}\n\n/**\n * End a timer and record duration on the step that was active when startTimer() was called.\n */\nfunction endTimer(token: number): void {\n const ctx = getContext();\n const entry = ctx.activeTimers.get(token);\n if (!entry || entry.consumed) return;\n\n entry.consumed = true;\n const durationMs = performance.now() - entry.start;\n\n let step: StoryStep | undefined;\n if (entry.stepId) {\n step = ctx.meta.steps.find((s) => s.id === entry.stepId);\n }\n if (!step && entry.stepIndex !== undefined) {\n step = ctx.meta.steps[entry.stepIndex];\n }\n\n if (step) {\n step.durationMs = durationMs;\n syncMetaToTask();\n }\n}\n\n// ============================================================================\n// Step Wrappers: story.fn() and story.expect()\n// ============================================================================\n\n/**\n * Wrap a function body as a step. Records the step with timing and `wrapped: true`.\n * Supports both sync and async functions. Returns whatever the function returns.\n *\n * @param keyword - The BDD keyword (Given, When, Then, And, But)\n * @param text - Step description\n * @param body - The function to execute\n * @returns The return value of body (or a Promise of it if body is async)\n *\n * @example\n * ```ts\n * const data = story.fn('Given', 'setup data', () => ({ a: 5, b: 3 }));\n * const result = await story.fn('When', 'call API', async () => fetch('/api'));\n * ```\n */\nfunction fn<T>(keyword: StepKeyword, text: string, body: () => T): T {\n const ctx = getContext();\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: [],\n wrapped: true,\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncMetaToTask();\n\n const start = performance.now();\n\n try {\n const result = body();\n\n // Handle async functions\n if (result instanceof Promise) {\n return result.then(\n (val) => {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n return val;\n },\n (err) => {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n throw err;\n },\n ) as T;\n }\n\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n throw err;\n }\n}\n\n/**\n * Wrap an assertion as a Then step. Shorthand for `story.fn('Then', text, body)`.\n *\n * @param text - Step description\n * @param body - The assertion function to execute\n *\n * @example\n * ```ts\n * story.expect('the result is 8', () => { expect(result).toBe(8); });\n * await story.expect('async check', async () => { ... });\n * ```\n */\nfunction storyExpect<T>(text: string, body: () => T): T {\n return fn('Then', text, body);\n}\n\n// ============================================================================\n// Span attachment: story.attachSpans()\n// ============================================================================\n\n/**\n * Attach OTel spans to the current test so the StoryReporter renders them\n * as a trace waterfall in HTML reports.\n *\n * Accepts any array of objects with at least `spanId` and `name` fields.\n * Structurally compatible with autotel's `SerializedSpan` and the\n * `OtelSpan` type from executable-stories-formatters.\n *\n * Pass `traceId` (and optionally `spanId`) when the trace was captured *after*\n * `story.init()` ran — e.g. the test wraps the work in its own root span. The\n * init-time OTel bridge can't see a trace that doesn't exist yet, so this is\n * where the \"View Trace\" link and trace badge get wired (using the\n * `traceUrlTemplate` from init).\n *\n * @example\n * ```ts\n * import { serializeSpan } from 'autotel/test-span-collector';\n *\n * // After running code that creates spans:\n * const spans = exporter.getFinishedSpans().map(serializeSpan);\n * story.attachSpans(spans, { traceId, spanId });\n * ```\n */\nfunction attachSpans(\n spans: ReadonlyArray<Record<string, unknown>>,\n options?: { traceId?: string; spanId?: string },\n): void {\n const ctx = getContext();\n if (ctx.taskMeta) {\n ctx.taskMeta.otelSpans = spans;\n }\n if (options?.traceId) {\n applyTraceToMeta(\n ctx.meta,\n options.traceId,\n options.spanId,\n ctx.traceUrlTemplate,\n );\n syncMetaToTask();\n }\n}\n\n// ============================================================================\n// Export story object\n// ============================================================================\n\n/**\n * The main story API object.\n *\n * Use with native Vitest describe/it/test for full IDE support:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n *\n * story.given('two numbers 5 and 3');\n * const a = 5, b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\nexport const story = {\n // Core\n init,\n feature,\n\n // BDD step markers\n given: createStepMarker('Given'),\n when: createStepMarker('When'),\n then: createStepMarker('Then'),\n and: createStepMarker('And'),\n but: createStepMarker('But'),\n\n // AAA pattern aliases\n arrange: createStepMarker('Given'),\n act: createStepMarker('When'),\n assert: createStepMarker('Then'),\n\n // Additional aliases\n setup: createStepMarker('Given'),\n context: createStepMarker('Given'),\n execute: createStepMarker('When'),\n action: createStepMarker('When'),\n verify: createStepMarker('Then'),\n\n // Standalone doc methods\n note,\n kv,\n json,\n state,\n code,\n table,\n link,\n section,\n mermaid,\n screenshot,\n video,\n html,\n tag,\n custom,\n\n // Attachments\n attach,\n\n // OTel span attachment\n attachSpans,\n\n // Step wrappers\n fn,\n expect: storyExpect,\n\n // Step timing\n startTimer,\n endTimer,\n};\n\nexport type Story = typeof story;\n","/**\n * Type definitions for executable-stories-vitest.\n *\n * Shared story types (StepKeyword, DocEntry, StoryStep, StoryMeta, etc.)\n * are imported from executable-stories-core — the single source of truth.\n * This module re-exports them and adds Vitest-specific types.\n */\n\n// Re-export shared story types from core\nexport type {\n StepKeyword,\n StepMode,\n DocPhase,\n DocEntry,\n StoryStep,\n StoryMeta,\n NormalizedTicket,\n} from 'executable-stories-core/types/story';\n\nexport { STORY_META_KEY } from 'executable-stories-core/types/story';\n\n// ============================================================================\n// Ticket Input Types\n// ============================================================================\n\n/** A ticket reference: either a plain string ID or an object with id and optional url */\nexport type TicketInput = string | { id: string; url?: string };\n\n// ============================================================================\n// Vitest-specific Types\n// ============================================================================\n\n/**\n * Inline documentation options for step markers.\n * Pass to story.given(), story.when(), story.then() as second argument.\n *\n * @example\n * ```ts\n * story.given('valid credentials', {\n * json: { label: 'Credentials', value: { email: 'test@example.com', password: '***' } },\n * note: 'Password is masked for security'\n * });\n * ```\n */\nexport interface StoryDocs {\n /** Add a free-text note */\n note?: string;\n /** Add tag(s) for categorization */\n tag?: string | string[];\n /** Add key-value pairs */\n kv?: Record<string, unknown>;\n /** Add a code block with label and optional language */\n code?: { label: string; content: string; lang?: string };\n /** Add a JSON data block with label */\n json?: { label: string; value: unknown };\n /** Capture a data snapshot of the world at this step (storyboard frame) */\n state?: { label?: string; value: unknown };\n /** Add a markdown table with label */\n table?: { label: string; columns: string[]; rows: string[][] };\n /** Add a hyperlink */\n link?: { label: string; url: string };\n /** Add a titled section with markdown content */\n section?: { title: string; markdown: string };\n /** Add a Mermaid diagram with optional title */\n mermaid?: { code: string; title?: string };\n /** Add a screenshot reference */\n screenshot?: { path: string; alt?: string };\n /** Add a video reference with optional caption and poster image */\n video?: { path: string; caption?: string; poster?: string };\n /** Embed HTML in a sandboxed iframe. Exactly one of path/url/content. */\n html?: { path?: string; url?: string; content?: string; title?: string; height?: number | string };\n /** Add a custom documentation entry */\n custom?: { type: string; data: unknown };\n}\n\n/**\n * Options for configuring a story via story.init().\n *\n * @example\n * ```ts\n * it('admin deletes user', ({ task }) => {\n * story.init(task, {\n * tags: ['admin', 'destructive'],\n * ticket: 'JIRA-456'\n * });\n * });\n * ```\n */\n/** One entry in a feature's glossary. */\nexport interface GlossaryTerm {\n /** The term as it appears in scenario and step text. */\n term: string;\n /** What it means, in one or two sentences. */\n definition: string;\n}\n\n/** What {@link Story.feature} accepts. */\nexport interface FeatureInput {\n /** Heading for the feature. */\n title: string;\n /**\n * How to introduce it. `ability` frames the feature as something a person can\n * now do; `business-need` covers cross-cutting concerns like security or\n * performance that no single user asks for. Defaults to `feature`.\n */\n kind?: \"feature\" | \"ability\" | \"business-need\";\n /** Markdown explaining why the feature exists and who it serves. */\n narrative?: string;\n /** Tags applied to every scenario in the file. */\n tags?: string[];\n /** Terms this feature defines, linked wherever they appear in the report. */\n glossary?: GlossaryTerm[];\n}\n\nexport interface StoryOptions {\n /** Tags for filtering and categorizing stories */\n tags?: string[];\n /** Ticket/issue reference(s) for requirements traceability */\n ticket?: TicketInput | TicketInput[];\n /** Product-code paths/globs this scenario exercises (project-root-relative). */\n covers?: string[];\n /** Arbitrary user-defined metadata */\n meta?: Record<string, unknown>;\n /** URL template for OTel trace links. Uses {traceId} placeholder. Also settable via OTEL_TRACE_URL_TEMPLATE env var. */\n traceUrlTemplate?: string;\n}\n\n// ============================================================================\n// Vitest Task Type (minimal interface)\n// ============================================================================\n\n/** Minimal Vitest suite interface for suite path extraction */\nexport interface VitestSuite {\n /** Suite name */\n name?: string;\n /** Parent suite (optional) */\n suite?: VitestSuite;\n}\n\n/**\n * Minimal Vitest task interface for story.init().\n * This is the { task } from it('name', ({ task }) => { ... }).\n *\n * Uses generic type parameter to be compatible with Vitest's actual TaskMeta type.\n */\nexport interface VitestTask<TMeta = Record<string, unknown>> {\n /** The test/task name */\n name: string;\n /** Task metadata object where we store story data */\n meta: TMeta;\n /** Parent suite (optional) */\n suite?: VitestSuite;\n /** The test file (optional) */\n file?: { name?: string };\n}\n","/**\n * Vitest reporter that reads task.meta.story from tests and writes reports\n * using the executable-stories-formatters package.\n *\n * Do not add value imports from \"vitest\" or \"./story-api.js\" here; this entry is loaded in vitest.config\n * before Vitest is ready. Use only `import type` from those modules.\n *\n * Type strategy: this reporter is duck-typed against vitest's runner contract,\n * not nominally typed via `implements Reporter`. That keeps it compatible with\n * any vitest-compatible runner (including forks like vite-plus) where the\n * `Vitest` class type carries different private fields and would otherwise\n * fail nominal assignability checks at the consumer's call site.\n *\n * Vitest types are imported only for parameter typing of internal helpers,\n * never as `implements`. Anything we touch on `ctx` is captured by the local\n * `VitestContext` structural type below.\n */\nimport type {\n SerializedError,\n TestModule,\n TestCase,\n TestRunEndReason,\n} from \"vitest/node\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"./types\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n sendNotifications,\n toCIInfo,\n loadHistory,\n updateHistory,\n saveHistory,\n type RawFeature,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n type CoverageSummary,\n type OtelSpan,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Structural runner context\n// ----------------------------------------------------------------------------\n// We only read `ctx.config.root`. Avoid importing the full `Vitest` class as\n// a parameter type — its private fields (`_clearScreenPending`, etc.) make\n// nominal assignability fail across vitest forks. A structural type lets the\n// reporter accept any vitest-compatible runner.\n// ============================================================================\n\nexport type VitestContext = {\n config?: {\n root?: string;\n /**\n * Set by `vitest -t` / `--testNamePattern`. Vitest reports only the tests\n * that match, so a run carrying one cannot speak for the rest of a file.\n */\n testNamePattern?: string | RegExp;\n };\n};\n\n// ============================================================================\n// Reporter Protocol (duck-typed interface for reporters)\n// ============================================================================\n// Exported protocol that captures the reporter contract. This allows\n// StoryReporter to be properly typed without nominal `implements Reporter`,\n// enabling duck-typing compatibility across vitest forks while maintaining\n// type safety at the consumer call site.\n// ============================================================================\n\n// Public-facing protocol uses loose params on runner-supplied callbacks so the\n// returned reporter is structurally assignable to any vitest-compatible runner's\n// `Reporter` type (vitest, vite-plus, future forks). The concrete StoryReporter\n// class still uses strict vitest types internally — method bivariance lets the\n// narrower class satisfy this looser protocol.\nexport interface StoryReporterProtocol {\n onInit(ctx: VitestContext): void;\n onCoverage(coverage: unknown): void;\n onTestRunEnd(\n testModules: readonly unknown[],\n unhandledErrors: readonly unknown[],\n reason: unknown,\n ): Promise<void>;\n}\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** When GITHUB_ACTIONS, append report to job summary via @actions/core. Default: true */\n enableGithubActionsSummary?: boolean;\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Coverage Types\n// ============================================================================\n\ntype CoverageMetric = { total: number; covered: number; pct: number };\ntype CoverageData = {\n statements: CoverageMetric;\n branches: CoverageMetric;\n functions: CoverageMetric;\n lines?: CoverageMetric;\n};\ntype CoverageFile = {\n s: Record<string, number>;\n f: Record<string, number>;\n b: Record<string, number[]>;\n l?: Record<string, number>;\n};\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Normalize Vitest onCoverage payload to coverage data.\n */\nfunction normalizeCoveragePayload(\n coverage: unknown\n): Record<string, CoverageFile> | undefined {\n if (!coverage || typeof coverage !== \"object\" || Array.isArray(coverage))\n return undefined;\n const raw = coverage as Record<string, unknown>;\n const data: Record<string, CoverageFile> = {};\n for (const [filePath, file] of Object.entries(raw)) {\n if (\n file &&\n typeof file === \"object\" &&\n \"s\" in file &&\n \"f\" in file &&\n \"b\" in file\n ) {\n data[filePath] = file as CoverageFile;\n }\n }\n if (Object.keys(data).length === 0) return undefined;\n return data;\n}\n\n/**\n * Summarize coverage data.\n */\nfunction summarizeCoverage(\n data: Record<string, CoverageFile>\n): CoverageData | undefined {\n let statementsTotal = 0;\n let statementsCovered = 0;\n let functionsTotal = 0;\n let functionsCovered = 0;\n let branchesTotal = 0;\n let branchesCovered = 0;\n let linesTotal = 0;\n let linesCovered = 0;\n let hasLines = false;\n\n for (const file of Object.values(data)) {\n for (const count of Object.values(file.s)) {\n statementsTotal += 1;\n if (count > 0) statementsCovered += 1;\n }\n for (const count of Object.values(file.f)) {\n functionsTotal += 1;\n if (count > 0) functionsCovered += 1;\n }\n for (const counts of Object.values(file.b)) {\n for (const count of counts) {\n branchesTotal += 1;\n if (count > 0) branchesCovered += 1;\n }\n }\n if (file.l) {\n hasLines = true;\n for (const count of Object.values(file.l)) {\n linesTotal += 1;\n if (count > 0) linesCovered += 1;\n }\n }\n }\n\n if (\n statementsTotal === 0 &&\n functionsTotal === 0 &&\n branchesTotal === 0 &&\n !hasLines\n ) {\n return undefined;\n }\n\n const metric = (covered: number, total: number): CoverageMetric => ({\n total,\n covered,\n pct: total === 0 ? 100 : Math.round((covered / total) * 100),\n });\n\n const summary: CoverageData = {\n statements: metric(statementsCovered, statementsTotal),\n branches: metric(branchesCovered, branchesTotal),\n functions: metric(functionsCovered, functionsTotal),\n };\n if (hasLines) {\n summary.lines = metric(linesCovered, linesTotal);\n }\n return summary;\n}\n\n/**\n * Convert internal coverage data to formatters CoverageSummary.\n */\nfunction toCoverageSummary(\n data: CoverageData | undefined\n): CoverageSummary | undefined {\n if (!data) return undefined;\n return {\n statementsPct: data.statements.pct,\n branchesPct: data.branches.pct,\n functionsPct: data.functions.pct,\n linesPct: data.lines?.pct,\n };\n}\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\n/**\n * Vitest reporter that generates reports using executable-stories-formatters.\n *\n * Reads `task.meta.story` from each test and generates reports in configured formats.\n * Supports output routing (aggregated/colocated) and multiple output formats.\n *\n * Implements StoryReporterProtocol for type-safe duck-typing across vitest forks.\n *\n * Prefer {@link createStoryReporter} in `vitest.config.ts`: it returns this same\n * reporter already typed as StoryReporterProtocol, so the config needs no cast.\n * Construct the class directly only when you need to subclass or wrap it.\n */\nexport default class StoryReporter implements StoryReporterProtocol {\n private options: StoryReporterOptions;\n private ctx: VitestContext | undefined;\n private startTime: number = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n /**\n * Left unknown until onInit sees a config. Claiming full coverage without\n * having looked would let a later merge retire scenarios on a guess.\n */\n private runScope: \"full\" | \"filtered\" | undefined;\n private coverageByFile: Record<string, CoverageFile> = {};\n\n constructor(options: StoryReporterOptions = {}) {\n this.options = options;\n }\n\n onInit(ctx: VitestContext): void {\n this.ctx = ctx;\n this.startTime = Date.now();\n const root = ctx.config?.root ?? process.cwd();\n // `vitest -t` reports the matching tests and nothing else, so consumers\n // must not read this run as the full contents of the files it touches.\n // Vitest hands us the pattern either way, so the answer is never a guess.\n if (ctx.config) {\n this.runScope = ctx.config.testNamePattern != null ? \"filtered\" : \"full\";\n }\n\n // Read metadata if needed\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(root);\n this.gitSha = readGitSha(root);\n }\n }\n\n onCoverage(coverage: unknown): void {\n const data = normalizeCoveragePayload(coverage);\n if (data) {\n this.coverageByFile = { ...this.coverageByFile, ...data };\n }\n }\n\n async onTestRunEnd(\n testModules: ReadonlyArray<TestModule>,\n _unhandledErrors: ReadonlyArray<SerializedError>,\n reason: TestRunEndReason\n ): Promise<void> {\n if (reason === \"interrupted\") return;\n\n const root = this.ctx?.config?.root ?? process.cwd();\n\n // Collect test cases\n const rawTestCases = this.collectTestCases(testModules, root);\n const coveredSourceFiles = this.collectCoveredSourceFiles(testModules, root);\n const incompleteSourceFiles = this.collectIncompleteSourceFiles(testModules, root);\n const features = collectFeatures(testModules, root);\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n ...(features.length > 0 ? { features } : {}),\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: root,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n ...(this.runScope ? { runScope: this.runScope } : {}),\n ...(coveredSourceFiles.length > 0 ? { coveredSourceFiles } : {}),\n ...(incompleteSourceFiles.length > 0 ? { incompleteSourceFiles } : {}),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n try {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(root, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n } catch (err) {\n console.error(\"Failed to write raw run JSON:\", err);\n }\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Add coverage if available\n const coverageData = summarizeCoverage(this.coverageByFile);\n if (coverageData) {\n canonicalRun.coverage = toCoverageSummary(coverageData);\n }\n\n // 1. Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n const results = await generator.generate(canonicalRun);\n\n // Append to GitHub Actions summary if enabled\n const enableGithubSummary = this.options.enableGithubActionsSummary ?? true;\n if (process.env.GITHUB_ACTIONS === \"true\" && enableGithubSummary) {\n const markdownPaths = results.get(\"markdown\") ?? [];\n if (markdownPaths.length > 0) {\n const firstPath = markdownPaths[0];\n const content = fs.readFileSync(firstPath, \"utf8\");\n await this.appendGithubSummary(content).catch(() => {});\n }\n }\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n\n // 2. Update history (independent of report generation)\n try {\n const histOpts = this.options.history;\n if (histOpts?.filePath) {\n const historyPath = path.isAbsolute(histOpts.filePath)\n ? histOpts.filePath\n : path.join(root, histOpts.filePath);\n const store = loadHistory(\n { filePath: historyPath },\n {\n readFile: (p: string) => { try { return fs.readFileSync(p, \"utf8\"); } catch { return undefined; } },\n logger: console,\n },\n );\n const updated = updateHistory({ store, run: canonicalRun, maxRuns: histOpts.maxRuns ?? 10 });\n const dir = path.dirname(historyPath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n saveHistory(\n { filePath: historyPath, store: updated },\n { writeFile: (p: string, c: string) => fs.writeFileSync(p, c, \"utf8\") },\n );\n }\n } catch (err) {\n console.error(\"Failed to update history:\", err);\n }\n\n // 3. Send notifications (independent of both above)\n try {\n if (this.options.notification) {\n await sendNotifications(\n { run: canonicalRun, notification: this.options.notification },\n { fetch: globalThis.fetch, logger: console, toCIInfo },\n );\n }\n } catch (err) {\n console.error(\"Failed to send notifications:\", err);\n }\n }\n\n /**\n * Collect test cases from Vitest test modules.\n */\n /**\n * Every module vitest ran, whether or not it produced a story.\n *\n * The collected test cases only name modules that produced something, so a\n * file whose last story was deleted would otherwise look like a file that did\n * not run and keep its old scenarios for good.\n */\n private collectCoveredSourceFiles(\n testModules: ReadonlyArray<TestModule>,\n root: string\n ): string[] {\n const files = new Set<string>();\n for (const mod of testModules) {\n const moduleId =\n mod.moduleId ?? (mod as { relativeModuleId?: string }).relativeModuleId ?? \"\";\n if (!moduleId) continue;\n const absolute = path.isAbsolute(moduleId) ? moduleId : path.resolve(root, moduleId);\n files.add(toRelativePosix(absolute, root));\n }\n return [...files].sort();\n }\n\n /**\n * Files whose scenarios could not be collected in full.\n *\n * A module that errored, or a test that failed without ever declaring a\n * story, is missing scenarios because the run broke. Treating that as\n * deletion would throw away documentation at the exact moment the suite is\n * unhealthy. Checked per test, not per file: one healthy story in a module\n * does not vouch for its broken siblings.\n */\n private collectIncompleteSourceFiles(\n testModules: ReadonlyArray<TestModule>,\n root: string\n ): string[] {\n const incomplete = new Set<string>();\n for (const mod of testModules) {\n const moduleId =\n mod.moduleId ?? (mod as { relativeModuleId?: string }).relativeModuleId ?? \"\";\n if (!moduleId) continue;\n const absolute = path.isAbsolute(moduleId) ? moduleId : path.resolve(root, moduleId);\n const sourceFile = toRelativePosix(absolute, root);\n\n const errors = (mod as { errors?: () => unknown[] }).errors?.() ?? [];\n const collection = mod.children;\n if (errors.length > 0 || !collection) {\n const state = (mod as { state?: () => string }).state?.();\n if (errors.length > 0 || state === \"failed\") incomplete.add(sourceFile);\n continue;\n }\n\n for (const test of collection.allTests()) {\n const meta = this.getStoryMeta(test);\n if (meta?.scenario && Array.isArray(meta.steps)) continue;\n if (test.options?.mode === \"todo\") continue;\n if (test.result?.()?.state === \"failed\") {\n incomplete.add(sourceFile);\n break;\n }\n }\n }\n return [...incomplete].sort();\n }\n\n private collectTestCases(\n testModules: ReadonlyArray<TestModule>,\n root: string\n ): RawTestCase[] {\n const testCases: RawTestCase[] = [];\n\n for (const mod of testModules) {\n const collection = mod.children;\n if (!collection) continue;\n\n const moduleId =\n mod.moduleId ??\n (mod as { relativeModuleId?: string }).relativeModuleId ??\n \"\";\n const absoluteModuleId = path.isAbsolute(moduleId)\n ? moduleId\n : path.resolve(root, moduleId);\n const sourceFile = toRelativePosix(absoluteModuleId, root);\n\n // Bodyless `it.todo(...)` never runs `story.init()`, so those tests have\n // no story meta. Collect them separately and emit them as planned\n // scenarios — but only for modules that contain story tests, so plain\n // (non-story) suites full of todos don't leak into the generated docs.\n const plannedTests: TestCase[] = [];\n let moduleHasStoryTests = false;\n\n for (const test of collection.allTests()) {\n const meta = this.getStoryMeta(test);\n if (!meta?.scenario || !Array.isArray(meta.steps)) {\n if (test.options?.mode === \"todo\") plannedTests.push(test);\n continue;\n }\n moduleHasStoryTests = true;\n\n const result = test.result?.();\n const state = result?.state ?? \"pending\";\n const durationMs =\n typeof (result as { duration?: number } | undefined)?.duration ===\n \"number\"\n ? (result as unknown as { duration: number }).duration\n : 0;\n\n // Get error details\n let errorMessage: string | undefined;\n let errorStack: string | undefined;\n if (state === \"failed\" && result) {\n const errors = (result as { errors?: SerializedError[] }).errors;\n if (errors?.length) {\n const err = errors[0];\n errorMessage = err.message;\n errorStack = err.stack;\n }\n }\n\n // Map Vitest state to raw status\n const statusMap: Record<string, string> = {\n passed: \"pass\",\n failed: \"fail\",\n skipped: \"skip\",\n pending: \"pending\",\n todo: \"pending\",\n };\n\n // Extract attachments from task.meta.storyAttachments\n const taskMeta = test.meta() as Record<string, unknown>;\n const scopedAttachments = (taskMeta?.storyAttachments ?? []) as Array<{\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n stepIndex?: number;\n stepId?: string;\n }>;\n const attachments: RawAttachment[] = scopedAttachments.map((a) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: a.body,\n encoding: a.encoding,\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing) from story steps\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number }, i: number) => ({\n index: i,\n title: (s as { text: string }).text,\n durationMs: s.durationMs,\n }));\n\n // Read autotel OTel spans from task.meta.otelSpans\n const otelSpans = taskMeta?.otelSpans;\n if (Array.isArray(otelSpans) && otelSpans.length > 0) {\n const valid = otelSpans.filter(\n (s: unknown): s is OtelSpan =>\n s != null &&\n typeof s === \"object\" &&\n typeof (s as Record<string, unknown>).spanId === \"string\" &&\n typeof (s as Record<string, unknown>).name === \"string\",\n );\n if (valid.length > 0) {\n meta.otelSpans = valid;\n }\n }\n\n // Retry info from Vitest result\n const retryCount = (result as { retryCount?: number } | undefined)?.retryCount ?? 0;\n const configuredRetries = Math.max(\n retryCount,\n (test as { retries?: number }).retries ??\n (test as { options?: { retry?: number } }).options?.retry ??\n 0,\n );\n\n testCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath\n ? [...meta.suitePath, meta.scenario]\n : [meta.scenario],\n story: meta,\n sourceFile,\n sourceLine: Math.max(1, meta.sourceOrder ?? 1),\n status: (statusMap[state] ?? \"unknown\") as RawTestCase[\"status\"],\n durationMs,\n error: errorMessage\n ? { message: errorMessage, stack: errorStack }\n : undefined,\n attachments: attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: retryCount,\n retries: configuredRetries,\n });\n }\n\n if (moduleHasStoryTests) {\n for (const test of plannedTests) {\n const titlePath = test.fullName.split(\" > \");\n testCases.push({\n title: test.name,\n titlePath,\n story: {\n scenario: test.name,\n steps: [],\n suitePath: titlePath.slice(0, -1),\n },\n sourceFile,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n });\n }\n }\n }\n\n return testCases;\n }\n\n private getStoryMeta(test: TestCase): StoryMeta | undefined {\n const meta = test.meta() as Record<string, unknown>;\n return meta?.[\"story\"] as StoryMeta | undefined;\n }\n\n private async appendGithubSummary(reportText: string): Promise<void> {\n try {\n const { summary } = await import(\"@actions/core\");\n summary.addRaw(reportText);\n await summary.write();\n } catch {\n // @actions/core not available or not in Actions\n }\n }\n}\n\n// ============================================================================\n// Factory Function (recommended API)\n// ============================================================================\n// Type-safe factory for creating story reporters. Returns a properly-typed\n// instance that implements StoryReporterProtocol, enabling clean usage in\n// vite.config.ts without type casts.\n//\n// Usage:\n// import { createStoryReporter } from 'executable-stories-vitest/reporter';\n// export default defineConfig({\n// test: {\n// reporters: [\n// 'default',\n// createStoryReporter({\n// formats: ['html', 'markdown'],\n// outputDir: 'reports',\n// }),\n// ],\n// },\n// });\n// ============================================================================\n\nexport function createStoryReporter(\n options?: StoryReporterOptions\n): StoryReporterProtocol {\n return new StoryReporter(options);\n}\n\nexport { StoryReporter };\n\n/**\n * Read the feature declarations tests attached to their metadata.\n *\n * `story.feature(...)` runs in the worker, so the declaration travels on\n * `task.meta` like everything else. Every test in a file carries the same one;\n * keying by the module's path collapses them back to one per file.\n */\nfunction collectFeatures(\n testModules: ReadonlyArray<TestModule>,\n root: string\n): RawFeature[] {\n const byFile = new Map<string, RawFeature>();\n\n for (const mod of testModules) {\n const collection = mod.children;\n if (!collection) continue;\n\n const moduleId =\n mod.moduleId ?? (mod as { relativeModuleId?: string }).relativeModuleId ?? \"\";\n const absoluteModuleId = path.isAbsolute(moduleId)\n ? moduleId\n : path.resolve(root, moduleId);\n const sourceFile = toRelativePosix(absoluteModuleId, root);\n\n for (const test of collection.allTests()) {\n const declared = (test.meta() as { storyFeature?: RawFeature } | undefined)?.storyFeature;\n if (declared?.title) {\n byFile.set(sourceFile, { ...declared, sourceFile });\n break;\n }\n }\n }\n\n return [...byFile.values()];\n}\n"],"mappings":"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","names":["StoryReporter","path","state","state","StoryReporter"]}