{"version":3,"file":"matcher-logic-07fFOz7r.cjs","names":[],"sources":["../../../../../../ai/src/contracts/end.type.ts","../../../../../../ai/src/testing/matcher-logic.ts"],"sourcesContent":["/**\n * Framework-global termination sentinel. Emitted by any primitive\n * whose control flow is routed through JSON-serializable strings —\n * supervisor (router agent's `next`, deterministic `route` callback),\n * future planner (plan steps), future orchestrator (session\n * directive), any future primitive that needs to say \"stop\" across a\n * wire that can't carry `null`.\n *\n * **Why one shared literal.** Consumers learn one word. Cross-\n * primitive pipes — a planner feeding a supervisor, a supervisor\n * nested as a tool inside an orchestrator — can pass the sentinel\n * through without translation.\n *\n * **Why this specific string.** Brand-prefixed (`__warlock:`) so it\n * can't collide with a realistic user-chosen intent / step / route\n * key (`\"end\"`, `\"done\"`, `\"stop\"` are all valid user keys).\n * Underscore-surrounded so it stands out in logs and snapshots.\n * JSON-safe so a router agent emits it verbatim.\n *\n * **Not used by every primitive.** Workflow's `nextStep` returns\n * `null` to end — a callback-level mechanism that predates this\n * sentinel. Workflows keep `null`; the sentinel is for primitives\n * whose \"end\" must survive a JSON boundary.\n *\n * @example\n * import { END } from \"@warlock.js/ai\";\n *\n * // Supervisor route callback\n * ai.supervisor({\n *   intents: { writer, critic },\n *   route: (ctx) => (ctx.iteration >= 5 ? END : \"writer\"),\n * });\n *\n * // Router agent output schema\n * const router = ai.agent({\n *   model,\n *   output: z.object({\n *     next: z.union([z.string(), z.array(z.string()), z.literal(END)]),\n *   }),\n * });\n */\nexport const END = \"__warlock:end__\" as const;\n\n/**\n * Value type of `END`. Exposed so consumers building Zod / Standard\n * Schema output schemas can write `z.literal(END)` without\n * hard-coding the string, and so generic helpers that accept \"the\n * end sentinel\" can type-narrow correctly.\n */\nexport type EndSentinel = typeof END;\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { END } from \"../contracts/end.type\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport type { SupervisorReport } from \"../contracts/result/supervisor-result.type\";\nimport type { WorkflowReport } from \"../contracts/result/workflow-result.type\";\n\n/**\n * Normalized matcher verdict — the library-agnostic shape every\n * matcher in this module returns. Vitest's `expect.extend` consumes\n * the same `{ pass, message }` contract, so the registration layer\n * forwards these verbatim.\n */\nexport type MatcherVerdict = {\n  /** Whether the assertion passed. */\n  pass: boolean;\n  /** Lazy message factory — Vitest calls it only when reporting. */\n  message: () => string;\n};\n\n/**\n * Any unified result envelope a matcher can be handed. Accepts the\n * full `{ report, ... }` result or a bare `BaseReport` so callers can\n * assert against either `await x.execute()` or `result.report`.\n */\ntype ReportLike = BaseReport | { report: BaseReport };\n\n/**\n * Coerce a matcher target into its `BaseReport`. Accepts the result\n * envelope (`{ report }`) or a report directly.\n */\nfunction toReport(received: ReportLike): BaseReport {\n  if (\"report\" in received && received.report) {\n    return received.report;\n  }\n\n  return received as BaseReport;\n}\n\n/** Narrow a `BaseReport` to a `SupervisorReport` by its discriminator. */\nfunction asSupervisorReport(report: BaseReport): SupervisorReport | undefined {\n  return report.type === \"supervisor\" ? (report as SupervisorReport) : undefined;\n}\n\n/** Narrow a `BaseReport` to a `WorkflowReport` by its discriminator. */\nfunction asWorkflowReport(report: BaseReport): WorkflowReport | undefined {\n  return report.type === \"workflow\" ? (report as WorkflowReport) : undefined;\n}\n\n/**\n * Collect every intent name dispatched by a supervisor across all\n * iterations — the keys of each iteration snapshot's `result` record.\n */\nfunction dispatchedIntents(report: SupervisorReport): string[] {\n  const intents = new Set<string>();\n\n  for (const snapshot of report.snapshots) {\n    for (const intent of Object.keys(snapshot.result)) {\n      intents.add(intent);\n    }\n  }\n\n  return [...intents];\n}\n\n/**\n * Assert that a supervisor routed to (dispatched) the named intent at\n * least once across its iterations. Targets a `SupervisorResult` or a\n * `SupervisorReport`.\n *\n * @example\n * expect(await supervisor.execute(input)).toRouteTo(\"critic\");\n */\nexport function matchRouteTo(received: ReportLike, intent: string): MatcherVerdict {\n  const report = toReport(received);\n  const supervisorReport = asSupervisorReport(report);\n\n  if (!supervisorReport) {\n    return {\n      pass: false,\n      message: () =>\n        `toRouteTo expects a supervisor result, but received a \"${report.type}\" report`,\n    };\n  }\n\n  const intents = dispatchedIntents(supervisorReport);\n  const pass = intents.includes(intent);\n\n  return {\n    pass,\n    message: () =>\n      pass\n        ? `expected supervisor not to route to \"${intent}\", but it did`\n        : `expected supervisor to route to \"${intent}\", but it routed to [${intents.join(\", \")}]`,\n  };\n}\n\n/**\n * Assert that a supervisor converged — terminated on its own decision\n * (`router` / `route` / `evaluate` / `classifier`) with a\n * `\"completed\"` status, rather than hitting the iteration cap, being\n * cancelled, or erroring. Targets a `SupervisorResult` or\n * `SupervisorReport`.\n *\n * @example\n * expect(await supervisor.execute(input)).toConverge();\n */\nexport function matchConverge(received: ReportLike): MatcherVerdict {\n  const report = toReport(received);\n  const supervisorReport = asSupervisorReport(report);\n\n  if (!supervisorReport) {\n    return {\n      pass: false,\n      message: () =>\n        `toConverge expects a supervisor result, but received a \"${report.type}\" report`,\n    };\n  }\n\n  const nonConvergent = new Set([\"max-iterations\", \"cancelled\", \"error\"]);\n  const pass =\n    supervisorReport.status === \"completed\" &&\n    !nonConvergent.has(supervisorReport.terminatedBy);\n\n  return {\n    pass,\n    message: () =>\n      pass\n        ? `expected supervisor not to converge, but it terminated via \"${supervisorReport.terminatedBy}\"`\n        : `expected supervisor to converge, but status=\"${supervisorReport.status}\" terminatedBy=\"${supervisorReport.terminatedBy}\" after ${supervisorReport.iterations} iteration(s)`,\n  };\n}\n\n/**\n * Assert that a workflow step completed successfully. Targets a\n * `WorkflowResult` or `WorkflowReport`; looks the step up by name in\n * `report.steps` and checks its status is `\"completed\"`.\n *\n * @example\n * expect(await workflow.execute(input)).toPassStep(\"draft\");\n */\nexport function matchPassStep(received: ReportLike, stepName: string): MatcherVerdict {\n  const report = toReport(received);\n  const workflowReport = asWorkflowReport(report);\n\n  if (!workflowReport) {\n    return {\n      pass: false,\n      message: () =>\n        `toPassStep expects a workflow result, but received a \"${report.type}\" report`,\n    };\n  }\n\n  const step = workflowReport.steps[stepName];\n\n  if (!step) {\n    const known = Object.keys(workflowReport.steps).join(\", \");\n    return {\n      pass: false,\n      message: () =>\n        `expected workflow to have a step \"${stepName}\", but steps are [${known}]`,\n    };\n  }\n\n  const pass = step.status === \"completed\";\n\n  return {\n    pass,\n    message: () =>\n      pass\n        ? `expected step \"${stepName}\" not to pass, but it completed`\n        : `expected step \"${stepName}\" to pass, but its status was \"${step.status}\"`,\n  };\n}\n\n/**\n * Result envelope carrying a typed `data` payload — what\n * `matchOutputShape` validates against a schema.\n */\ntype DataResult = { data?: unknown };\n\n/**\n * Assert that a result's `data` validates against a Standard Schema.\n * Targets any result envelope with a `data` field (agent / workflow /\n * supervisor). Runs the schema's `~standard.validate` and passes only\n * when it reports no issues.\n *\n * Synchronous-only: a schema whose `validate` returns a Promise is\n * rejected with a clear message rather than silently passing — the\n * async variant belongs on a dedicated async matcher if needed.\n *\n * @example\n * expect(await agent.execute(input, { output: schema })).toOutputShape(schema);\n */\nexport function matchOutputShape(\n  received: DataResult,\n  schema: StandardSchemaV1,\n): MatcherVerdict {\n  const data = received.data;\n\n  if (data === undefined) {\n    return {\n      pass: false,\n      message: () => \"toOutputShape expected result.data to be defined, but it was undefined\",\n    };\n  }\n\n  const validation = schema[\"~standard\"].validate(data);\n\n  if (validation instanceof Promise) {\n    return {\n      pass: false,\n      message: () =>\n        \"toOutputShape received an async schema; use a synchronous Standard Schema for this matcher\",\n    };\n  }\n\n  const pass = validation.issues === undefined;\n\n  return {\n    pass,\n    message: () => {\n      if (pass) {\n        return \"expected result.data not to match the schema, but it did\";\n      }\n\n      const summary = (validation.issues ?? []).map((issue) => issue.message).join(\"; \");\n      return `expected result.data to match the schema, but validation failed: ${summary}`;\n    },\n  };\n}\n\n// Re-exported so matcher consumers and tests can reference the\n// termination sentinel without reaching into contracts.\nexport { END };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,MAAM;;;;;;;;ACXnB,SAAS,SAAS,UAAkC;CAClD,IAAI,YAAY,YAAY,SAAS,QACnC,OAAO,SAAS;CAGlB,OAAO;AACT;;AAGA,SAAS,mBAAmB,QAAkD;CAC5E,OAAO,OAAO,SAAS,eAAgB,SAA8B;AACvE;;AAGA,SAAS,iBAAiB,QAAgD;CACxE,OAAO,OAAO,SAAS,aAAc,SAA4B;AACnE;;;;;AAMA,SAAS,kBAAkB,QAAoC;CAC7D,MAAM,0BAAU,IAAI,IAAY;CAEhC,KAAK,MAAM,YAAY,OAAO,WAC5B,KAAK,MAAM,UAAU,OAAO,KAAK,SAAS,MAAM,GAC9C,QAAQ,IAAI,MAAM;CAItB,OAAO,CAAC,GAAG,OAAO;AACpB;;;;;;;;;AAUA,SAAgB,aAAa,UAAsB,QAAgC;CACjF,MAAM,SAAS,SAAS,QAAQ;CAChC,MAAM,mBAAmB,mBAAmB,MAAM;CAElD,IAAI,CAAC,kBACH,OAAO;EACL,MAAM;EACN,eACE,0DAA0D,OAAO,KAAK;CAC1E;CAGF,MAAM,UAAU,kBAAkB,gBAAgB;CAClD,MAAM,OAAO,QAAQ,SAAS,MAAM;CAEpC,OAAO;EACL;EACA,eACE,OACI,wCAAwC,OAAO,iBAC/C,oCAAoC,OAAO,uBAAuB,QAAQ,KAAK,IAAI,EAAE;CAC7F;AACF;;;;;;;;;;;AAYA,SAAgB,cAAc,UAAsC;CAClE,MAAM,SAAS,SAAS,QAAQ;CAChC,MAAM,mBAAmB,mBAAmB,MAAM;CAElD,IAAI,CAAC,kBACH,OAAO;EACL,MAAM;EACN,eACE,2DAA2D,OAAO,KAAK;CAC3E;CAGF,MAAM,gBAAgB,IAAI,IAAI;EAAC;EAAkB;EAAa;CAAO,CAAC;CACtE,MAAM,OACJ,iBAAiB,WAAW,eAC5B,CAAC,cAAc,IAAI,iBAAiB,YAAY;CAElD,OAAO;EACL;EACA,eACE,OACI,+DAA+D,iBAAiB,aAAa,KAC7F,gDAAgD,iBAAiB,OAAO,kBAAkB,iBAAiB,aAAa,UAAU,iBAAiB,WAAW;CACtK;AACF;;;;;;;;;AAUA,SAAgB,cAAc,UAAsB,UAAkC;CACpF,MAAM,SAAS,SAAS,QAAQ;CAChC,MAAM,iBAAiB,iBAAiB,MAAM;CAE9C,IAAI,CAAC,gBACH,OAAO;EACL,MAAM;EACN,eACE,yDAAyD,OAAO,KAAK;CACzE;CAGF,MAAM,OAAO,eAAe,MAAM;CAElC,IAAI,CAAC,MAAM;EACT,MAAM,QAAQ,OAAO,KAAK,eAAe,KAAK,CAAC,CAAC,KAAK,IAAI;EACzD,OAAO;GACL,MAAM;GACN,eACE,qCAAqC,SAAS,oBAAoB,MAAM;EAC5E;CACF;CAEA,MAAM,OAAO,KAAK,WAAW;CAE7B,OAAO;EACL;EACA,eACE,OACI,kBAAkB,SAAS,mCAC3B,kBAAkB,SAAS,iCAAiC,KAAK,OAAO;CAChF;AACF;;;;;;;;;;;;;;AAqBA,SAAgB,iBACd,UACA,QACgB;CAChB,MAAM,OAAO,SAAS;CAEtB,IAAI,SAAS,QACX,OAAO;EACL,MAAM;EACN,eAAe;CACjB;CAGF,MAAM,aAAa,OAAO,YAAY,CAAC,SAAS,IAAI;CAEpD,IAAI,sBAAsB,SACxB,OAAO;EACL,MAAM;EACN,eACE;CACJ;CAGF,MAAM,OAAO,WAAW,WAAW;CAEnC,OAAO;EACL;EACA,eAAe;GACb,IAAI,MACF,OAAO;GAIT,OAAO,qEADU,WAAW,UAAU,CAAC,EAAC,CAAE,KAAK,UAAU,MAAM,OAAO,CAAC,CAAC,KAAK,IACI;EACnF;CACF;AACF"}