@zuke/ai

← API reference

`@zuke/ai` — AI-powered code review for Zuke builds.

Symbols

#

AgentContext

interface
interface AgentContext

The failure context handed to an AgentRunner, plus a ready prompt.

MemberSignatureDoc
target target: string The name of the failed target.
attempt attempt: number The 1-based recovery attempt.
command? command?: string The command line that failed, if known.
output output: string The captured error output (stderr, or the error message).
conventions? conventions?: string Project conventions (CLAUDE.md / AGENTS.md), if found.
prompt prompt: string A ready-to-use prompt assembled from the fields above.
#

agentFixer

function
function agentFixer(run: AgentRunner, configure?: Configure<AgentFixer>): AgentFixer

Construct an AgentFixer from an AgentRunner and apply the configuration lambda. Plug the result into a target with .recoverWith(...).

#

AgentFixer

class
class AgentFixer

A fluent agent fixer. Construct one via agentFixer with a runner, and attach it to a target with .recoverWith(...). Diagnose/report defaults are on; file changes happen through the agent, gated to local runs unless .runOnly(...) says otherwise.

MemberSignatureDoc
runOnly runOnly(scope: RunScope): this Where the agent may run. Defaults to "local" — run on a developer's machine, refuse on CI. "ci" is the inverse, and the shape a repository's own build wants: an agent that heals a pull request must not be turned loose on a working tree someone is in the middle of editing. Off CI the fixer then reports the skip and leaves the failure standing, without ever starting the agent. "both" permits either host. One setter on one axis, so the effective scope never depends on the order two flags were called in.
allowCI allowCI(): this Permit the agent to run (and edit files) on CI as well as locally.
suggest suggest(): this Propose the agent's changes as committable inline suggestions on the pull request (from its git diff) instead of committing them. The build stays failed — the human applies the suggestions to fix it. Mutually exclusive with commitFixes (suggest takes precedence). GitHub only; elsewhere it falls back to the overview comment.
commitFixes commitFixes(): this After the agent runs, stage all its changes, commit, and push to the current branch so a healed PR carries the fix. Requires a checkout that can push; a failed push is reported, not fatal.
commitMessage commitMessage(message: string): this Override the commit subject used by commitFixes.
noPush noPush(): this Commit the fix but do not push it.
comment comment(): this Also post what the agent did as a PR comment (on by default).
noComment noComment(): this Do not post a PR comment (the job summary is still written).
commentToken commentToken(token: AnyParameter | string): this The token used to post the PR comment (defaults to the host's env var).
criteria criteria(criteria: string): this Project-specific notes appended to the agent's prompt.
conventions conventions(text: string): this Supply the project conventions text directly, instead of letting the fixer read CLAUDE.md/AGENTS.md from the working tree. Pass an empty string to send none. This is a trust pin, not only a convenience. By default the conventions are read from the tree under repair, which on a contributor's branch is content that contributor wrote — so the model is told to respect a document the author of the failing change controls. The prompt fences it as untrusted data and says so, but a fence is defence-in-depth against a model that can still be coaxed, not a guarantee. Pinning the text here — or passing "" — takes the branch out of the loop entirely, and is worth doing wherever the fixer is authorized to write: an AgentFixer runs an agent that edits files and runs commands with no path allow-list at all.
quiet quiet(): this Suppress the console printout (the summary/comment are still written).
env env(reader: EnvReader): this The environment reader used to detect CI and the comment host (test seam).
exec exec(run: unknown): this The git runner used for committing (test seam).
readFile readFile(impl: unknown): this The convention-file reader (test seam).
fetch fetch(impl: fetch): this The fetch implementation used to post the PR comment (test seam).
remediate remediate(context: RemediationContext): Promise<RemediationResult> Run the agent against the failure, optionally commit its changes, and ask the executor to re-run the target as the verifier. Skips (no retry) outside the hosts runOnly permits, or if the agent run itself fails.
name name: string A name for diagnostics — "agent fix".
#

AgentResult

type
type AgentResult = CommandOutput | string | void

What an AgentRunner may resolve to: a CommandOutput (its stdout is captured for the report), a string, or nothing.

#

AgentRunner

type
type AgentRunner = unknown

Runs the coding agent against the assembled AgentContext. The agent is expected to edit files in place; the executor then re-runs the target. Throw (or let the underlying command throw) to signal the agent could not run.

#

aiCache

function
function aiCache(configure?: Configure<AiCache>): AiCache

Construct an AiCache, applying an optional configure lambda so it can be set up inline — e.g. aiCache((c) => c.dir(".cache").ttl(3600)).

#

AiCache

class
class AiCache

A best-effort cache of AI provider responses, keyed by a stableHash of each call's salient parts and persisted through a CacheStore (the default writes JSON files under AiCache.dir). Configure it inline with aiCache: set the AiCache.dir, the AiCache.ttl, or AiCache.disable it entirely, then read with AiCache.get_ and write with AiCache.put_.

MemberSignatureDoc
dir dir(path: string): this Directory for the default file store (default ".zuke/ai-cache").
ttl ttl(seconds: number): this Entries older than this many seconds are ignored (default 604800 = 7 days; 0 = never expire).
disable disable(): this Turn the cache off programmatically (get_ misses, put_ is a no-op).
store store(custom: CacheStore): this Inject a custom backing store (test seam; overrides the file store).
now now(clock: unknown): this Clock seam for createdAt and TTL checks (default Date.now).
enabled_ enabled_(): boolean INTERNAL: whether the cache is active.
key_ key_(parts: string[]): string INTERNAL: derive a stable key from the given parts.
get_ get_(key: string): Promise<CacheEntry | undefined> INTERNAL: fetch a live (non-expired) entry, or undefined.
put_ put_(key: string, text: string, usage?: Usage): Promise<void> INTERNAL: store a response under key.
#

aiFixer

function
function aiFixer(configure?: Configure<AiFixer>): AiFixer

Construct an AiFixer and apply the configuration lambda. Plug the result into a target with .recoverWith(...):

test = target()
  .executes(() => DenoTasks.test((s) => s.allowAll()))
  .recoverWith(aiFixer((f) => f.provider("claude").apiKey(this.key)));
#

AiFixer

class
class AiFixer

A fluent AI fixer. Construct one via aiFixer, configure it, and attach it to a target with .recoverWith(...). Only .provider(...) and .apiKey(...) are required; everything else defaults.

MemberSignatureDoc
provider provider(provider: Provider): this Set the model provider (required).
apiKey apiKey(apiKey: AnyParameter | string): this Set the API key, from a secret parameter or a literal string (required).
model model(model: string): this Override the model (default: the provider's recommended model).
effort effort(effort: Effort): this Set the thinking-effort hint (honoured by Claude; ignored elsewhere).
criteria criteria(criteria: string): this Project-specific notes appended to the prompt (idioms, constraints).
conventions conventions(text: string): this Supply the project conventions text directly, instead of letting the fixer read CLAUDE.md/AGENTS.md from the working tree. Pass an empty string to send none. This is a trust pin, not only a convenience. By default the conventions are read from the tree under repair, which on a contributor's branch is content that contributor wrote — so the model is told to respect a document the author of the failing change controls. The prompt fences it as untrusted data and says so, but a fence is defence-in-depth against a model that can still be coaxed, not a guarantee. Pinning the text here — or passing "" — takes the branch out of the loop entirely, and is worth doing wherever the fixer is authorized to write: AiFixer.autoApply or AiFixer.commitFixes.
diff diff(configure: Configure<DiffSettings>): this Configure the diff source used for context (default: the working tree).
include include(...globs: string[]): this Only include diff sections matching these globs in the prompt context.
exclude exclude(...globs: string[]): this Exclude diff sections matching these globs from the prompt context.
maxDiffTokens maxDiffTokens(tokens: number): this Cap the context diff at roughly this many tokens (default 16000).
autoApply autoApply(): this Apply the proposed fix to the working tree and ask the executor to re-run the target. Off by default (the fixer only diagnoses). Writes are confined by allowPaths, the built-in exclusions, and maxEdits, and are confined to the hosts runOnly permits.
allowPaths allowPaths(...globs: string[]): this Restrict applied edits to paths matching these globs (default: all).
excludePaths excludePaths(...globs: string[]): this Exclude paths matching these globs from edits, on top of the built-ins.
maxEdits maxEdits(count: number): this Cap how many files a single applied fix may touch (default 10).
runOnly runOnly(scope: RunScope): this Where the fixer may run. Defaults to "local" — apply on a developer's machine, refuse on CI. "ci" is the inverse, and the shape a repository's own build wants: self-heal a pull request without ever rewriting a working tree someone is in the middle of editing. Off CI the fixer then returns before the model is called, so it costs no tokens and the underlying failure stands unchanged. That is a stronger refusal than the default scope makes on CI, where the fixer still diagnoses and only declines to write: a diagnosis on a pull request is worth reading, whereas one on a local run is an unasked-for charge against the developer's own key. "both" permits either host. One setter on one axis, so the effective scope never depends on the order two flags were called in. ``ts aiFixer((f) => f.provider("openai").apiKey(key).autoApply().runOnly("ci")); ``
allowCI allowCI(): this Permit auto-apply (and committing) on CI as well as locally.
commitFixes commitFixes(): this After applying a fix, stage it, commit it, and push to the current branch — so a healed pull request carries the fix as a commit. Implies autoApply. Requires a checkout that can push (a non-detached branch with credentials); a failed push is reported, not fatal.
commitMessage commitMessage(message: string): this Override the commit subject used by commitFixes.
noPush noPush(): this Commit the fix but do not push it (leave it staged in a local commit).
comment comment(): this Also post the diagnosis/fix as a PR comment (on by default).
noComment noComment(): this Do not post a PR comment (the job summary is still written).
suggest suggest(): this On GitHub, post each code location as an inline review comment with a committable suggestion block (the Copilot-style suggestion) instead of a single overview comment. On by default; a no-op off GitHub or when the model reports no specific locations, where the overview comment is used.
noSuggest noSuggest(): this Post a single overview comment instead of inline GitHub suggestions.
commentToken commentToken(token: AnyParameter | string): this The token used to post the PR comment (defaults to the host's env var).
retry retry(options?: RetryOptions): this Retry the provider call on transient failures (see RetryOptions).
quiet quiet(): this Suppress the console printout (the summary/comment are still written).
fetch fetch(impl: fetch): this The fetch implementation for the API call (test seam).
exec exec(run: unknown): this The git runner used for the diff and commit (test seam).
write write(impl: unknown): this The file writer used when applying edits (test seam).
readFile readFile(impl: unknown): this The convention-file reader (test seam).
env env(reader: EnvReader): this The environment reader used to detect CI and the comment host (test seam).
budget budget(budget: Budget): this Attach a shared Budget that caps spend by an exact token count (a USD cap is opt-in, computed from prices you supply to the budget). Once the cap is reached the fixer skips the model call (and reports it) rather than running up the bill; share one budget across reviewers and fixers to bound a whole build.
cache cache(cache: AiCache): this Reuse a prior fix for an identical failure (same provider, model, and prompt) instead of calling the API again — see AiCache. Helpful when the same failure recurs across CI re-runs; a hit does not draw down the budget.
remediate remediate(context: RemediationContext): Promise<RemediationResult> Diagnose the failure and, when permitted, apply (and commit) the fix. Always reports; returns { retry: true } only when it changed the working tree so the executor re-runs the target as the verifier.
name name: string A name for diagnostics — "AI fix".
#

AiReviewError

class
class AiReviewError extends Error

Raised when a reviewer is misconfigured, the API fails, or the gate trips.

MemberSignatureDoc
name name: string The error name.
#

aiReviewWorkflow

function
function aiReviewWorkflow(spec: AiReviewWorkflowSpec): CiFile

Declare a generated AI-review workflow on the build. The returned CiFile is automatically discovered by discoverCiFiles and kept on disk by syncCiFiles. Default host: "github"; pass "gitlab" or "azure" to target those hosts.

class Pipeline extends Build {
  openaiKey = parameter("OpenAI key").secret().env("OPENAI_API_KEY");
  review = securityReviewer((r) =>
    r.provider("openai").apiKey(this.openaiKey).comment()
  );
  reviewTarget = target().validateBefore(this.review).executes(() => {});
  ghWorkflow = aiReviewWorkflow({ reviewers: [this.review] });
  glWorkflow = aiReviewWorkflow({ host: "gitlab", reviewers: [this.review] });
}
#

AiReviewWorkflowSpec

interface
interface AiReviewWorkflowSpec

What to generate — only reviewers is required.

MemberSignatureDoc
reviewers reviewers: unknown The reviewers whose key env vars and .comment() setting drive the generated workflow. Each reviewer's .apiKey(param) parameter becomes an env: entry (or its host equivalent) that maps the secret in; any reviewer with .comment() causes the workflow to grant the right commenting scope and pass the host's token env var.
host? host?: CiProvider The CI host the workflow targets. Defaults to "github". Use "gitlab", "azure", or "bitbucket" to generate the equivalent for those hosts.
target? target?: string The build target the workflow runs. Defaults to "review".
baseBranch? baseBranch?: string The base branch the diff is taken against (used by the GitHub workflow's fetch step). Defaults to "master".
fetchBase? fetchBase?: boolean Emit the git fetch step that makes the base branch available, and point the reviewers at what it fetched. Defaults to true, because a pull-request checkout is shallow and has no base to diff against. Set it to false when the build's review target fetches its own base — then the workflow drops the step, and the reviewers use their own configured base rather than the fetched FETCH_HEAD. Preferable where it applies: the same zuke review then works locally, where no workflow step exists to run.
hardenRunner? hardenRunner?: string The pinned step-security/harden-runner@<sha> to harden the runner with. Supplying this — or checkout — renders the two separate steps instead of the prelude action, because naming an action means those specific actions were asked for. Leave both unset for the default, which is the one action that does both and carries its own pin.
checkout? checkout?: string The pinned actions/checkout@<sha> to check the repository out with. Like hardenRunner, supplying it renders the separate steps.
pins? pins?: CiPinResolver Resolves each action's pinned reference by name, exactly as cicd's own option does. Supply it when the build sources pins from somewhere that stays current. Without it the prelude action falls back to the reference baked into the core this package resolved against — which is a release behind as soon as the action is released again, and silently so: this file would name a different commit from every other generated workflow in the repository, and only a diff would show it.
path? path?: string Output path. Defaults to the host's conventional location.
name? name?: string Workflow name shown in the host's UI. Defaults to "AI Review".
timeoutMinutes? timeoutMinutes?: number Per-job timeout in minutes. Defaults to 15.
command? command?: Configure<ReviewCommandSettings> Run the review on demand when a maintainer comments a command on a pull request — any pull request, a fork's included. GitHub only; see ReviewCommandSettings for the job it adds and the gate it runs behind. ``ts aiReviewWorkflow({ reviewers: [this.security], command: (c) => c.text("@zuke-build review") .secrets("ZUKE_BUILD_APP_ID", "ZUKE_BUILD_APP_KEY"), }); ``
#

Assessment

interface
interface Assessment

The structured result of a review.

MemberSignatureDoc
score score: number Overall risk score, 0 (none) to 10 (severe).
severity severity: Severity The overall severity.
summary summary: string A one-line summary of the assessment.
findings findings: AssessmentFinding[] The individual findings.
#

AssessmentFinding

interface
interface AssessmentFinding

A single issue reported by the model.

MemberSignatureDoc
title title: string A short title for the issue.
severity severity: Severity The issue's severity.
id? id?: string A stable fingerprint for the finding, assigned by the reviewer (see "./suppress.ts".findingFingerprint). Copy it into the suppress list to dismiss a recurring false positive.
file? file?: string The file the issue is in, if the model attributed one.
line? line?: number The line the issue is at, if the model attributed one.
detail? detail?: string A longer explanation, if provided.
verification? verification?: confirmed | uncertain The verify pass's verdict on this finding, when .verify() ran and the verifier answered for it: "confirmed" means the concrete failure path was traced, "uncertain" means the evidence neither confirmed nor refuted it. Both stay reported and gate — only a refutation (which removes the finding and lists it in the report's refuted table) mutes a candidate. Absent when verify did not run or returned no verdict for the finding.
#

AssessmentType

type
type AssessmentType = generic | security | secrets | correctness | license

The kind of review an assessment performs.

#

budget

function
function budget(configure?: Configure<Budget>): Budget

Construct a Budget, applying an optional configure lambda so caps can be set inline — e.g. budget((b) => b.maxTokens(100_000).maxCost(1)).

#

Budget

class
class Budget

A running token and cost budget for AI provider calls. Build one with budget, set caps with the fluent setters, then fold each call's usage in with Budget.record_ and gate further calls on Budget.exhausted_.

MemberSignatureDoc
maxTokens maxTokens(total: number): this Cap total tokens (input + output across all recorded calls).
maxCost maxCost(usd: number): this Cap estimated USD cost. Only takes effect for models you've priced via prices — no prices ship by default — so the estimate uses your own current rates. Pair it with maxTokens for a guaranteed hard cap.
prices prices(table: Record<string, ModelPrice>): this Supply per-model prices (USD per 1,000,000 tokens, keyed by model id) so a maxCost cap and the cost estimate can be computed. Merges across calls. Nothing is priced until you call this — provider pricing changes too often to bake a table in, so the rates here are yours to keep current.
exhausted_ exhausted_(): boolean INTERNAL: whether a configured cap has already been reached.
record_ record_(usage: Usage | undefined, model: string): void INTERNAL: fold one provider call's usage into the running totals.
spend_ spend_(): BudgetSpend INTERNAL: a snapshot of consumption so far.
remainingTokens_ remainingTokens_(): number | undefined INTERNAL: remaining tokens before the cap, or undefined when no token cap.
describe_ describe_(): string INTERNAL: a one-line human summary, e.g. "1,234 tokens (~$0.01) of 10,000 / $1.00".
#

BudgetSpend

interface
interface BudgetSpend

A snapshot of what a Budget has consumed so far.

MemberSignatureDoc
calls calls: number How many provider calls were recorded.
inputTokens inputTokens: number Total input (prompt) tokens across all recorded calls.
outputTokens outputTokens: number Total output (completion) tokens across all recorded calls.
totalTokens totalTokens: number Total tokens (input + output) across all recorded calls.
cost? cost?: number Estimated USD cost, when at least one recorded call had a known price.
#

CacheEntry

interface
interface CacheEntry

A cached provider response.

MemberSignatureDoc
text text: string The model's raw text response.
usage? usage?: Usage Token usage reported for the original call, if any.
createdAt createdAt: number Epoch milliseconds when the entry was written (for TTL).
#

CacheStore

interface
interface CacheStore

A pluggable backing store for the cache (the default is file-backed).

MemberSignatureDoc
get get(key: string): Promise<CacheEntry | undefined> Fetch the entry stored under key, or undefined when absent.
set set(key: string, entry: CacheEntry): Promise<void> Store entry under key, replacing any prior value.
#

CommentTokenSource

type
type CommentTokenSource = AnyParameter | string | unknown

Where a reviewer's comment-posting token comes from: a secret parameter (for its env var), a literal, or a function that produces the token when a post first needs it — the shape for a token minted by the build itself, such as a GitHub App installation token, so the comments carry the app's identity.

#

Confidence

type
type Confidence = low | medium | high

The model's confidence that a fix is correct.

#

correctnessReviewer

function
function correctnessReviewer(configure?: Configure<Reviewer>): Reviewer

A reviewer that scores the diff for correctness bugs and regressions.

#

DiffFetch

interface
interface DiffFetch

The base-branch fetch a DiffSettings.fetchBase requested — the remote to fetch from and the branch (auto-detected from CI when unset).

MemberSignatureDoc
branch? branch?: string The branch to fetch, or undefined to auto-detect it from the CI env.
remote remote: string The remote to fetch from (default "origin").
#

DiffSettings

class
class DiffSettings

Fluent diff source configuration passed to "./reviewer.ts".Reviewer.diff.

MemberSignatureDoc
base base(ref: string): this Review the diff against ref (e.g. "origin/main").
staged staged(): this Review the staged changes (git diff --cached).
text text(diff: string): this Review a diff supplied directly, bypassing git (useful in tests).
fetchBase fetchBase(branch?: string, remote?: string): this Fetch the base branch (a shallow, tag-less git fetch) before diffing, and diff against it — so CI needs no manual git fetch step. With no branch, the base is auto-detected from the CI environment (GitHub's GITHUB_BASE_REF — the pull request's base branch). Honoured by the "./fixer.ts".AiFixer; if the fetch fails it falls back to the working-tree diff.
text_ text_(): string | undefined The literal diff text supplied via DiffSettings.text, if any.
base_ base_(): string | undefined The base ref supplied via DiffSettings.base, if any.
fetch_ fetch_(): DiffFetch | undefined The base-branch fetch requested via DiffSettings.fetchBase, or undefined when none was requested.
argv_ argv_(): string[] The git argv this diff source resolves to.
#

DiscussionSettings

class
class DiscussionSettings

Fluent settings for "./reviewer.ts".Reviewer.discussion — who the reviewer listens to on the PR thread, and how much of it the model may see.

MemberSignatureDoc
trustAssociations trustAssociations(...associations: string[]): this Replace the trusted author_association set (default OWNER, MEMBER, COLLABORATOR). Comments from authors outside it (and outside trustAuthors) are dropped before the model sees them.
trustAuthors trustAuthors(...logins: string[]): this Trust these author logins in addition to the association rule — e.g. an outside collaborator whose review the project wants the reviewer to engage with.
maxCommentTokens maxCommentTokens(tokens: number): this Cap the total comment text sent to the model at roughly this many tokens (default 4000), newest comments kept first — so a wall of text cannot crowd the diff and the rubric out of the context window.
associations_ associations_(): string[] INTERNAL: the trusted association set.
authors_ authors_(): string[] INTERNAL: the extra trusted author logins.
threads threads(): this Also anchor each finding to a file/line review thread on the pull request, so a maintainer contests it by replying in that thread instead of quoting its id somewhere on the PR. The summary comment is posted either way and stays the one source of truth: it lists every finding — anchored or not — and carries the state block, so a thread that cannot be posted never hides a finding. The reviewer replies into the thread with the outcome and resolves it once the finding is fixed or dismissed. GitHub only; on other hosts the reviewer notes that and posts the summary alone.
maxTokens_ maxTokens_(): number INTERNAL: the total comment-token cap.
threads_ threads_(): boolean INTERNAL: whether findings are also anchored to review threads.
#

Effort

type
type Effort = low | medium | high | xhigh | max

The thinking-depth hint passed to providers that support it (Claude).

#

EnvReader

type
type EnvReader = unknown

Read an environment variable, tolerating an absent --allow-env permission.

#

FileEdit

interface
interface FileEdit

A whole-file edit: the complete new contents of one file.

MemberSignatureDoc
path path: string Repository-relative path of the file to write.
content content: string The complete new contents of the file (not a patch).
#

findingFingerprint

function
function findingFingerprint(assessment: AssessmentType, finding: AssessmentFinding): string

A stable fingerprint for a finding: hash of the assessment kind, the normalised title (trimmed, lowercased, whitespace collapsed), and the file. Independent of line number so a finding keeps its id as code shifts.

#

Fix

interface
interface Fix

The structured result of a fix attempt.

MemberSignatureDoc
diagnosis diagnosis: string A one-line explanation of what failed and why.
rootCause rootCause: string The underlying root cause the fix addresses.
confidence confidence: Confidence The model's confidence that the edits resolve the failure.
locations locations: FixLocation[] The specific code locations the fix targets, with verbatim source.
edits edits: FileEdit[] The whole-file edits that, applied together, should fix the failure.
#

FixLocation

interface
interface FixLocation

A specific code location the fix targets: the exact offending source quoted verbatim, its file and line(s), and the suggested replacement. Rendered as a diff in the report so the comment shows real code, not just prose.

MemberSignatureDoc
file file: string Repository-relative path of the file.
line line: number The 1-based line where the offending code starts.
endLine? endLine?: number The 1-based line where it ends, when it spans more than one line.
code code: string The exact offending source line(s), quoted verbatim.
suggestion? suggestion?: string The suggested replacement for code (empty means delete it).
#

GateRule

type
type GateRule = unknown | unknown

A configured rule for "./reviewer.ts".Reviewer.failWhen.

#

GateSettings

class
class GateSettings

Fluent gate configuration passed to "./reviewer.ts".Reviewer.failWhen.

MemberSignatureDoc
scoreAbove scoreAbove(value: number): this Fail when the assessed risk score is strictly above value (0–10).
severityAtLeast severityAtLeast(value: Severity): this Fail when the overall severity is at least value.
rules_ rules_(): GateRule[] The configured gate rules, in the order they were added.
#

genericReviewer

function
function genericReviewer(configure?: Configure<Reviewer>): Reviewer

A general-purpose reviewer scored on code quality and maintainability. Pair with .criteria(...) to add project-specific notes (idioms, conventions, a coding-style document); the built-in rubric is sufficient without them.

#

licenseReviewer

function
function licenseReviewer(configure?: Configure<Reviewer>): Reviewer

A reviewer that scores the diff for license and compliance risk.

#

ModelPrice

interface
interface ModelPrice

Per-model price in USD per 1,000,000 tokens.

MemberSignatureDoc
input input: number USD per 1,000,000 input (prompt) tokens.
output output: number USD per 1,000,000 output (completion) tokens.
#

Provider

type
type Provider = claude | openai | gemini

A supported model provider.

#

RetryInfo

interface
interface RetryInfo

What happened before a retry, for RetryOptions.onRetry.

MemberSignatureDoc
attempt attempt: number The attempt that just failed (1-based).
attempts attempts: number The total number of attempts that will be made.
delayMs delayMs: number How long the helper will wait before the next attempt, in milliseconds.
reason reason: string Why the attempt failed — e.g. "HTTP 503" or "timed out after 60000ms".
#

RetryOptions

interface
interface RetryOptions

Configurable knobs for retryingFetch.

MemberSignatureDoc
attempts? attempts?: number Total attempts (first try + retries). Defaults to DEFAULT_ATTEMPTS.
baseDelayMs? baseDelayMs?: number Backoff for the first retry; doubles each subsequent retry.
timeoutMs? timeoutMs?: number Per-attempt timeout in milliseconds (default 60s). 0 disables it.
onRetry? onRetry?: unknown Invoked before each retry, so a caller can report progress.
sleep? sleep?: unknown Sleep seam — overridden in tests so retries don't take real time.
#

ReviewCommandSettings

class
class ReviewCommandSettings

The comment command that runs the review on demand, configured through AiReviewWorkflowSpec.command.

The job it adds runs on issue_comment, which GitHub delivers for comments on pull requests too, always from the default branch and always with the repository's secrets — so its if: is the whole access control. It fires only when the comment is on a pull request, starts with text, was written by a human (not a bot account), and its author's author_association is OWNER, MEMBER or COLLABORATOR. The comment body is matched in the expression and never interpolated into a run: line. Then, before the review runs, the job asks the collaborators API whether the commenter has push access, and stops if not — an association alone admits read-only members and collaborators.

What runs is the default branch's build, never the pull request's: the job passes ZUKE_REVIEW_PR, and the reviewers fetch that pull request's merge as data and diff it. That is what makes the flow safe for a fork — the code under review is read, not executed — and it is why a pull request cannot change the rules it is judged by from this flow: its zuke.ts, suppressions and criteria never load. The maintainer's comment is the human gate, as it is for Dependabot's @dependabot commands.

MemberSignatureDoc
text text(command: string): this The command, e.g. "@zuke-build review". Matched case-insensitively at the start of the comment, so a reply that quotes it (> @zuke-build review) does not start a run. Letters, digits, @/_.:- and single spaces only.
secrets secrets(...names: string[]): this Pass these repository secrets to the command job's review step as env vars of the same name — e.g. the GitHub App credentials the build mints its commentToken from, so the review posts as the app. Only the command job receives them; the pull_request job is unchanged.
text_? text_?: string The command a comment must start with. Set by text.
secrets_ secrets_: string[] Secrets the command job's review step receives beyond the reviewers' own keys and the host token. Set by secrets.
#

Reviewer

class
class Reviewer

A fluent AI reviewer. Construct one via securityReviewer (and the sibling factories), configure it, and attach it to a target with .validateBefore(...) / .validateAfter(...). .provider(...) and .apiKey(...) are required; everything else has a default.

MemberSignatureDoc
provider_ provider_(): Provider | undefined The model provider, once .provider(...) has been called.
apiKey_ apiKey_(): AnyParameter | string | undefined The configured API key (a parameter — for its env var — or a literal).
commentEnabled_ commentEnabled_(): boolean Whether .comment() is set — i.e. this reviewer posts to the PR.
commentToken_ commentToken_(): CommentTokenSource | undefined The configured comment-posting token, if .commentToken(...) was called.
provider provider(provider: Provider): this Set the model provider (required).
apiKey apiKey(apiKey: AnyParameter | string): this Set the API key, from a secret parameter or a literal string (required).
model model(model: string): this Override the model (default: the provider's recommended model).
effort effort(effort: Effort): this Set the thinking-effort hint (honoured by Claude; ignored elsewhere).
criteria criteria(criteria: string): this Optional project-specific notes appended above the diff in the user prompt — framing that fine-tunes the built-in rubric (e.g. "strict TypeScript, no any/as"). Works for every reviewer; the assessment's own system prompt already covers what to look for, so this is purely additive. These notes are read from the head under review, and cannot be anything else: they are build code, evaluated by the very build the pull request changed, so there is no base copy to read short of running the base's build. A branch can therefore widen what its own review overlooks, which is real but bounded — the change is first-party code in the diff a maintainer reads, and a build that could not configure its own reviewer from its own source would be a different feature. Reviewer.criteriaFile is the base-anchored form for notes that should not be editable by the change they judge.
diff diff(configure: Configure<DiffSettings>): this Configure the diff source (default: the working-tree diff, git diff).
include include(...globs: string[]): this Only review files matching these globs (default: all files).
exclude exclude(...globs: string[]): this Exclude files matching these globs (in addition to lockfiles).
maxDiffTokens maxDiffTokens(tokens: number): this Cap the diff at roughly this many tokens, truncating the rest.
failWhen failWhen(configure: Configure<GateSettings>): this Choose the gate that breaks the build (default: score above 7).
onError onError(mode: fail | warn): this What to do when the review itself fails (API error, refusal, bad JSON): "fail" breaks the build (default), "warn" logs and passes.
retry retry(options?: RetryOptions): this Retry the provider call on transient failures (HTTP 408/429/500/502/503/ 504 and network errors). The default is on — three attempts with exponential backoff and Retry-After honoured. Pass an object to override: { attempts: 5 } to retry more, or { attempts: 1 } to disable.
skipIfKeyMissing skipIfKeyMissing(): this Skip the review (instead of failing) when the API key is missing — handy when the key is a CI-only secret. The skip is announced on the console and in the job summary so the gap is visible.
comment comment(mode?: update | append): this Also post the review to the pull/merge request as a comment. Works on every supported CI host — GitHub Actions, GitLab CI, Azure Pipelines, Bitbucket Pipelines — dispatched at runtime by detectCiHost. A no-op outside a PR context (e.g. local runs). On each host the workflow must grant the right scope: GitHub pull-requests: write, GitLab a token with the api scope, Azure System.AccessToken, Bitbucket an app password. mode chooses how re-runs post: "update" (default) keeps a single comment per reviewer, edited in place; "append" posts a fresh comment every run, so earlier assessments — and their finding ids — stay on the thread as history. The discussion feature works with both: its state block rides on every comment, and the newest one is read back.
commentToken commentToken(token: CommentTokenSource): this The token used to post the PR/MR comment. Defaults to the active host's conventional env var: GITHUB_TOKEN (GitHub), GITLAB_TOKEN (GitLab), SYSTEM_ACCESSTOKEN (Azure), BITBUCKET_TOKEN (Bitbucket). A function is called each time a post needs the token — for a token that does not exist until the build mints it, such as a GitHub App installation token narrowed to pull_requests: write, which makes the comments the app's rather than github-actions[bot]'s. A function that mints should remember its result, since a review posts more than once. The generated workflow cannot name a secret for a function, so it passes the host's default token alongside, for the function to fall back to.
githubToken githubToken(token: CommentTokenSource): this Backwards-compatible alias for commentToken.
quiet quiet(): this Suppress the findings printout and the job-summary section.
fetch fetch(impl: fetch): this The fetch implementation for the API call (test seam).
exec exec(run: unknown): this The git runner used to produce the diff (test seam).
env env(reader: EnvReader): this Environment reader used to auto-detect the base branch for "./diff.ts".DiffSettings.fetchBase (reads GITHUB_BASE_REF). A test seam; defaults to the process environment.
budget budget(budget: Budget): this Attach a shared Budget that caps spend by an exact token count (a USD cap is opt-in, computed from prices you supply to the budget). Pass the same budget to several reviewers and a fixer to bound the whole build: once the cap is reached, further reviews are skipped (not failed) with a note, rather than running up the bill.
cache cache(cache: AiCache): this Reuse a prior model response for an identical review (same provider, model, and prompt) instead of calling the API again — see AiCache. A cache hit costs nothing and does not draw down the budget.
suppress suppress(suppressions: Suppressions): this Hide findings whose stable ID is in a Suppressions list — a learned set of dismissed false positives. Every finding is fingerprinted and its ID surfaced in the report, so dismissing one is a copy-paste into the list. Like Reviewer.criteria, the list is read at the head under review — inline entries are build code, and the file is read from disk — so a change can suppress a finding on its own run. That is deliberate: every suppressed finding is still listed in the report, so the muting is visible on the thread rather than silent, and the entry is in the diff a maintainer reads. Nothing here reads the base, and nothing should be assumed to.
conventionsFile conventionsFile(path: string, maxTokens?: number): this Feed the project's conventions document (e.g. AGENTS.md) to the model as reference material, so the review judges the change against the project's documented rules instead of generic taste. When the diff has a base ref (.diff((d) => d.base(...)) or a successful .fetchBase()), the file is read from that base via git show — never from the head under review, so a pull request cannot rewrite the rules it is judged by. Without a base (a local working-tree review) it is read from disk. Truncated at roughly maxTokens (default 8000).
criteriaFile criteriaFile(path: string, maxTokens?: number): this Project-specific notes read from a file, appended to whatever Reviewer.criteria set inline — the base-anchored half of the same slot. Read exactly as Reviewer.conventionsFile is: from the diff's base ref via git show when there is one, from disk otherwise, truncated at roughly maxTokens (default 8000). That is the point of it. A note that tells the reviewer a design is accepted — and so suppresses the findings that restate it — is a rule the review is judged by, and a pull request should not be able to add one to its own run. Keeping it in a file the base supplies costs one merge of lag, which is the same lag the conventions document already accepts, and is arguably the point: a newly accepted design should be agreed before it starts muting findings. Use it for what a branch should not be able to widen; keep Reviewer.criteria for the framing that travels with the build. What it does not buy: the build still decides *whether* to read a criteria document at all, and that decision is head code, so a branch can drop the call as easily as it can edit a string. This bounds what a change can add to the rules of its own review, not whether the reviewer is configured — the same limit Reviewer.conventionsFile has, and the reason the diff a maintainer reads remains the control that matters.
fileContext fileContext(maxTokens?: number): this Also send the full post-image contents of the changed files (read via git show HEAD:<path>), bounded at roughly maxTokens (default 12000) — so the model can check a finding against the surrounding code (an existing guard, a validation a few lines away) instead of judging hunks in isolation. Skipped silently for a literal .diff((d) => d.text(...)) source with no repository behind it.
verify verify(): this Add an adversarial verification pass: after the review produces candidate findings, a second model call re-checks each against the diff (and the fileContext, when enabled) and refutes any whose failure path it cannot concretely trace. Refuted candidates are listed in the report but neither posted as findings nor gated on. Costs one extra API call per review with findings; if the pass itself errors, the unverified findings are kept (fail toward reporting, never toward silence).
discussion discussion(configure?: Configure<DiscussionSettings>): this Engage with the pull-request discussion instead of repeating findings: the reviewer reads the PR's comments, and when a trusted commenter (by the host's own author metadata — see DiscussionSettings) contests a finding by quoting its ID, an adjudication pass weighs the rebuttal on technical merit and either upholds the finding (with the gap named) or dismisses it. Dismissals persist across runs in a state block inside the reviewer's own PR comment, so a dismissed finding — or a rewording of it — does not resurface without new evidence. Requires comment (the comment is where state lives) and a host that can list comments (GitHub currently); elsewhere the discussion is skipped with a console note. Untrusted comments are dropped in code before any prompt is built — the model never sees them, so a drive-by "the maintainer approved this" comment cannot influence the review.
validate validate(context: ValidationContext): Promise<void> Run the review and gate the build. Throws an AiReviewError when the gate trips (or on a configuration/API error with onError: "fail").
name name: string A name for diagnostics — "<assessment> review".
#

RunScope

type
type RunScope = local | ci | both

Where a fixer may run, set with .runOnly(...).

- "local" — apply on a developer's machine, refuse on CI. The default, and what a fixer has when .runOnly(...) is never called. - "ci" — apply on CI, and do not run at all off it. The scope a repository's own build wants: heal a pull request without ever rewriting a working tree someone is editing. - "both" — apply on either host. What .allowCI() selects.

"On CI" means any CI system, not only the four Zuke names it can generate pipelines for: isCI recognises GitHub Actions, GitLab CI, Azure Pipelines and Bitbucket Pipelines by their own variables, and Jenkins, Buildkite, CircleCI, Travis, TeamCity and the generic CI convention by theirs.

Reading one of those as a developer's machine would fail in the dangerous direction for both of the other scopes, which is why the broader test is the right one: "ci" would silently skip every run, and "local" — the default — would apply and commit changes to what it believed was a working tree someone was editing. Jenkins is the case that matters most, since it sets none of the four and does not set CI either.

#

secretsReviewer

function
function secretsReviewer(configure?: Configure<Reviewer>): Reviewer

A reviewer that scans the diff for leaked secrets and credentials.

#

securityReviewer

function
function securityReviewer(configure?: Configure<Reviewer>): Reviewer

A reviewer that scores the diff for security vulnerabilities.

#

Severity

type
type Severity = none | low | medium | high | critical

A severity level, ordered none < low < medium < high < critical.

#

suppressions

function
function suppressions(configure?: Configure<Suppressions>): Suppressions

Construct a Suppressions, applying an optional configure lambda so the file and inline fingerprints can be set inline — e.g. suppressions((s) => s.file(".zuke/suppress.json").add("abc")).

#

Suppressions

class
class Suppressions

A file-backed set of suppressed finding fingerprints. The effective set is the union of the fingerprints read from Suppressions.file and any added inline with Suppressions.add; the reviewer drops a finding whose findingFingerprint is in that set.

MemberSignatureDoc
file file(path: string): this Path of the JSON suppress list (default ".zuke/ai-suppress.json").
add add(...fingerprints: string[]): this Add fingerprints inline (in addition to any from the file).
reader reader(read: unknown): this Reader seam for the suppress file (default reads from disk, missing -> undefined).
load_ load_(): Promise<Set<string>> INTERNAL: the effective set of suppressed fingerprints (file ∪ inline).
#

Usage

interface
interface Usage

Token counts a provider reported for a review call, when the response carries them. Each field is optional because not every provider reports every count.

MemberSignatureDoc
inputTokens? inputTokens?: number Tokens in the prompt / input.
outputTokens? outputTokens?: number Tokens in the model's output / completion.
totalTokens? totalTokens?: number Total tokens, reported by the provider or derived from input + output.