executable-stories-vitest 8.0.0 → 8.1.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.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { StepKeyword } from 'executable-stories-formatters';
2
- export { ColocatedStyle, DocEntry, DocPhase, FormatterOptions, OutputFormat, OutputMode, OutputRule, STORY_META_KEY, StepKeyword, StepMode, StoryMeta, StoryStep } from 'executable-stories-formatters';
1
+ import { DocEntry, StepKeyword } from 'executable-stories-formatters';
2
+ export { ColocatedStyle, DocEntry, DocPhase, FormatterOptions, NormalizedTicket, OutputFormat, OutputMode, OutputRule, STORY_META_KEY, StepKeyword, StepMode, StoryMeta, StoryStep } from 'executable-stories-formatters';
3
3
  export { StoryReporterOptions } from './reporter.js';
4
4
  import 'vitest/node';
5
5
 
@@ -11,6 +11,11 @@ import 'vitest/node';
11
11
  * This module re-exports them and adds Vitest-specific types.
12
12
  */
13
13
 
14
+ /** A ticket reference: either a plain string ID or an object with id and optional url */
15
+ type TicketInput = string | {
16
+ id: string;
17
+ url?: string;
18
+ };
14
19
  /**
15
20
  * Inline documentation options for step markers.
16
21
  * Pass to story.given(), story.when(), story.then() as second argument.
@@ -90,7 +95,7 @@ interface StoryOptions {
90
95
  /** Tags for filtering and categorizing stories */
91
96
  tags?: string[];
92
97
  /** Ticket/issue reference(s) for requirements traceability */
93
- ticket?: string | string[];
98
+ ticket?: TicketInput | TicketInput[];
94
99
  /** Arbitrary user-defined metadata */
95
100
  meta?: Record<string, unknown>;
96
101
  /** URL template for OTel trace links. Uses {traceId} placeholder. Also settable via OTEL_TRACE_URL_TEMPLATE env var. */
@@ -199,7 +204,7 @@ declare function init(task: TaskLike, options?: StoryOptions): void;
199
204
  /**
200
205
  * Add a free-text note to the current step or story-level if before any step.
201
206
  */
202
- declare function note(text: string): void;
207
+ declare function note(text: string, children?: DocEntry[]): DocEntry;
203
208
  /** Options for kv() - key-value pair */
204
209
  interface KvOptions {
205
210
  label: string;
@@ -251,52 +256,52 @@ interface CustomOptions {
251
256
  * Add a key-value pair to the current step or story-level.
252
257
  * @example story.kv({ label: 'Payment ID', value: 'pay_123' })
253
258
  */
254
- declare function kv(options: KvOptions): void;
259
+ declare function kv(options: KvOptions, children?: DocEntry[]): DocEntry;
255
260
  /**
256
261
  * Add a JSON code block to the current step or story-level.
257
262
  * @example story.json({ label: 'Order', value: { id: 123 } })
258
263
  */
259
- declare function json(options: JsonOptions): void;
264
+ declare function json(options: JsonOptions, children?: DocEntry[]): DocEntry;
260
265
  /**
261
266
  * Add a code block with optional language to the current step or story-level.
262
267
  * @example story.code({ label: 'Config', content: 'port: 3000', lang: 'yaml' })
263
268
  */
264
- declare function code(options: CodeOptions): void;
269
+ declare function code(options: CodeOptions, children?: DocEntry[]): DocEntry;
265
270
  /**
266
271
  * Add a markdown table to the current step or story-level.
267
272
  * @example story.table({ label: 'Users', columns: ['Name', 'Role'], rows: [['Alice', 'Admin']] })
268
273
  */
269
- declare function table(options: TableOptions): void;
274
+ declare function table(options: TableOptions, children?: DocEntry[]): DocEntry;
270
275
  /**
271
276
  * Add a hyperlink to the current step or story-level.
272
277
  * @example story.link({ label: 'API Docs', url: 'https://docs.example.com' })
273
278
  */
274
- declare function link(options: LinkOptions): void;
279
+ declare function link(options: LinkOptions, children?: DocEntry[]): DocEntry;
275
280
  /**
276
281
  * Add a titled section with markdown content to the current step or story-level.
277
282
  * @example story.section({ title: 'Details', markdown: 'This is **important**' })
278
283
  */
279
- declare function section(options: SectionOptions): void;
284
+ declare function section(options: SectionOptions, children?: DocEntry[]): DocEntry;
280
285
  /**
281
286
  * Add a Mermaid diagram to the current step or story-level.
282
287
  * @example story.mermaid({ code: 'graph LR; A-->B', title: 'Flow' })
283
288
  */
284
- declare function mermaid(options: MermaidOptions): void;
289
+ declare function mermaid(options: MermaidOptions, children?: DocEntry[]): DocEntry;
285
290
  /**
286
291
  * Add a screenshot reference to the current step or story-level.
287
292
  * @example story.screenshot({ path: '/screenshots/result.png', alt: 'Final result' })
288
293
  */
289
- declare function screenshot(options: ScreenshotOptions): void;
294
+ declare function screenshot(options: ScreenshotOptions, children?: DocEntry[]): DocEntry;
290
295
  /**
291
296
  * Add tag(s) to the current step or story-level.
292
297
  * @example story.tag('admin') or story.tag(['admin', 'security'])
293
298
  */
294
- declare function tag(name: string | string[]): void;
299
+ declare function tag(name: string | string[], children?: DocEntry[]): DocEntry;
295
300
  /**
296
301
  * Add a custom documentation entry for use with custom renderers.
297
302
  * @example story.custom({ type: 'myType', data: { foo: 'bar' } })
298
303
  */
299
- declare function custom(options: CustomOptions): void;
304
+ declare function custom(options: CustomOptions, children?: DocEntry[]): DocEntry;
300
305
  /**
301
306
  * Attach a file or inline content to the current step or test case.
302
307
  * @example story.attach({ name: 'screenshot', mediaType: 'image/png', path: '/tmp/screenshot.png' })
@@ -339,6 +344,24 @@ declare function fn<T>(keyword: StepKeyword, text: string, body: () => T): T;
339
344
  * ```
340
345
  */
341
346
  declare function storyExpect<T>(text: string, body: () => T): T;
347
+ /**
348
+ * Attach OTel spans to the current test so the StoryReporter renders them
349
+ * as a trace waterfall in HTML reports.
350
+ *
351
+ * Accepts any array of objects with at least `spanId` and `name` fields.
352
+ * Structurally compatible with autotel's `SerializedSpan` and the
353
+ * `OtelSpan` type from executable-stories-formatters.
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * import { serializeSpan } from 'autotel/test-span-collector';
358
+ *
359
+ * // After running code that creates spans:
360
+ * const spans = exporter.getFinishedSpans().map(serializeSpan);
361
+ * story.attachSpans(spans);
362
+ * ```
363
+ */
364
+ declare function attachSpans(spans: ReadonlyArray<Record<string, unknown>>): void;
342
365
  /**
343
366
  * The main story API object.
344
367
  *
@@ -369,54 +392,67 @@ declare const story: {
369
392
  init: typeof init;
370
393
  given: {
371
394
  (text: string, docs?: StoryDocs): void;
395
+ (text: string, children: DocEntry[]): void;
372
396
  <T>(text: string, body: () => T): T;
373
397
  };
374
398
  when: {
375
399
  (text: string, docs?: StoryDocs): void;
400
+ (text: string, children: DocEntry[]): void;
376
401
  <T>(text: string, body: () => T): T;
377
402
  };
378
403
  then: {
379
404
  (text: string, docs?: StoryDocs): void;
405
+ (text: string, children: DocEntry[]): void;
380
406
  <T>(text: string, body: () => T): T;
381
407
  };
382
408
  and: {
383
409
  (text: string, docs?: StoryDocs): void;
410
+ (text: string, children: DocEntry[]): void;
384
411
  <T>(text: string, body: () => T): T;
385
412
  };
386
413
  but: {
387
414
  (text: string, docs?: StoryDocs): void;
415
+ (text: string, children: DocEntry[]): void;
388
416
  <T>(text: string, body: () => T): T;
389
417
  };
390
418
  arrange: {
391
419
  (text: string, docs?: StoryDocs): void;
420
+ (text: string, children: DocEntry[]): void;
392
421
  <T>(text: string, body: () => T): T;
393
422
  };
394
423
  act: {
395
424
  (text: string, docs?: StoryDocs): void;
425
+ (text: string, children: DocEntry[]): void;
396
426
  <T>(text: string, body: () => T): T;
397
427
  };
398
428
  assert: {
399
429
  (text: string, docs?: StoryDocs): void;
430
+ (text: string, children: DocEntry[]): void;
400
431
  <T>(text: string, body: () => T): T;
401
432
  };
402
433
  setup: {
403
434
  (text: string, docs?: StoryDocs): void;
435
+ (text: string, children: DocEntry[]): void;
404
436
  <T>(text: string, body: () => T): T;
405
437
  };
406
438
  context: {
407
439
  (text: string, docs?: StoryDocs): void;
440
+ (text: string, children: DocEntry[]): void;
408
441
  <T>(text: string, body: () => T): T;
409
442
  };
410
443
  execute: {
411
444
  (text: string, docs?: StoryDocs): void;
445
+ (text: string, children: DocEntry[]): void;
412
446
  <T>(text: string, body: () => T): T;
413
447
  };
414
448
  action: {
415
449
  (text: string, docs?: StoryDocs): void;
450
+ (text: string, children: DocEntry[]): void;
416
451
  <T>(text: string, body: () => T): T;
417
452
  };
418
453
  verify: {
419
454
  (text: string, docs?: StoryDocs): void;
455
+ (text: string, children: DocEntry[]): void;
420
456
  <T>(text: string, body: () => T): T;
421
457
  };
422
458
  note: typeof note;
@@ -431,6 +467,7 @@ declare const story: {
431
467
  tag: typeof tag;
432
468
  custom: typeof custom;
433
469
  attach: typeof attach;
470
+ attachSpans: typeof attachSpans;
434
471
  fn: typeof fn;
435
472
  expect: typeof storyExpect;
436
473
  startTimer: typeof startTimer;
@@ -485,4 +522,4 @@ declare class StoryReporter {
485
522
  constructor();
486
523
  }
487
524
 
488
- export { type Story, type StoryDocs, type StoryOptions, StoryReporter, type VitestSuite, type VitestTask, story };
525
+ export { type Story, type StoryDocs, type StoryOptions, StoryReporter, type TicketInput, type VitestSuite, type VitestTask, story };
package/dist/index.js CHANGED
@@ -41,7 +41,8 @@ function extractSuitePath(task) {
41
41
  }
42
42
  function normalizeTickets(ticket) {
43
43
  if (!ticket) return void 0;
44
- return Array.isArray(ticket) ? ticket : [ticket];
44
+ const arr = Array.isArray(ticket) ? ticket : [ticket];
45
+ return arr.map((t) => typeof t === "string" ? { id: t } : t);
45
46
  }
46
47
  function convertStoryDocsToEntries(docs) {
47
48
  const entries = [];
@@ -156,7 +157,7 @@ function init(task, options) {
156
157
  if (options?.tags?.length) span.setAttribute("story.tags", options.tags);
157
158
  if (options?.ticket) {
158
159
  const tickets = Array.isArray(options.ticket) ? options.ticket : [options.ticket];
159
- span.setAttribute("story.tickets", tickets);
160
+ span.setAttribute("story.tickets", tickets.map((t) => typeof t === "string" ? t : t.id));
160
161
  }
161
162
  }
162
163
  } catch {
@@ -177,17 +178,37 @@ function createStepMarker(keyword) {
177
178
  function stepMarker(text, docsOrBody) {
178
179
  const ctx = getContext();
179
180
  const isCallback = typeof docsOrBody === "function";
181
+ const isChildrenArray = Array.isArray(docsOrBody);
180
182
  const resolvedKeyword = (keyword === "Given" || keyword === "When" || keyword === "Then") && ctx.meta.steps.some((s) => s.keyword === keyword) ? "And" : keyword;
183
+ let stepDocs = [];
184
+ if (!isCallback && !isChildrenArray && docsOrBody) {
185
+ stepDocs = convertStoryDocsToEntries(docsOrBody);
186
+ }
181
187
  const step = {
182
188
  id: `step-${ctx.stepCounter++}`,
183
189
  keyword: resolvedKeyword,
184
190
  text,
185
- docs: !isCallback && docsOrBody ? convertStoryDocsToEntries(docsOrBody) : [],
191
+ docs: stepDocs,
186
192
  ...isCallback ? { wrapped: true } : {}
187
193
  };
188
194
  ctx.meta.steps.push(step);
189
195
  ctx.currentStep = step;
190
196
  syncMetaToTask();
197
+ if (isChildrenArray) {
198
+ const children = docsOrBody;
199
+ if (children.length > 0) {
200
+ const childSet = new Set(children);
201
+ ctx.meta.docs = (ctx.meta.docs ?? []).filter((d) => !childSet.has(d));
202
+ for (const prevStep of ctx.meta.steps) {
203
+ if (prevStep !== step && prevStep.docs) {
204
+ prevStep.docs = prevStep.docs.filter((d) => !childSet.has(d));
205
+ }
206
+ }
207
+ step.docs = [...step.docs ?? [], ...children];
208
+ }
209
+ syncMetaToTask();
210
+ return;
211
+ }
191
212
  if (!isCallback) return;
192
213
  const body = docsOrBody;
193
214
  const start = performance.now();
@@ -218,19 +239,20 @@ function createStepMarker(keyword) {
218
239
  }
219
240
  return stepMarker;
220
241
  }
221
- function note(text) {
222
- const ctx = getContext();
223
- const entry = { kind: "note", text, phase: "runtime" };
224
- if (ctx.currentStep) {
225
- ctx.currentStep.docs ??= [];
226
- ctx.currentStep.docs.push(entry);
227
- } else {
228
- ctx.meta.docs ??= [];
229
- ctx.meta.docs.push(entry);
230
- }
242
+ function note(text, children) {
243
+ return attachDoc({ kind: "note", text, phase: "runtime" }, children);
231
244
  }
232
- function attachDoc(entry) {
245
+ function attachDoc(entry, children) {
233
246
  const ctx = getContext();
247
+ if (children && children.length > 0) {
248
+ entry.children = children;
249
+ const childSet = new Set(children);
250
+ const filterDocs = (docs) => docs.filter((d) => !childSet.has(d));
251
+ ctx.meta.docs = filterDocs(ctx.meta.docs ?? []);
252
+ for (const step of ctx.meta.steps) {
253
+ if (step.docs) step.docs = filterDocs(step.docs);
254
+ }
255
+ }
234
256
  if (ctx.currentStep) {
235
257
  ctx.currentStep.docs ??= [];
236
258
  ctx.currentStep.docs.push(entry);
@@ -239,86 +261,87 @@ function attachDoc(entry) {
239
261
  ctx.meta.docs.push(entry);
240
262
  }
241
263
  syncMetaToTask();
264
+ return entry;
242
265
  }
243
- function kv(options) {
244
- attachDoc({
266
+ function kv(options, children) {
267
+ return attachDoc({
245
268
  kind: "kv",
246
269
  label: options.label,
247
270
  value: options.value,
248
271
  phase: "runtime"
249
- });
272
+ }, children);
250
273
  }
251
- function json(options) {
274
+ function json(options, children) {
252
275
  const content = JSON.stringify(options.value, null, 2);
253
- attachDoc({
276
+ return attachDoc({
254
277
  kind: "code",
255
278
  label: options.label,
256
279
  content,
257
280
  lang: "json",
258
281
  phase: "runtime"
259
- });
282
+ }, children);
260
283
  }
261
- function code(options) {
262
- attachDoc({
284
+ function code(options, children) {
285
+ return attachDoc({
263
286
  kind: "code",
264
287
  label: options.label,
265
288
  content: options.content,
266
289
  lang: options.lang,
267
290
  phase: "runtime"
268
- });
291
+ }, children);
269
292
  }
270
- function table(options) {
271
- attachDoc({
293
+ function table(options, children) {
294
+ return attachDoc({
272
295
  kind: "table",
273
296
  label: options.label,
274
297
  columns: options.columns,
275
298
  rows: options.rows,
276
299
  phase: "runtime"
277
- });
300
+ }, children);
278
301
  }
279
- function link(options) {
280
- attachDoc({
302
+ function link(options, children) {
303
+ return attachDoc({
281
304
  kind: "link",
282
305
  label: options.label,
283
306
  url: options.url,
284
307
  phase: "runtime"
285
- });
308
+ }, children);
286
309
  }
287
- function section(options) {
288
- attachDoc({
310
+ function section(options, children) {
311
+ return attachDoc({
289
312
  kind: "section",
290
313
  title: options.title,
291
314
  markdown: options.markdown,
292
315
  phase: "runtime"
293
- });
316
+ }, children);
294
317
  }
295
- function mermaid(options) {
296
- attachDoc({
318
+ function mermaid(options, children) {
319
+ return attachDoc({
297
320
  kind: "mermaid",
298
321
  code: options.code,
299
322
  title: options.title,
300
323
  phase: "runtime"
301
- });
324
+ }, children);
302
325
  }
303
- function screenshot(options) {
304
- attachDoc({
326
+ function screenshot(options, children) {
327
+ return attachDoc({
305
328
  kind: "screenshot",
306
329
  path: options.path,
307
330
  alt: options.alt,
308
331
  phase: "runtime"
309
- });
332
+ }, children);
310
333
  }
311
- function tag(name) {
334
+ function tag(name, children) {
312
335
  const names = Array.isArray(name) ? name : [name];
313
- attachDoc({ kind: "tag", names, phase: "runtime" });
336
+ return attachDoc({ kind: "tag", names, phase: "runtime" }, children);
314
337
  }
315
- function custom(options) {
316
- attachDoc({
338
+ function custom(options, children) {
339
+ return attachDoc({
317
340
  kind: "custom",
318
341
  type: options.type,
319
342
  data: options.data,
320
343
  phase: "runtime"
321
- });
344
+ }, children);
322
345
  }
323
346
  function attach(options) {
324
347
  const ctx = getContext();
@@ -404,6 +427,12 @@ function fn(keyword, text, body) {
404
427
  function storyExpect(text, body) {
405
428
  return fn("Then", text, body);
406
429
  }
430
+ function attachSpans(spans) {
431
+ const ctx = getContext();
432
+ if (ctx.taskMeta) {
433
+ ctx.taskMeta.otelSpans = spans;
434
+ }
435
+ }
407
436
  var story = {
408
437
  // Core
409
438
  init,
@@ -437,6 +466,8 @@ var story = {
437
466
  custom,
438
467
  // Attachments
439
468
  attach,
469
+ // OTel span attachment
470
+ attachSpans,
440
471
  // Step wrappers
441
472
  fn,
442
473
  expect: storyExpect,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/story-api.ts","../src/types.ts","../src/index.ts"],"sourcesContent":["/**\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 { createRequire } from 'node:module';\nimport {\n tryGetActiveOtelContext,\n resolveTraceUrl,\n} from 'executable-stories-formatters';\nimport type {\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryMeta,\n StoryOptions,\n StoryStep,\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}\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// 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 format.\n */\nfunction normalizeTickets(\n ticket: string | string[] | undefined,\n): string[] | undefined {\n if (!ticket) return undefined;\n return Array.isArray(ticket) ? ticket : [ticket];\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 // 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 // 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 */\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 meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // OTel bridge: detect active span, flow data bidirectionally\n const otelCtx = tryGetActiveOtelContext();\n if (otelCtx) {\n // OTel -> Story: capture traceId in structured meta\n meta.meta = { ...meta.meta, otel: { traceId: otelCtx.traceId, spanId: otelCtx.spanId } };\n\n // OTel -> Story: inject human-readable doc entries\n meta.docs = meta.docs ?? [];\n meta.docs.push({ kind: 'kv', label: 'Trace ID', value: otelCtx.traceId, phase: 'runtime' });\n\n const template = options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n const url = resolveTraceUrl(template, otelCtx.traceId);\n if (url) {\n meta.docs.push({ kind: 'link', label: 'View Trace', url, phase: 'runtime' });\n }\n\n // Story -> OTel: enrich active span with story attributes\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\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);\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\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 };\n}\n\n// ============================================================================\n// Step Markers\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<T>(text: string, body: () => T): T;\n function stepMarker<T>(text: string, docsOrBody?: StoryDocs | (() => T)): T | void {\n const ctx = getContext();\n const isCallback = typeof docsOrBody === 'function';\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 const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: (!isCallback && docsOrBody) ? convertStoryDocsToEntries(docsOrBody) : [],\n ...(isCallback ? { wrapped: true } : {}),\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncMetaToTask();\n\n if (!isCallback) return;\n\n const body = docsOrBody as () => T;\n const start = performance.now();\n\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; syncMetaToTask(); return val; },\n (err) => { step.durationMs = performance.now() - start; syncMetaToTask(); throw err; },\n ) as T;\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 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): void {\n const ctx = getContext();\n const entry: DocEntry = { kind: 'note', text, phase: 'runtime' };\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}\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 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 custom() - custom doc entry */\ninterface CustomOptions {\n type: string;\n data: unknown;\n}\n\n// ============================================================================\n// Helper to attach doc entry to current step or story-level\n// ============================================================================\n\nfunction attachDoc(entry: DocEntry): void {\n const ctx = getContext();\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}\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): void {\n attachDoc({\n kind: 'kv',\n label: options.label,\n value: options.value,\n phase: 'runtime',\n });\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): void {\n const content = JSON.stringify(options.value, null, 2);\n attachDoc({\n kind: 'code',\n label: options.label,\n content,\n lang: 'json',\n phase: 'runtime',\n });\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): void {\n attachDoc({\n kind: 'code',\n label: options.label,\n content: options.content,\n lang: options.lang,\n phase: 'runtime',\n });\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): void {\n attachDoc({\n kind: 'table',\n label: options.label,\n columns: options.columns,\n rows: options.rows,\n phase: 'runtime',\n });\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): void {\n attachDoc({\n kind: 'link',\n label: options.label,\n url: options.url,\n phase: 'runtime',\n });\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): void {\n attachDoc({\n kind: 'section',\n title: options.title,\n markdown: options.markdown,\n phase: 'runtime',\n });\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): void {\n attachDoc({\n kind: 'mermaid',\n code: options.code,\n title: options.title,\n phase: 'runtime',\n });\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): void {\n attachDoc({\n kind: 'screenshot',\n path: options.path,\n alt: options.alt,\n phase: 'runtime',\n });\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[]): void {\n const names = Array.isArray(name) ? name : [name];\n attachDoc({ kind: 'tag', names, phase: 'runtime' });\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): void {\n attachDoc({\n kind: 'custom',\n type: options.type,\n data: options.data,\n phase: 'runtime',\n });\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// 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\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 code,\n table,\n link,\n section,\n mermaid,\n screenshot,\n tag,\n custom,\n\n // Attachments\n attach,\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-formatters — 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 formatters\nexport type {\n StepKeyword,\n StepMode,\n DocPhase,\n DocEntry,\n StoryStep,\n StoryMeta,\n} from 'executable-stories-formatters';\n\nexport { STORY_META_KEY } from 'executable-stories-formatters';\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 /** 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 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 */\nexport interface StoryOptions {\n /** Tags for filtering and categorizing stories */\n tags?: string[];\n /** Ticket/issue reference(s) for requirements traceability */\n ticket?: string | 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 * 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, import StoryReporter from \"executable-stories-vitest/reporter\":\n *\n * @example\n * ```ts\n * import { defineConfig } from \"vitest/config\";\n * import { StoryReporter } from \"executable-stories-vitest/reporter\";\n *\n * export default defineConfig({\n * test: {\n * reporters: [\"default\", new StoryReporter()],\n * },\n * });\n * ```\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 StepKeyword,\n StepMode,\n DocPhase,\n StoryDocs,\n StoryOptions,\n VitestTask,\n VitestSuite,\n} from './types';\n\nexport { STORY_META_KEY } from './types';\n\n// Reporter types (actual reporter is in /reporter subpath)\nexport type {\n StoryReporterOptions,\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} 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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/story-api.ts","../src/types.ts","../src/index.ts"],"sourcesContent":["/**\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 { createRequire } from 'node:module';\nimport {\n tryGetActiveOtelContext,\n resolveTraceUrl,\n} from 'executable-stories-formatters';\nimport type {\n DocEntry,\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}\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// 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 // 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 // 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 */\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 meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // OTel bridge: detect active span, flow data bidirectionally\n const otelCtx = tryGetActiveOtelContext();\n if (otelCtx) {\n // OTel -> Story: capture traceId in structured meta\n meta.meta = { ...meta.meta, otel: { traceId: otelCtx.traceId, spanId: otelCtx.spanId } };\n\n // OTel -> Story: inject human-readable doc entries\n meta.docs = meta.docs ?? [];\n meta.docs.push({ kind: 'kv', label: 'Trace ID', value: otelCtx.traceId, phase: 'runtime' });\n\n const template = options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n const url = resolveTraceUrl(template, otelCtx.traceId);\n if (url) {\n meta.docs.push({ kind: 'link', label: 'View Trace', url, phase: 'runtime' });\n }\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\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 };\n}\n\n// ============================================================================\n// Step Markers\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 ctx.meta.steps.push(step);\n ctx.currentStep = step;\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\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; syncMetaToTask(); return val; },\n (err) => { step.durationMs = performance.now() - start; syncMetaToTask(); throw err; },\n ) as T;\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 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 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 custom() - custom doc entry */\ninterface CustomOptions {\n type: string;\n data: unknown;\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/**\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 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 * @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);\n * ```\n */\nfunction attachSpans(spans: ReadonlyArray<Record<string, unknown>>): void {\n const ctx = getContext();\n if (ctx.taskMeta) {\n ctx.taskMeta.otelSpans = spans;\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\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 code,\n table,\n link,\n section,\n mermaid,\n screenshot,\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-formatters — 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 formatters\nexport type {\n StepKeyword,\n StepMode,\n DocPhase,\n DocEntry,\n StoryStep,\n StoryMeta,\n NormalizedTicket,\n} from 'executable-stories-formatters';\n\nexport { STORY_META_KEY } from 'executable-stories-formatters';\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 /** 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 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 */\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 /** 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 * 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, import StoryReporter from \"executable-stories-vitest/reporter\":\n *\n * @example\n * ```ts\n * import { defineConfig } from \"vitest/config\";\n * import { StoryReporter } from \"executable-stories-vitest/reporter\";\n *\n * export default defineConfig({\n * test: {\n * reporters: [\"default\", new StoryReporter()],\n * },\n * });\n * ```\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 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 (actual reporter is in /reporter subpath)\nexport type {\n StoryReporterOptions,\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} 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"],"mappings":"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","names":[]}