zuke.tools.ai
@zuke/ai 2.13.0: @zuke/ai — AI-powered code review for Zuke builds.
zuke on PyPI ↗ 6 symbols
Symbols
class AiTasks Typed task functions over the reviewers, for a build that calls rather than declares them.
-
static async review(ctx: ServiceContext, configure: Callable[[ReviewSettings], ReviewSettings]) -> NoneRun the configured reviewers as peers of one target, in order. Throws when a reviewer's gate trips, as the target it guards would fail.
-
static async preflight(ctx: ServiceContext, configure: Callable[[ReviewSettings], ReviewSettings]) -> NoneRun the reviewers' preflight: on a comment-started run, refuse a commenter who may not start a review. A no-op on any other run.
class DiffSettings(Settings) Fluent diff source configuration passed to "./reviewer.ts".Reviewer.diff.
-
base(ref: str) -> SelfReview the diff against
ref(e.g."origin/main"). -
staged() -> SelfReview the staged changes (
git diff --cached). -
text(diff: str) -> SelfReview a diff supplied directly, bypassing
git(useful in tests). -
fetch_base(branch: str | None=None, remote: str | None=None) -> SelfFetch the base branch (a shallow, tag-less
git fetch) before diffing, and diff against it — so CI needs no manualgit fetchstep. With nobranch, the base is auto-detected from the CI environment (GitHub'sGITHUB_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.
class DiscussionSettings(Settings) 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.
-
trust_associations(*associations: str) -> SelfReplace the trusted
author_associationset (defaultOWNER,MEMBER,COLLABORATOR). Comments from authors outside it (and outside trustAuthors) are dropped before the model sees them. -
trust_authors(*logins: str) -> SelfTrust these author logins in addition to the association rule — e.g. an outside collaborator whose review the project wants the reviewer to engage with.
-
max_comment_tokens(tokens: float) -> SelfCap 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.
-
threads() -> SelfAlso 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.
-
commands(mention: str) -> SelfTake commands from maintainers, addressed to
mention— e.g."@zuke-build", the account the workflow's comment command is addressed to, so one handle serves both. With it set, every review comment carries a collapsed Commands panel listing what a maintainer can say, and a trusted comment starting with<mention> accept <id> <reason>accepts the finding: it is recorded as accepted for this pull request without adjudication, answered in its thread and the thread resolved, and never re-raised on the pull request by any reviewer, however it is reworded, moved or escalated. In a finding's own thread the id may be left out. The trust gate is the one rebuttals pass — trustAssociations and trustAuthors, decided in code from the host's author metadata — so a stranger'sacceptis dropped before anything reads it. Unlike a rebuttal, an acceptance is one key: no adjudicator weighs it. On a host where the default associations admit accounts that should not decide for the project (GitHub'sMEMBERandCOLLABORATORinclude read-only ones), narrow the gate with trustAssociations or trustAuthors before turning commands on. The panel also lists<mention> review, the workflow's comment command that runs the review on demand; pair this withaiReviewWorkflow'scommandso that both are true.
class GateSettings(Settings) Fluent gate configuration passed to "./reviewer.ts".Reviewer.failWhen.
-
score_above(value: float) -> SelfFail when the assessed risk score is strictly above
value(0–10). -
severity_at_least(value: Literal['none', 'low', 'medium', 'high', 'critical']) -> SelfFail when the overall severity is at least
value.
class Reviewer(Settings) 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.
-
provider(provider: Literal['claude', 'openai', 'gemini']) -> SelfSet the model provider (required).
-
api_key(api_key: str) -> SelfSet the API key, from a secret parameter or a literal string (required).
-
model(model: str) -> SelfOverride the model (default: the provider's recommended model).
-
effort(effort: Literal['low', 'medium', 'high', 'xhigh', 'max']) -> SelfSet the thinking-effort hint (honoured by Claude; ignored elsewhere).
-
criteria(criteria: str) -> SelfOptional 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(configure: Callable[[DiffSettings], DiffSettings]) -> SelfConfigure the diff source (default: the working-tree diff,
git diff). -
include(*globs: str) -> SelfOnly review files matching these globs (default: all files).
-
exclude(*globs: str) -> SelfExclude files matching these globs (in addition to lockfiles).
-
max_diff_tokens(tokens: float) -> SelfCap the diff at roughly this many tokens, truncating the rest.
-
fail_when(configure: Callable[[GateSettings], GateSettings]) -> SelfChoose the gate that breaks the build (default: score above 7).
-
on_error(mode: Literal['fail', 'warn']) -> SelfWhat to do when the review itself fails (API error, refusal, bad JSON):
"fail"breaks the build (default),"warn"logs and passes. -
skip_if_key_missing() -> SelfSkip 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(mode: Literal['update', 'append'] | None=None) -> SelfAlso 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 theapiscope, AzureSystem.AccessToken, Bitbucket an app password.modechooses 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. Reviewers attached side by side to one target —.validateBefore(security, quality)— post one comment between them rather than one each: a table of every reviewer's verdict, the findings of all of them in one table with a column naming the reviewer (and its badge) that raised each, and each reviewer's full report folded underneath. Every one of them runs even when an earlier one fails its gate; the target then fails with all of their reasons. The comment is posted by the last of them, with its token, and appended when any of them appends. Each reviewer's discussion state rides in it under the reviewer's name, and a reviewer that used to post alone still reads its old comment. Another validation between two reviewers splits them, and a reviewer alone posts its own comment as before. -
comment_token(token: str) -> SelfThe 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 topull_requests: write, which makes the comments the app's rather thangithub-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. -
github_token(token: str) -> SelfBackwards-compatible alias for commentToken.
-
reactions(enabled: bool) -> SelfWhether the reviewer reacts on the pull request to show its progress (on by default, whenever
.comment()is set). On the description: 👀 while it runs, then 👍 when it passes clean, 🎉 when it passes clean where the previous run failed, 🤏 when it has findings under the failing threshold (😕 on GitHub, whose reaction set has no 🤏), 👎 when it fails. On the comment that started a comment-started run: 👍 as it starts, 😕 when the run could not review. On a trusted maintainer's reply: 👀 once read, ❤️ when it decided a finding. The reviewers on one pull request share the reactions, so the verdict shown is the worst of a run's. GitHub and GitLab have the reactions; on Azure DevOps and Bitbucket this is a no-op.falseturns all of it off, as does.quiet(), under which the reviewer does not speak on the pull request. -
badge(emoji: str) -> SelfThe emoji that marks this reviewer's findings — in the combined comment the reviewers of one target share (see comment), and on the review threads it opens. Defaults by kind: 🛡️ security, 🧹 code quality (
genericReviewer), 🔑 secrets, 🐛 correctness, ⚖️ license. Set one when two reviewers of a kind review side by side — and give one of them a name of its own, which is what the comment tells them apart by. -
quiet() -> SelfSuppress the findings printout and the job-summary section.
-
conventions_file(path: str, max_tokens: float | None=None) -> SelfFeed 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 viagit 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 roughlymaxTokens(default 8000). -
criteria_file(path: str, max_tokens: float | None=None) -> SelfProject-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 showwhen there is one, from disk otherwise, truncated at roughlymaxTokens(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. -
file_context(max_tokens: float | None=None) -> SelfAlso send the full post-image contents of the changed files (read via
git show HEAD:<path>), bounded at roughlymaxTokens(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() -> SelfAdd 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(configure: Callable[[DiscussionSettings], DiscussionSettings] | None=None) -> SelfEngage 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.
class ReviewSettings(Settings) The reviewers one AiTasks.review runs, as .validateBefore(a, b) would run them, and the target they review for.
-
target(name: str) -> SelfThe target the reviewers are attached to, named in their output;
reviewby default. -
generic(configure: Callable[[Reviewer], Reviewer] | None=None) -> SelfAdd a code-quality reviewer, as genericReviewer makes it.
-
security(configure: Callable[[Reviewer], Reviewer] | None=None) -> SelfAdd a security reviewer, as securityReviewer makes it.
-
secrets(configure: Callable[[Reviewer], Reviewer] | None=None) -> SelfAdd a leaked-secrets reviewer, as secretsReviewer makes it.
-
correctness(configure: Callable[[Reviewer], Reviewer] | None=None) -> SelfAdd a correctness reviewer, as correctnessReviewer makes it.
-
license(configure: Callable[[Reviewer], Reviewer] | None=None) -> SelfAdd a license reviewer, as licenseReviewer makes it.