{"version":3,"file":"agent/run-gherkin-scenario.mjs","sources":["../../../src/agent/run-gherkin-scenario.ts"],"sourcesContent":["import type { AiActOptions } from './agent';\n\nexport type GherkinStepKeyword = 'Given' | 'When' | 'Then' | 'And' | 'But';\n\ntype RunGherkinScenarioStepAction = 'aiAct' | 'aiAssert';\n\nexport type RunGherkinScenarioOptions = AiActOptions & {\n  context?: string;\n};\n\ntype GherkinPrimaryKeyword = 'Given' | 'When' | 'Then';\n\ntype ParsedGherkinStep = {\n  keyword: GherkinStepKeyword;\n  effectiveKeyword: GherkinPrimaryKeyword;\n  text: string;\n  lineNumber: number;\n};\n\ntype RawGherkinStep = {\n  keyword: GherkinStepKeyword;\n  text: string;\n  lineNumber: number;\n};\n\ntype ParsedGherkinScenario = {\n  scenario?: string;\n  steps: ParsedGherkinStep[];\n};\n\ntype GherkinScenarioAgent = {\n  aiAct: (taskPrompt: string, opt?: AiActOptions) => Promise<unknown>;\n  aiAssert: (\n    assertion: string,\n    msg?: string,\n    opt?: { context?: string; abortSignal?: AbortSignal },\n  ) => Promise<unknown>;\n};\n\nconst stepKeywordPattern = /^(Given|When|Then|And|But)\\s+(.+)$/i;\nconst headerPattern = /^(Scenario):\\s*(.*)$/i;\nconst unsupportedHeaderPattern =\n  /^(Feature|Background|Scenario Outline|Scenario Template|Examples|Rule):/i;\n\nconst normalizeStepKeyword = (keyword: string): GherkinStepKeyword => {\n  const lowerKeyword = keyword.toLowerCase();\n  if (lowerKeyword === 'given') return 'Given';\n  if (lowerKeyword === 'when') return 'When';\n  if (lowerKeyword === 'then') return 'Then';\n  if (lowerKeyword === 'and') return 'And';\n  return 'But';\n};\n\nconst isPrimaryKeyword = (\n  keyword: GherkinStepKeyword,\n): keyword is GherkinPrimaryKeyword => {\n  return keyword === 'Given' || keyword === 'When' || keyword === 'Then';\n};\n\nconst resolveSteps = (steps: RawGherkinStep[]): ParsedGherkinStep[] => {\n  let previousPrimaryKeyword: GherkinPrimaryKeyword | undefined;\n\n  return steps.map((step) => {\n    const effectiveKeyword = isPrimaryKeyword(step.keyword)\n      ? step.keyword\n      : previousPrimaryKeyword;\n\n    if (!effectiveKeyword) {\n      throw new Error(\n        `runGherkinScenario cannot resolve \"${step.keyword}\" at line ${step.lineNumber}; use Given, When, or Then before ${step.keyword}.`,\n      );\n    }\n\n    previousPrimaryKeyword = effectiveKeyword;\n\n    return {\n      ...step,\n      effectiveKeyword,\n    };\n  });\n};\n\nconst throwIfAborted = (abortSignal: AbortSignal | undefined) => {\n  if (!abortSignal?.aborted) {\n    return;\n  }\n\n  if (typeof abortSignal.throwIfAborted === 'function') {\n    abortSignal.throwIfAborted();\n  }\n\n  throw new Error(\n    `runGherkinScenario aborted: ${abortSignal.reason || 'signal already aborted'}`,\n  );\n};\n\nexport const parseGherkinScenario = (\n  scenarioText: string,\n): ParsedGherkinScenario => {\n  const lines = scenarioText.split(/\\r?\\n/);\n  const scenarioSteps: RawGherkinStep[] = [];\n  const anonymousSteps: RawGherkinStep[] = [];\n  let scenario: string | undefined;\n  let scenarioCount = 0;\n  let section: 'prelude' | 'scenario' = 'prelude';\n\n  for (const [lineIndex, rawLine] of lines.entries()) {\n    const lineNumber = lineIndex + 1;\n    const line = rawLine.trim();\n\n    if (!line || line.startsWith('#') || line.startsWith('@')) {\n      continue;\n    }\n\n    if (line === '\"\"\"' || line === \"'''\") {\n      throw new Error(\n        `runGherkinScenario does not support doc strings; found one at line ${lineNumber}.`,\n      );\n    }\n\n    if (line.startsWith('|')) {\n      throw new Error(\n        `runGherkinScenario does not support data tables; found one at line ${lineNumber}.`,\n      );\n    }\n\n    if (unsupportedHeaderPattern.test(line)) {\n      throw new Error(\n        `runGherkinScenario does not support \"${line}\" at line ${lineNumber}.`,\n      );\n    }\n\n    const headerMatch = line.match(headerPattern);\n    if (headerMatch) {\n      scenarioCount += 1;\n      scenario = headerMatch[2].trim() || undefined;\n      section = 'scenario';\n      continue;\n    }\n\n    const stepMatch = line.match(stepKeywordPattern);\n    if (stepMatch) {\n      const step = {\n        keyword: normalizeStepKeyword(stepMatch[1]),\n        text: stepMatch[2].trim(),\n        lineNumber,\n      };\n\n      if (section === 'scenario') {\n        scenarioSteps.push(step);\n      } else {\n        anonymousSteps.push(step);\n      }\n\n      continue;\n    }\n\n    throw new Error(\n      `runGherkinScenario does not support content at line ${lineNumber}: ${line}`,\n    );\n  }\n\n  if (scenarioCount > 1) {\n    throw new Error(\n      `runGherkinScenario expects exactly one Scenario, but found ${scenarioCount}.`,\n    );\n  }\n\n  if (scenarioCount === 1 && anonymousSteps.length > 0) {\n    throw new Error(\n      'runGherkinScenario cannot mix anonymous steps with a Scenario block.',\n    );\n  }\n\n  const rawSteps = scenarioCount === 1 ? scenarioSteps : anonymousSteps;\n\n  if (rawSteps.length === 0) {\n    throw new Error('runGherkinScenario requires at least one Gherkin step.');\n  }\n\n  return {\n    scenario,\n    steps: resolveSteps(rawSteps),\n  };\n};\n\nconst buildStepPrompt = (step: ParsedGherkinStep) => {\n  if (step.effectiveKeyword === 'Given') {\n    return `Set up this precondition: ${step.text}`;\n  }\n\n  if (step.effectiveKeyword === 'When') {\n    return `Perform this user action: ${step.text}`;\n  }\n\n  return `Verify that ${step.text}`;\n};\n\nconst describeStepExecution = (\n  step: ParsedGherkinStep,\n  action: RunGherkinScenarioStepAction,\n) => {\n  const keywordMapping =\n    step.keyword === step.effectiveKeyword\n      ? step.keyword\n      : `${step.keyword} as ${step.effectiveKeyword}`;\n\n  if (step.effectiveKeyword === 'Given') {\n    return `setting up the precondition (${keywordMapping} -> ${action})`;\n  }\n\n  if (step.effectiveKeyword === 'When') {\n    return `performing the user action (${keywordMapping} -> ${action})`;\n  }\n\n  return `verifying the expected result (${keywordMapping} -> ${action})`;\n};\n\nconst describeErrorCause = (error: unknown) => {\n  if (error instanceof Error && error.message) {\n    return ` Original error: ${error.message}`;\n  }\n\n  return '';\n};\n\nexport const runGherkinScenario = async (\n  agent: GherkinScenarioAgent,\n  scenarioText: string,\n  opt?: RunGherkinScenarioOptions,\n): Promise<void> => {\n  const parsedScenario = parseGherkinScenario(scenarioText);\n  const aiActOptions: AiActOptions = {\n    ...opt,\n    cacheable: false,\n  };\n\n  for (const step of parsedScenario.steps) {\n    throwIfAborted(opt?.abortSignal);\n\n    const action: RunGherkinScenarioStepAction =\n      step.effectiveKeyword === 'Then' ? 'aiAssert' : 'aiAct';\n    const prompt = buildStepPrompt(step);\n\n    try {\n      if (action === 'aiAct') {\n        await agent.aiAct(prompt, aiActOptions);\n      } else if (opt?.context || opt?.abortSignal) {\n        await agent.aiAssert(prompt, undefined, {\n          context: opt.context,\n          abortSignal: opt.abortSignal,\n        });\n      } else {\n        await agent.aiAssert(prompt);\n      }\n    } catch (error) {\n      throw new Error(\n        `runGherkinScenario failed while ${describeStepExecution(\n          step,\n          action,\n        )} at line ${step.lineNumber}: ${step.keyword} ${step.text}.${describeErrorCause(\n          error,\n        )}`,\n        {\n          cause: error,\n        },\n      );\n    }\n  }\n};\n"],"names":["stepKeywordPattern","headerPattern","unsupportedHeaderPattern","normalizeStepKeyword","keyword","lowerKeyword","isPrimaryKeyword","resolveSteps","steps","previousPrimaryKeyword","step","effectiveKeyword","Error","throwIfAborted","abortSignal","parseGherkinScenario","scenarioText","lines","scenarioSteps","anonymousSteps","scenario","scenarioCount","section","lineIndex","rawLine","lineNumber","line","headerMatch","undefined","stepMatch","rawSteps","buildStepPrompt","describeStepExecution","action","keywordMapping","describeErrorCause","error","runGherkinScenario","agent","opt","parsedScenario","aiActOptions","prompt"],"mappings":"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"}