> ## Documentation Index
> Fetch the complete documentation index at: https://smithers-feat-claude-workflow-mirror.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# <CheckSuite>

> Parallel checks with auto-aggregated pass/fail verdict.

```ts theme={null}
// Props
import { CheckSuite } from "smithers-orchestrator";

type CheckConfig = { id: string; agent?: AgentLike; command?: string; label?: string };

type CheckSuiteProps = {
  id?: string;                                              // default: "checksuite"
  checks: CheckConfig[] | Record<string, Omit<CheckConfig, "id">>;
  verdictOutput: OutputTarget;
  strategy?: "all-pass" | "majority" | "any-pass";          // default: "all-pass"
  maxConcurrency?: number;                                  // default: Infinity
  continueOnFail?: boolean;                                 // default: true
  skipIf?: boolean;
};
```

```tsx theme={null}
<Workflow name="ci-checks">
  <CheckSuite
    checks={[
      { id: "lint", agent: lintAgent, label: "ESLint" },
      { id: "typecheck", agent: typecheckAgent, label: "TypeScript" },
      { id: "test", agent: testAgent, label: "Unit Tests" },
    ]}
    verdictOutput={outputs.verdict}
    strategy="all-pass"
  />
</Workflow>
```

## Notes

* Check task ids are `{prefix}-{checkId}`; verdict is `{prefix}-verdict`.
* `strategy` is evaluated in pure code: `all-pass` requires every check to pass, `majority` requires more than half (`passCount*2 > total`), and `any-pass` requires at least one to pass.
* Use `command` instead of `agent` for shell-based checks.

## Source

The `<CheckSuite>` implementation and the files it imports, straight from the package source. This section is generated; edit the source, not this block.

<CodeGroup>
  ```js CheckSuite.js theme={null}
  // @smithers-type-exports-begin
  /** @typedef {import("./CheckSuiteProps.ts").CheckSuiteProps} CheckSuiteProps */
  // @smithers-type-exports-end

  import React from "react";
  import { SmithersContext } from "@smithers-orchestrator/react-reconciler/context";
  import { Sequence } from "./Sequence.js";
  import { Parallel } from "./Parallel.js";
  import { Task } from "./Task.js";
  /** @typedef {import("./CheckConfig.ts").CheckConfig} CheckConfig */

  /**
   * Whether a single check's output row counts as a pass. A missing row (the
   * check never produced output) or an explicit failure signal counts as a fail.
   * @param {unknown} row
   * @returns {boolean}
   */
  function checkPassed(row) {
      if (row == null)
          return false;
      if (typeof row === "object") {
          const r = /** @type {Record<string, unknown>} */ (row);
          if (r.passed === false || r.ok === false || r.failed === true)
              return false;
          if (r.error != null && r.error !== false)
              return false;
      }
      return true;
  }

  /**
   * Resolve the overall pass/fail verdict from the per-check pass count.
   * @param {"all-pass" | "majority" | "any-pass"} strategy
   * @param {number} passCount
   * @param {number} total
   * @returns {boolean}
   */
  function resolveVerdict(strategy, passCount, total) {
      if (strategy === "any-pass")
          return passCount > 0;
      if (strategy === "majority")
          return passCount * 2 > total;
      return total > 0 && passCount === total;
  }

  /**
   * @param {CheckConfig[] | Record<string, Omit<CheckConfig, "id">>} checks
   * @returns {CheckConfig[]}
   */
  function normalizeChecks(checks) {
      if (Array.isArray(checks))
          return checks;
      return Object.entries(checks).map(([key, cfg]) => ({
          id: key,
          ...cfg,
      }));
  }
  /**
   * <CheckSuite> — Parallel checks with auto-aggregated pass/fail verdict.
   *
   * Composes: Sequence > Parallel[Task per check] > Task(verdict aggregator)
   * @param {CheckSuiteProps} props
   */
  export function CheckSuite(props) {
      if (props.skipIf)
          return null;
      const ctx = React.useContext(SmithersContext);
      const { id, checks, verdictOutput, strategy = "all-pass", maxConcurrency, continueOnFail = true, } = props;
      const prefix = id ?? "checksuite";
      const normalized = normalizeChecks(checks);
      // Build parallel check tasks
      const checkTasks = normalized.map((check) => {
          const taskId = `${prefix}-${check.id}`;
          const childContent = check.command
              ? `Run check: ${check.command}`
              : `Run check: ${check.label ?? check.id}`;
          const taskProps = {
              key: taskId,
              id: taskId,
              output: verdictOutput,
              continueOnFail,
              label: check.label ?? check.id,
          };
          if (check.agent) {
              taskProps.agent = check.agent;
          }
          return React.createElement(Task, taskProps, childContent);
      });
      const parallelEl = React.createElement(Parallel, { maxConcurrency }, ...checkTasks);
      // The verdict depends on every check. We use dependsOn (the mechanism the
      // graph extractor honors) so the verdict only runs once all checks have
      // produced output — a `needs` map alone is ignored when no `deps` are set.
      const checkIds = normalized.map((check) => `${prefix}-${check.id}`);
      // Compute the aggregate verdict from the per-check outputs. Reads are taken
      // from the workflow context at render time and captured in the closure; the
      // component re-renders reactively as each check's output becomes available,
      // and the engine defers execution until every dependency has completed.
      const verdictTask = React.createElement(Task, {
          id: `${prefix}-verdict`,
          output: verdictOutput,
          dependsOn: checkIds,
          label: "verdict",
      }, () => {
          let passCount = 0;
          const results = {};
          for (const check of normalized) {
              const checkId = `${prefix}-${check.id}`;
              const row = ctx?.outputMaybe(verdictOutput, { nodeId: checkId });
              const passed = checkPassed(row);
              results[check.id] = passed;
              if (passed)
                  passCount += 1;
          }
          const total = normalized.length;
          return {
              passed: resolveVerdict(strategy, passCount, total),
              passCount,
              total,
              strategy,
              results,
          };
      });
      return React.createElement(Sequence, null, parallelEl, verdictTask);
  }
  ```

  ```js context.js theme={null}
  // @smithers-type-exports-begin
  /** @typedef {import("./OutputSnapshot.ts").OutputSnapshot} OutputSnapshot */
  /** @typedef {import("./SmithersCtxOptions.ts").SmithersCtxOptions} SmithersCtxOptions */
  // @smithers-type-exports-end

  import React from "react";
  import { SmithersCtx } from "@smithers-orchestrator/driver/SmithersCtx";
  import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
  export { SmithersCtx } from "@smithers-orchestrator/driver/SmithersCtx";
  /** @type {React.Context<SmithersCtx<any> | null>} */
  export const SmithersContext = React.createContext(null);
  SmithersContext.displayName = "SmithersContext";
  /**
   * @template Schema
   * @returns {{ SmithersContext: React.Context<SmithersCtx<Schema> | null>, useCtx: () => SmithersCtx<Schema> }}
   */
  export function createSmithersContext() {
      /** @type {React.Context<SmithersCtx<Schema> | null>} */
      const Context = React.createContext(null);
      Context.displayName = "SmithersContext";
      /**
     * @returns {SmithersCtx<Schema>}
     */
      function useCtx() {
          const ctx = React.useContext(Context);
          if (!ctx) {
              throw new SmithersError("CONTEXT_OUTSIDE_WORKFLOW", "useCtx() must be called inside a <Workflow> created by createSmithers()");
          }
          return ctx;
      }
      return { SmithersContext: Context, useCtx };
  }
  ```

  ```js Sequence.js theme={null}
  import React from "react";
  /** @typedef {import("./SequenceProps.ts").SequenceProps} SequenceProps */

  /**
   * @param {SequenceProps} props
   */
  export function Sequence(props) {
      if (props.skipIf)
          return null;
      // Sequence carries no host props of its own; pass an empty bag (align with
      // the sanitizing structural components) so control props don't leak through.
      return React.createElement("smithers:sequence", {}, props.children);
  }
  ```

  ```js Parallel.js theme={null}
  import React from "react";
  /** @typedef {import("./ParallelProps.ts").ParallelProps} ParallelProps */

  /**
   * @param {ParallelProps} props
   */
  export function Parallel(props) {
      if (props.skipIf)
          return null;
      // Align prop sanitization with other structural components
      const next = {
          maxConcurrency: props.maxConcurrency,
          id: props.id,
      };
      return React.createElement("smithers:parallel", next, props.children);
  }
  ```

  ```js Task.js theme={null}
  // @smithers-type-exports-begin
  /**
   * @template D
   * @typedef {import("./InferDeps.ts").InferDeps<D>} InferDeps
   */
  /** @typedef {import("./OutputTarget.ts").OutputTarget} OutputTarget */
  // @smithers-type-exports-end

  import React from "react";
  import { renderToStaticMarkup } from "react-dom/server";
  import { markdownComponents } from "../markdownComponents.js";
  import { zodSchemaToJsonExample } from "../zod-to-example.js";
  import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
  import { SmithersContext } from "@smithers-orchestrator/react-reconciler/context";
  import { AspectContext } from "../aspects/AspectContext.js";
  import { AntigravityAgent } from "@smithers-orchestrator/agents/AntigravityAgent";
  import { ClaudeCodeAgent } from "@smithers-orchestrator/agents/ClaudeCodeAgent";
  import { GeminiAgent } from "@smithers-orchestrator/agents/GeminiAgent";
  import { PiAgent } from "@smithers-orchestrator/agents/PiAgent";
  /** @typedef {import("@smithers-orchestrator/agents/AgentLike").AgentLike} AgentLike */
  /** @typedef {import("./DepsSpec.ts").DepsSpec} DepsSpec */
  /**
   * @template Row, Output, D
   * @typedef {import("./TaskProps.ts").TaskProps<Row, Output, D>} TaskProps
   */

  /**
   * Render a prompt React node to plain markdown text.
   *
   * If the prompt is a React element (e.g. a compiled MDX component), we inject
   * `markdownComponents` via the standard MDX `components` prop so that
   * renderToStaticMarkup outputs clean markdown instead of HTML.
   * No HTML tag stripping or entity decoding needed.
   * @param {unknown} prompt
   * @returns {string}
   */
  export function renderPromptToText(prompt) {
      if (prompt == null)
          return "";
      if (typeof prompt === "string")
          return prompt;
      if (typeof prompt === "number")
          return String(prompt);
      try {
          let element;
          if (React.isValidElement(prompt)) {
              // Inject markdown components into the element so MDX components
              // render fragments instead of HTML tags.
              element = React.cloneElement(prompt, {
                  components: markdownComponents,
              });
          }
          else {
              element = React.createElement(React.Fragment, null, prompt);
          }
          return renderToStaticMarkup(element)
              .replace(/\n{3,}/g, "\n\n")
              .trim();
      }
      catch (err) {
          const result = String(prompt ?? "");
          if (result === "[object Object]") {
              throw new SmithersError("MDX_PRELOAD_INACTIVE", `MDX prompt could not be rendered — the prompt resolved to [object Object] instead of a React component.\n\n` +
                  `This usually means the MDX preload is not active. Common causes:\n` +
                  `  • bunfig.toml uses [run] preload instead of top-level preload (the [run] section doesn't apply to dynamic imports)\n` +
                  `  • bunfig.toml is not in the current working directory\n` +
                  `  • mdxPlugin() is not registered in the preload script\n` +
                  `  • The MDX file is imported without a default import (use: import MyPrompt from "./prompt.mdx")\n\n` +
                  `Original error: ${err instanceof Error ? err.message : String(err)}`);
          }
          return result;
      }
  }
  /**
   * @param {unknown} value
   * @returns {value is import("zod").ZodObject<import("zod").ZodRawShape>}
   */
  function isZodObject(value) {
      return Boolean(value && typeof value === "object" && "shape" in value);
  }
  /**
   * @param {DepsSpec | undefined} deps
   * @param {Record<string, string> | undefined} needs
   * @returns {string[] | undefined}
   */
  function deriveDepNodeIds(deps, needs) {
      if (!deps)
          return undefined;
      const ids = new Set();
      for (const key of Object.keys(deps)) {
          const nodeId = needs?.[key] ?? key;
          if (nodeId)
              ids.add(nodeId);
      }
      return ids.size > 0 ? [...ids] : undefined;
  }
  /**
   * @param {string[] | undefined} dependsOn
   * @param {string[] | undefined} depNodeIds
   * @returns {string[] | undefined}
   */
  function mergeDependsOn(dependsOn, depNodeIds) {
      const merged = new Set();
      for (const id of dependsOn ?? [])
          merged.add(id);
      for (const id of depNodeIds ?? [])
          merged.add(id);
      return merged.size > 0 ? [...merged] : undefined;
  }
  /**
   * @param {any} ctx
   * @param {DepsSpec | undefined} deps
   * @param {Record<string, string> | undefined} needs
   * @returns {Record<string, unknown> | null}
   */
  function resolveDeps(ctx, deps, needs) {
      if (!deps)
          return Object.create(null);
      const keys = Object.keys(deps);
      if (keys.length === 0)
          return Object.create(null);
      const resolved = Object.create(null);
      for (const key of keys) {
          const target = deps[key];
          const nodeId = needs?.[key] ?? key;
          const value = ctx.outputMaybe(target, { nodeId });
          if (value === undefined)
              return null;
          resolved[key] = value;
      }
      return resolved;
  }
  /**
   * @param {AgentLike} agent
   * @param {string[] | undefined} allowTools
   * @returns {AgentLike}
   */
  function applyCliToolAllowlist(agent, allowTools) {
      if (!allowTools) {
          return agent;
      }
      if (agent instanceof ClaudeCodeAgent) {
          const opts = { ...agent.opts };
          if (allowTools.length === 0) {
              return new ClaudeCodeAgent({
                  ...opts,
                  allowedTools: [],
                  tools: "",
              });
          }
          return new ClaudeCodeAgent({
              ...opts,
              allowedTools: [...allowTools],
          });
      }
      if (agent instanceof PiAgent) {
          const opts = { ...agent.opts };
          if (allowTools.length === 0) {
              return new PiAgent({
                  ...opts,
                  tools: [],
                  noTools: true,
              });
          }
          return new PiAgent({
              ...opts,
              tools: [...allowTools],
              noTools: false,
          });
      }
      if (agent instanceof GeminiAgent) {
          const opts = { ...agent.opts };
          return new GeminiAgent({
              ...opts,
              allowedTools: [...allowTools],
          });
      }
      if (agent instanceof AntigravityAgent) {
          const opts = { ...agent.opts };
          return new AntigravityAgent({
              ...opts,
              allowedTools: [...allowTools],
          });
      }
      return agent;
  }
  /**
   * @param {unknown} ctx
   * @param {string[] | undefined} allowTools
   * @returns {string[] | undefined}
   */
  function resolveCliToolAllowlist(ctx, allowTools) {
      if (allowTools !== undefined) {
          return allowTools;
      }
      const cliAgentToolsDefault = ctx && typeof ctx === "object"
          ? ctx.__smithersRuntime?.cliAgentToolsDefault
          : undefined;
      return cliAgentToolsDefault === "explicit-only" ? [] : undefined;
  }
  /**
   * @template Row, Output, D
   * @param {TaskProps<Row, Output, D>} props
   * @returns {React.ReactElement | null}
   */
  export function Task(props) {
      const { children, agent, fallbackAgent, deps, ...rest } = props;
      const taskContext = props.smithersContext ?? SmithersContext;
      const ctx = React.useContext(taskContext);
      const aspectCtx = React.useContext(AspectContext);
      const depNodeIds = deriveDepNodeIds(deps, rest.needs);
      if (deps && !ctx) {
          throw new SmithersError("CONTEXT_OUTSIDE_WORKFLOW", "Task deps require a workflow context. Build the workflow with createSmithers().");
      }
      const resolvedDeps = deps ? resolveDeps(ctx, deps, rest.needs) : undefined;
      if (deps && resolvedDeps == null) {
          // Deps not yet available — component defers until upstream tasks complete.
          // This is normal reactive behavior; the task will re-render once deps are
          // ready. Record the deferral so the engine can distinguish a transient wait
          // from a permanent one: a deferral that survives to quiescence means a
          // dependency that can never resolve (e.g. a deps key that maps to a node id
          // no task produces), which would otherwise be a silent skip.
          ctx?.recordDeferredDep?.(props.id, depNodeIds ?? []);
          return null;
      }
      // Build aspect metadata to attach to the task element so the engine can
      // enforce budgets and track metrics at execution time.
      const aspectMeta = aspectCtx ? buildAspectMeta(aspectCtx) : undefined;
      const agentChain = Array.isArray(agent)
          ? fallbackAgent
              ? [...agent, fallbackAgent]
              : agent
          : agent && fallbackAgent
              ? [agent, fallbackAgent]
              : agent;
      const effectiveAllowTools = resolveCliToolAllowlist(ctx, rest.allowTools);
      const restrictedAgentChain = Array.isArray(agentChain)
          ? agentChain.map((entry) => applyCliToolAllowlist(entry, effectiveAllowTools))
          : agentChain
              ? applyCliToolAllowlist(agentChain, effectiveAllowTools)
              : agentChain;
      const nextDependsOn = mergeDependsOn(rest.dependsOn, depNodeIds);
      const childValue = typeof children === "function" && (agent || deps)
          ? children(resolvedDeps ?? Object.create(null))
          : children;
      if (agent) {
          // Auto-inject `schema` prop into React element children when output is a ZodObject
          let childElement = childValue;
          const schemaForInjection = props.outputSchema ??
              (isZodObject(props.output) ? props.output : undefined);
          if (React.isValidElement(childValue) && schemaForInjection) {
              childElement = React.cloneElement(childValue, {
                  schema: zodSchemaToJsonExample(schemaForInjection),
              });
          }
          const prompt = renderPromptToText(childElement);
          return React.createElement("smithers:task", {
              ...rest,
              dependsOn: nextDependsOn,
              waitAsync: rest.async === true,
              agent: restrictedAgentChain,
              __smithersKind: "agent",
              ...aspectMeta,
          }, prompt);
      }
      if (typeof children === "function" && !deps) {
          const nextProps = {
              ...rest,
              dependsOn: nextDependsOn,
              waitAsync: rest.async === true,
              __smithersKind: "compute",
              __smithersComputeFn: children,
              ...aspectMeta,
          };
          return React.createElement("smithers:task", nextProps, null);
      }
      const nextProps = {
          ...rest,
          dependsOn: nextDependsOn,
          waitAsync: rest.async === true,
          __smithersKind: "static",
          __smithersPayload: childValue,
          __payload: childValue,
          ...aspectMeta,
      };
      return React.createElement("smithers:task", nextProps, null);
  }
  /**
   * Build the __aspects metadata object from the current AspectContext.
   * This is attached to the smithers:task element props so the engine can read
   * budgets and tracking config at execution time.
   * @param {{
   *     tokenBudget?: unknown;
   *     latencySlo?: unknown;
   *     tracking?: unknown;
   *     accumulator?: unknown;
   * }} aspectCtx
   * @returns {{ __aspects: Record<string, unknown> }}
   */
  function buildAspectMeta(aspectCtx) {
      return {
          __aspects: {
              tokenBudget: aspectCtx.tokenBudget,
              latencySlo: aspectCtx.latencySlo,
              tracking: aspectCtx.tracking,
              accumulator: aspectCtx.accumulator,
          },
      };
  }
  ```

  ```ts CheckSuiteProps.ts theme={null}
  import type { CheckConfig } from "./CheckConfig.ts";
  import type { OutputTarget } from "./OutputTarget.ts";

  export type CheckSuiteProps = {
  	id?: string;
  	checks: CheckConfig[] | Record<string, Omit<CheckConfig, "id">>;
  	verdictOutput: OutputTarget;
  	strategy?: "all-pass" | "majority" | "any-pass";
  	maxConcurrency?: number;
  	continueOnFail?: boolean;
  	skipIf?: boolean;
  };
  ```

  ```ts CheckConfig.ts theme={null}
  import type { AgentLike } from "@smithers-orchestrator/agents/AgentLike";

  export type CheckConfig = {
  	id: string;
  	agent?: AgentLike;
  	command?: string;
  	label?: string;
  };
  ```
</CodeGroup>
