Coduck logo Coduck

Documentation

Everything coduck does today, in the order you'll actually need it.

Overview

Coduck reviews what your coding agent is about to do, before it does it.

Status. Version 0.1.0. macOS and Linux, Intel and ARM.

The commands, most of which you type yourself:

CommandWhat it does
coduck initConfigure a repository. Once, from its root
coduck authConnect coduck to Notion, or to Jira and Confluence
coduck reviewReview your own work, or a branch you have not checked out
coduck review-pull-requestReview an open GitHub pull request against its own base
coduck postPut findings from a finished review on the pull request
coduck gateRun a gate. The coding agent calls this, not you. A stub today
coduck completionPrint a shell completion script for bash, zsh, fish or PowerShell

Every command takes --help: coduck review --help lists all of its flags. The full list is also in the Command reference. --verbose (-v) is on every command. It traces each step on stderr, and in a review it also prints the suggested fix under every finding.

Install

Coduck drives the coding agent you already run. It does not ship a model and it does not talk to a service of ours.

  • claude or codex on your PATH. Whichever you use, logged in as normal. Coduck calls it the way you would.
  • git, and a repository with a branch to review.

Nothing else. There is no account to create and no key to paste.

npm

npm install -g coduck-cli
coduck --version

Five packages, not one. Four carry a binary each and declare the platform they are for with os and cpu, named coduck-<os>-<arch>; coduck-cli holds a launcher and lists those four as optional dependencies. npm installs the one that matches your machine and skips the rest, so you download about 5 MB rather than the 21 MB all four weigh together.

Both names are unscoped, because the coduck organisation on npm is already taken.

No postinstall script, and nothing is downloaded after the install. A postinstall that fetches a binary fails closed under npm ci --ignore-scripts, which is ordinary CI policy, and fails outright on an air-gapped machine. The binary is in the package.

The install script

cd <the folder you unzipped this into>
sh ./install.sh

It picks the archive for your machine, checks it against checksums.txt, and refuses to install if they disagree.

It installs into the first conventional location that is both writable and already on your PATH, /usr/local/bin, Homebrew's bin, ~/.local/bin or ~/bin. If none of them qualifies it falls back to ~/.local/bin and prints the exact export PATH=... line to add, for the shell you run.

To choose the location yourself:

CODUCK_INSTALL_DIR=~/bin sh ./install.sh

Either way, check it landed:

coduck --version

Configure a repository

From anywhere inside the repository you want coduck to watch:

coduck init

It finds the repository root itself, by walking up to the nearest .git, so running it from a subdirectory configures the repository rather than leaving a stray .coduck/ somewhere below it.

Three questions, in this order:

  1. Which coding agent do you use? claude or codex. Recorded, not detected, see Which coding agent you run.
  2. Where do your tickets live? notion, atlassian or none. Picking one opens your browser straight away, see Connect your tickets. Default is none.
  3. Which documents should reviews be judged against? A list of what this repository already holds, to tick with SPACE. Nothing arrives ticked, see Pointing coduck at your own documents.

Every answer also has a flag, so a scripted run skips the prompts entirely. A non-interactive run with no flags declares claude, connects nothing and records no documents.

coduck init --agent claude --ticketing-system notion --instructions AGENTS.md
FlagWhat it does
--rootRepository root (default: the enclosing git repository, else the working directory)
--agentCoding agent you run: claude or codex
--ticketing-systemWhere your tickets live, connected during init: notion, atlassian or none
--instructionsA document reviews should be judged against. Repeatable
--disable-gateTurn a gate off. Repeatable: plan, dependency, completion
--forceReplace existing settings
Init takes no paths. There is one settings file per repository, and a monorepo needs no second one: what kind of work a change touches is worked out per file at review time rather than declared here. See Domains and frameworks.

Running init where settings already exist fails rather than overwriting. --force replaces them, and leaves your .coduck/.gitignore and every ephemeral directory alone.

Ticketing systems

The product reviewer judges a change against what was asked for. It needs to read the ticket or the requirements page, and it reads it from the system that holds it, as you.

Coduck connects to two systems today. One connection covers Jira and Confluence, because they are one Atlassian site.

SystemCommandCovers
Notioncoduck auth notionNotion pages
Atlassiancoduck auth atlassianJira issues and Confluence pages

Both use the same door: your browser, the system's own consent screen, and nothing to set up first. No integration to create, no app to register, no API token to paste, and no secret anywhere. Coduck asks the authorization server for an identity at the moment you connect, as a public client, and proves the exchange with a one-time verifier it generated itself. That is what lets a local binary do this with no back end of ours.

Coduck asks for no scopes. It reads what you can already see, or what you share with it, and the system decides what that means. It never creates a page, edits one, transitions an issue or comments on anything.

During init

coduck init asks where your tickets live. Answer notion or atlassian and your browser opens there and then, while you are still at the keyboard.

coduck init

? Which coding agent do you use?  claude
? Where do your tickets live?     atlassian

Opening your browser to connect coduck to Jira and Confluence...

To answer it from a script, or to skip it:

coduck init --ticketing-system atlassian
coduck init --ticketing-system none        # do not ask, do not connect

Connecting is never fatal to init. A browser that does not open, a consent screen you close, a network that is down: init has already written its settings, and the run ends with a warning naming the command to try later.

warning: could not connect to notion (...);
         run `coduck auth notion` when you are ready
Init asks nothing if you are already connected. A connection belongs to you and to this machine rather than to a repository, so it survives every checkout and every re-run of init. The summary reports it as tickets notion and moves on. To add the second system, name it: coduck auth atlassian.

Connecting later

Nothing about connecting depends on init, and you do not have to be in a configured repository, or in a repository at all.

coduck auth                  # asks which system, then connects it
coduck auth notion           # connect Notion
coduck auth atlassian        # connect Jira and Confluence

coduck auth with no subcommand asks the same question init asks, for somebody who skipped it then or who wants a second system. In a script, where there is nobody to ask, it prints its help instead.

Connecting again when you already are says so rather than opening a browser:

coduck auth notion

coduck is already connected to Notion. Use --forget to disconnect.

To disconnect, both ends:

coduck auth notion --forget

That drops what coduck holds on this machine. Remove coduck from the other end too, in your Notion settings or your Atlassian account settings, which is the half that actually revokes access.

During a review

Pass a page to a review and coduck connects on the spot if it has to. The browser opens at the start of the run, before the slow part, rather than forty seconds in when nobody is watching the terminal any more.

coduck review --base main --ticket https://acme.atlassian.net/browse/ENG-123

Fetching itself happens later, when the product reviewer is about to be given the page. Splitting the two is deliberate: consent is the half that needs a person, and it should be asked for while one is there.

An address from a system coduck cannot fetch is reported the instant the command starts, before anything is scanned, connected or spent. A fetch that fails afterwards, because the page was not shared or the address is wrong, leaves the product reviewer out of the report carrying the reason. The other reviewers run exactly as they would have.

Notion

Coduck recognises addresses on notion.so, notion.site and notion.com.

Connecting is not enough on its own. Notion decides what a connection can see through its own sharing, so each page has to be handed over:

  1. Open the page in Notion.
  2. ••• menu, top right.
  3. Connections, then add coduck.

Then pass it:

coduck review --base main \
  --requirements https://www.notion.so/acme/Checkout-rewrite-1a2b3c
Notion answers identically for a page that does not exist and a page that exists and has not been shared, on purpose, so that asking cannot be used to map a workspace. Coduck cannot tell them apart either, so it names the workspace it is connected to instead and lets you see which it was.

Jira and Confluence

One connection, one consent screen, both products. Coduck recognises addresses on atlassian.net, atlassian.com and jira.com.

There is nothing to share. Coduck reads the issues and pages you can already see.

Addresses it understands, which are the ones people actually paste. Below your own site, so https://acme.atlassian.net and then:

ShapeExample
A Jira issue/browse/ENG-123
An issue selected on a board/jira/software/projects/ENG/boards/7?selectedIssue=ENG-123
A Confluence page/wiki/spaces/ENG/pages/98765/Checkout+rewrite
A Confluence short link/wiki/x/AbCdEf

Whether an address is an issue or a page is decided from its path, and the reading tool is chosen to match. Coduck asks Atlassian's server what it offers and picks a tool that reads one thing: of the thirty-odd it exposes, the ones that create an issue, edit it, transition it or comment on it are never eligible, and a test enforces that.

Where credentials are stored

In your home directory, one file per system, readable by you and nobody else:

~/.coduck/credentials/notion.json       # 0600
~/.coduck/credentials/atlassian.json    # 0600
~/.coduck/credentials/                  # 0700

Nothing lands in the repository, and no token is ever written into .coduck/. The directory is owner-only as well as the files, because a listable credentials directory tells other users on the machine which services you have connected.

The access token expires hourly and is renewed without anybody present. If a renewal fails, because access was revoked or the refresh token itself aged out, the stored connection is dropped rather than retried on every run, and the next command that needs it asks for consent again.

Set CODUCK_HOME to keep connections somewhere else. That is the variable, not HOME.

Hooks, scripts and CI

Coduck never opens a browser where there is no terminal to open one from. A hook, a pipeline or anything with stdin redirected gets an error naming the command to run instead:

coduck is not connected to Notion, and cannot open a browser here;
run `coduck auth notion` first

A connection is per user and per machine, so connecting once from a terminal covers every checkout on that machine, including ones that have never run coduck init. A fresh CI runner has none, and there is no way to hand it one today: run those reviews without --ticket or --requirements; every reviewer still runs, and product judges the change against your documents instead of the ticket.

Review

Three jobs, two commands.

coduck review --base main                              # your own work
coduck review --target origin/theirs --base main       # a colleague's branch
coduck review-pull-request --url <github pull request url>

Everything is a flag. Nothing is positional, so no argument depends on where it sits and none means two different things in two commands.

A review reports five reviewers, always the same five, so that two runs are comparable and a missing one is visible rather than absent:

ReviewerThe question it answers
productWhat does this change decide for the user? With --ticket, also: did it do what was asked?
architectureDoes it fit how this system is built?
securityCan it be abused?
correctnessDoes it work, on the inputs it will actually see?
dryDoes the repository already have this? Did changing shared code break callers who did not ask for it?
This spends money and takes minutes. Each reviewer is a separate headless call to the coding agent you declared at init, running read-only against your own logged-in session, four at a time. --budget-dollars and --timeout cap one call each; --reviewers narrows the run.
The exit code is always 0 for a verdict. It is not a merge gate. A refusal is the exception, and exits non-zero.

What gets reviewed

What --target does decides which:

ReviewedUncommitted workWhere
without --targetyour working treeincludedin place
with --targetthat refexcluded, a ref has nonea temporary clone

Without --target that means everything: commits on this branch, staged changes, unstaged edits, and untracked files git is not already ignoring.

Reviewing a ref needs a working tree at that ref. Checking it out in yours would carry your uncommitted changes across, so your scratch file ends up reviewed as part of someone else's change. Coduck clones instead. Nothing is downloaded: the objects are already in your .git and git hardlinks them.

Your branch and your own work are never touched, so reviewing a colleague's branch works in the middle of your own. If you point --target at the branch you are standing on while your tree is dirty, coduck says how many changes it is leaving out rather than letting you discover it from the report.

--target is resolved in your repository before anything is cloned. A colleague's branch usually arrives as a remote-tracking ref, so it is origin/theirs rather than theirs.

The base branch

--base is required. Git does not record which branch a branch was cut from, so guessing it is unreliable: coduck used to try origin/HEAD, then main, then master, and on a branch cut from another branch that once turned a ten-file change into a 176-file review.

coduck review --base main
coduck review --base origin/develop   # a base you have not checked out

git log --oneline --graph --decorate -20   # not sure what you cut from?

Refused reviews

Some comparisons cannot tell you anything however well the review runs, and coduck declines them before spending a minute and real money. There are two.

The base already contains the branch, and has moved on past it. Nothing of the branch can be in the diff, so a review of it says nothing about the branch, which is how a finished ticket once got reported as "entirely unimplemented". Reviewing work you have not committed yet is not this case and is never refused.

The change is too large to read. Measured on the hand-written part alone, so a pull request that regenerates an API client is judged on the code somebody wrote:

MeasureMost one review can read
Hand-written diff1000 KB
Files a person wrote225
Lines a person wrote20,000

Every limit that was passed is named, not the first, because somebody told only about files will split a change and then hit the line limit. This refuses the whole review, the secret scan included: a report listing what it managed to look at reads as a report on the change.

A refusal exits non-zero, unlike a verdict. It looks like this:

coduck review

NOT REVIEWED   the base already contains this branch

  this branch   feature/ENG-TASKS-1245-COPY   no commits of its own
  the base      origin/feature/launchpad      59 commits ahead

  Only uncommitted work could differ, so reviewing against this base
  cannot tell you anything about the branch.

  Review against the base this branch was cut from
      git log --oneline --graph --decorate -20

Everything coduck says when it does not review goes to stderr, stdout carries a review or nothing, so a script reading it gets a report or an empty stream, never a sentence where a table should be.

A change set with no files in it is not a refusal, and not a verdict either. Coduck says there was nothing to review rather than drawing an APPROVED for work it never opened.

Reviewing against a ticket

Pass the ticket's address and coduck fetches it, as you, from the system that holds it. Every reviewer gets it; the product reviewer judges the change against it.

# a Jira issue
coduck review --base main --ticket https://acme.atlassian.net/browse/ENG-482

# a Notion page
coduck review --base main --ticket https://www.notion.so/acme/Checkout-rewrite-1a2b3c

# a Confluence page holding the spec, as well as the ticket
coduck review --base main \
  --ticket https://acme.atlassian.net/browse/ENG-482 \
  --requirements https://acme.atlassian.net/wiki/spaces/ENG/pages/98765

# a pull request takes the same two flags
coduck review-pull-request --url https://github.com/owner/repository/pull/1245 \
  --ticket https://acme.atlassian.net/browse/ENG-482
FlagWhat it takes
--ticketThe ticket's address, fetched. A bare identifier such as ENG-123 is accepted too, but only labels the report; nothing is fetched
--requirementsThe page holding what was asked for, such as a spec or PRD, fetched

Addresses on notion.so, notion.site, notion.com, atlassian.net, atlassian.com and jira.com are fetched. Anything else is refused as soon as the command starts, before any time or money is spent. Connect the system first with coduck auth, or let the review open the browser the first time. See Ticketing systems.

Without a ticket the product reviewer still runs, reviewing the change against your repository’s own documents and against itself, and asking the author what it cannot answer. With a ticket it can also say whether the change did what was asked, missed part of it, or did more.

A page over 400,000 characters is refused rather than truncated.

Reviewing a pull request

coduck review-pull-request --url https://github.com/owner/repository/pull/1245

Pass the full URL, not just the number. A bare 1245 would be resolved against whichever repository you happen to be in, and in an org with a frontend and a backend that reviews the wrong pull request while looking like it worked. If you do pass a number, coduck refuses and builds the address from your origin so you can paste it back.

You do not need to check anything out. Coduck clones into a temporary directory, reviews the pull request there, and deletes it afterwards. Your branch and your uncommitted work are untouched, so this works mid-change.

The base comes from the pull request itself. GitHub publishes pull requests as refs, and the first parent of refs/pull/N/merge is the base tip, so this needs no GitHub token, no login and no gh: just the access that cloned the repository.

Nothing is downloaded for the clone. The objects are already in your .git and git clone --local hardlinks them, so only the working files are new. On a real repository that took 0.27s. The clone and the two refs/coduck/ refs are removed whether the review succeeded or not.

Settings come from your repository, not the clone: which agent you run, and the documents reviews are judged against. The clone supplies code and nothing else. The report is labelled with the pull request number.

Open pull requests only. GitHub publishes that merge ref while a pull request is open and merges cleanly, and removes it once it is merged or closed. Review a merged one with coduck review --target.
GitHub only. GitLab and Bitbucket publish pull requests differently.

Reading the report

One line at the top, then a row per reviewer, then the findings. The headline is a code review's words, because that is what a pull request comment will have to say:

VerdictWhen
REQUEST CHANGESSomething must be fixed
COMMENTNothing must be fixed, and there is something worth saying
APPROVEDNothing found
NOT REVIEWEDEvery model call failed, so nothing below was read by a model

Findings sort into must fix, should fix and nits, and the headline turns on the first of those alone, so it and the table can never disagree. A reviewer that did not run draws a dash, never a zero, which would read as having looked and found nothing.

Work no requirement asked for is reported beside the findings rather than among them, and does not move the verdict. Engineers improve code they are already touching, and a review that reports every unasked-for line as a defect is one people learn to skim.

--verbose adds the suggested fix under every finding. The trace it also turns on goes to stderr, so coduck review | cat still gives the report alone.

Other flags

Both review commands take all of these.

FlagDefaultWhat it does
--targetyour working treecoduck review only: the ref to review, cloned
--baserequiredcoduck review only: what the change is measured against
--urlrequiredcoduck review-pull-request only: the pull request's address
--reviewersall fiveRun some reviewers only: --reviewers security,correctness. The secret scan always runs
--ticketnoneThe ticket this change belongs to, fetched as you
--requirementsnoneThe page holding what was asked for, fetched as you
--modelyour agent's ownModel to review with, passed to your coding agent
--budget-dollars2.50Most one reviewer's call may spend
--timeout5mHow long one reviewer's call may run
--verbose, -voffTrace every step on stderr, and print the fix under every finding

The repository is the one you are standing in; there is no --root on a review. Reviewers run four at a time and that is not adjustable.

Posting comments to a pull request

Coduck never posts on its own. When a pull request review finishes in a terminal it asks, and only the findings you pick are sent.

coduck review-pull-request --url https://github.com/owner/repository/pull/1245

   ...the report...

? What would you like to do next?
  > Post comments to GitHub
    Exit

? Which findings? [must fix / should fix / nits / all], or numbers like 3,4,9-11

Applying fixes is not built yet, so it is not offered as a third option.

Coming back later

Declining costs nothing. The review is saved with the pull request it was of, and coduck post sends it without paying for a second review.

coduck post --url https://github.com/owner/repository/pull/1245
FlagWhat it does
--urlrequired. The pull request to post to, as its full address
--runAn earlier run to post, named as it appears under .coduck/runs

The most recent review of that pull request is the one used. Coduck keeps the last twenty runs in a repository, so a review from last month is probably gone and posting it means reviewing again.

coduck post reviews nothing and calls no model. It reads a review that already ran and sends it.

GitHub access

Coduck holds no GitHub credential of its own. It posts with the access you already have, asking gh first, then git’s credential helper.

gh auth login      # or clone over HTTPS so git stores one

If the machine has no GitHub access, the menu is not shown at all. Coduck says so once, and the report is on screen either way.

Submitted as a comment, never as an approval or a request for changes. The verdict on a pull request stays a person’s to give.

Which findings can be posted

A comment has to attach to a line the pull request touched. Findings pointing anywhere else are listed on screen instead, so nothing is dropped silently.

If the fix is a single edit covering exactly the lines flagged, the comment carries a GitHub suggestion block you can apply in one click. If it spans other lines, the fix is written out underneath instead, with the lines it replaces, because applying it needs judgement.

Posting more than once

Every comment carries a fingerprint. Before posting, coduck reads what it has already said on that pull request and skips anything already there, so running it again, from another machine or as another person, will not duplicate comments.

Nothing is asked without a terminal. In CI or through a pipe, a review ends exactly where it did before any of this existed: the report, then the exit code. There is no flag that posts on your behalf.

Using coduck with Claude Code

An agent is a poor judge of its own work. It has the whole conversation in context, including every reason it talked itself into the approach, so asking it whether the change is good gets you the same reasoning back.

Coduck is a different reader. It runs the agent again with a fresh context, restricted, with no access to your conversation, given only the diff, the ticket and your team’s documents. It is the same model looking at the work cold, which is what a second pair of eyes actually is.

The loop

Claude writes the change
  → coduck review --base main --ticket <url>
  → Claude fixes what came back
  → coduck review --base main --ticket <url>
  → open the pull request

The review reads your working tree, so it runs before anything is committed. Claude can fix a finding and have it reviewed again without a branch, a push, or a pull request existing yet.

As a skill

The version worth setting up first. Claude invokes it itself when it is finishing a change, because the description tells it when to.

# .claude/skills/review-with-coduck/SKILL.md
---
name: review-with-coduck
description: Use before telling the user a change is finished, and before
  opening a pull request. Runs an independent review of the working tree.
---

Run the review against the base this branch was cut from:

    coduck review --base main --ticket <the ticket url, if there is one>

It takes a few minutes and calls the model several times. Wait for it.

Then:

- Fix everything under MUST FIX before saying the change is done.
- For SHOULD FIX, either fix it or tell the user why you did not.
- Ignore nothing silently.
- If the product reviewer says the change does not match the ticket,
  stop and ask the user rather than arguing with the finding.

Re-run the review after fixing. A finding that survives a fix usually
means the fix was wrong, not that the finding was.
The last two lines matter more than they look. Without them an agent will often explain why a finding does not apply, which is the failure mode this whole loop exists to prevent.

As a slash command

For running it yourself rather than leaving it to Claude:

# .claude/commands/review.md
Run `coduck review --base main $ARGUMENTS`, wait for it to finish, then
summarise what must be fixed and fix it.
/review --ticket https://acme.atlassian.net/browse/ENG-482

As a hook

You can run a review from a Stop hook in .claude/settings.json, so it fires whenever Claude finishes a turn.

Think about this one before you wire it. A review takes minutes and spends real money on every call, and a Stop hook fires on every turn, including the ones where Claude answered a question and changed nothing. A skill that Claude invokes when a change is actually finished costs a fraction of it.

Give it the ticket

Without --ticket or --requirements, the product reviewer can only judge the change against itself and your documents. With one, it answers the question you most want answered: does this change do what was asked for. Pass the address, not a summary, and coduck fetches it as you. See Ticketing systems.

This is also what catches the case an agent is most prone to: code that works, reads well, and implements something adjacent to the ticket rather than the ticket.

Keeping the change small

Two things in a review push back on size, and both are worth knowing about.

  • Work nobody asked for is reported separately. Coduck lists it beside the findings rather than among them, and it does not move the verdict. You get the map of what was added, without every unasked-for improvement being called a defect.
  • A change too large to read is refused. Past 1000 KB, 225 files or 20,000 lines of hand-written work, coduck declines rather than producing a report on the part it reached. See Refused reviews.

If you want Claude to keep pull requests small, the useful instruction is in your CLAUDE.md rather than in the review: tell it to stop and ask before doing work the ticket did not ask for. Coduck tells you when that happened; it cannot stop it happening.

What init writes

Init writes inside .coduck/ and nowhere else. It touches no file of yours.

In the repository, committed:

  • .coduck/config.yaml, the coding agent, the documents reviews are judged against, and gate toggles. No secrets, ever: a connection lives in your home directory.
  • .coduck/.gitignore, keeps the ephemeral and personal files out of git, even under git add -A. Written once and never rewritten, so a line your team adds to it survives coduck init --force.

Yours, gitignored:

  • .coduck/config.local.yaml, overrides anything in the committed file. This is where you say you run codex while the team's file says claude, rather than committing your preference onto everybody.

Ephemeral, safe to delete:

  • .coduck/cache/ and .coduck/decisions/, made by init.
  • .coduck/runs/, made by the first review. The last 20 runs are kept and older ones are removed as each review finishes.

Settings are read committed-file-first, then the local one, then flags. A layer that does not mention something does not override it, maps merge key by key and document lists are combined, so a local file can add one note without restating the team's.

Settings reference

Everything .coduck/config.yaml accepts. config.local.yaml takes the same keys. An unknown key is a parse error rather than a line that quietly does nothing.

# .coduck/config.yaml
version: 2

coding-agent: claude

instructions:
  all:          [AGENTS.md]
  product:      [docs/product/glossary.md]
  architecture: [.claude/skills/architecture.md, docs/adr/*.md]
  security:     [.claude/skills/security.md]
  correctness:  [docs/testing.md]
  dry:          [docs/shared-modules.md]

gates:
  plan:       { enabled: true,  on_error: fail_open }
  dependency: { enabled: true,  on_error: fail_closed }
  completion: { enabled: true,  on_error: fail_open }

generated_paths: [dist/, proto/**, "*.min.js"]
reviewed_paths:  [api/openapi.yaml]
KeyWhat it does
coding-agentThe CLI coduck invokes: claude or codex
instructionsDocuments to judge against, per reviewer plus all. See below
gatesPer-gate enabled, and on_error: fail_open or fail_closed
generated_pathsFiles a tool wrote, beyond the ones coduck already recognises. Not reviewed, and not counted towards the size a review can hold
reviewed_pathsFiles that must be reviewed whatever else says otherwise. Beats every other rule, including coduck's own

generated_paths takes three forms: dist/ for a directory at any depth, proto/** for everything under a prefix, *.min.js for a glob. reviewed_paths exists because coduck's shipped patterns are a guess about somebody else's repository: a team that hand-writes an API specification coduck would skip needs a way to say so that nothing overrules.

domain and domains are still accepted so that files written by an older coduck still parse. Nothing writes them and nothing reads them. See Domains and frameworks.

Coding agent

coduck init asks which agent you use, claude or codex, and records it:

coding-agent: claude

This is what coduck invokes for every reviewer, for the domain classification, and for anything else that needs a model. The calls run headless and read-only against your own logged-in session, so they count against whatever that session already counts against.

The declared agent is not the detected one. A repository routinely carries a .claude directory from a colleague's setup while you run something else, so declaring your agent is what decides, not what happens to be lying around.

Your team's documents

Coduck decides how code is reviewed. Your team supplies what to judge it against.

This matters more than it looks, reviewing agents run restricted and load nothing on their own, not CLAUDE.md, not AGENTS.md, nothing in .claude/. Whatever your team has written down is invisible to a review unless it is named here.

# .coduck/config.yaml
instructions:
  all:          [AGENTS.md]
  architecture: [.claude/skills/architecture.md, docs/adr/*.md]
  security:     [.claude/skills/security.md]
  correctness:  [docs/testing.md]

Everything under all goes to every reviewer, everything under a reviewer's name goes to that one. The five keys are product, architecture, security, correctness and dry; a misspelled one is a parse error rather than a line that does nothing.

Globs are allowed and may match nothing, a team often lists docs/adr/*.md before writing the first one. A path named exactly and not present does not stop a review, because settings outlive the files they point at, but it is named on stderr, so a review judged against less than you believe is visible rather than silent.

Two limits worth knowing. Paths must stay inside the repository. And each reviewer is a separate model call, so the same document is charged once per reviewer, there is a size limit, and going over it names the files rather than silently truncating your standards. Init warns at the moment you tick past it.

coduck init offers what it can find, up to 40 entries, with each one's file count and size beside it so a one-page standard is distinguishable from a documentation site. It never opens a document to decide: judging which of a team's standards matter belongs to the person reading the list. Anything it left out is counted in the question, so a short list does not look complete.

You cannot switch a reviewer off permanently or replace how coduck reviews one. --reviewers narrows a single run.

Domains and frameworks

Backend security and frontend security are different documents with different content, so coduck has to know which kind of work a change touched before it can choose what to load.

That is worked out per review, from the paths in the change, by asking your coding agent. It used to be a prefix map typed at init, and a directory routinely holds both kinds of code: packages/ui is React and packages/prisma is a schema; apps/web/pages/api/*.ts is a server route and apps/web/components/*.tsx is a component. The map got seven of fifty-two wrong.

One extra call, one turn, cents against a review costing dollars. It degrades rather than fails: if there is no agent to ask, or the answer comes back unusable, coduck loads every domain, which is broad and visible rather than narrow and wrong.

Two domains ship today, backend and frontend. Within one, coduck also detects the frameworks and APIs in play from the repository's own manifests, and loads the instructions for those. Infrastructure and data-science were removed rather than shipped unmeasured: a reviewer whose quality is unknown is worse than one that is absent, because the report gives it the same row and the same weight as the ones that were tested.

Secret scanning

The review itself is agentic: every judgement about your code is made by a model reading the change. The one exception is a scan for credentials, which runs on every review before any model is called.

It is a scan rather than a reviewer because a leaked key is a pattern, not a judgement. It costs nothing, takes about a second, and its findings report under security alongside everything the security reviewer found.

Looks forHow
Credentials in the changeContent of every changed file
Private keys and certificatesPEM blocks, and files such as .pem
Files that carry secrets by conventionFilename, for example .env and .env.production

The last one matches on the name alone and never on content, because .env.production may hold nothing today and everything tomorrow.

This cannot be switched off and --reviewers does not narrow it. A credential it finds carries the same weight in the verdict as anything a reviewer reports.

Gates and hooks

Three gates, each against a coding agent event:

GateEventOn error
planUserPromptSubmitfail open
dependencyPreToolUse, scoped to Bashfail closed
completionStop and SubagentStopfail open
Not wired up yet. coduck gate is a stub: it appends a line to .coduck/gate.log, prints one line, and always exits 0 so the agent proceeds. No gate logic exists, and coduck init registers no hooks with your coding agent. The toggles below are recorded so that the work has something to read.

coduck init --disable-gate plan writes enabled: false for that gate, and an unrecognised name is reported rather than ignored, so nobody believes a gate is off when it is still on.

To try the wiring by hand, the hook command is the bare word coduck:

echo '{"hook_event_name":"Stop"}' | coduck gate completion

The bare word, resolved from PATH, rather than an absolute path that is right on your machine and breaks a teammate's checkout. Which means: if gates ever do not fire, check that coduck is on the PATH of whatever launches your agent. A GUI-launched editor does not inherit a shell's PATH on macOS. install.sh places coduck in a directory that is already on PATH; go install uses $(go env GOPATH)/bin, which is not.

Command reference

Every command explains itself. Add --help to any of them for its full description, examples and flags. This section is the same list, checked against the CLI.

coduck --help
coduck review --help
coduck auth notion --help

These work on every command:

FlagWhat it does
-h, --helpShow the command’s description, examples and flags
-v, --verboseTrace every step on stderr, and print the fix under every finding
--versionPrint the version (root command only)

coduck init

coduck init [flags]
FlagWhat it does
--agentCoding agent you run: claude, codex
--ticketing-systemWhere your tickets live, connected during init: notion, atlassian or none
--instructionsA document reviews should be judged against. Repeatable: --instructions AGENTS.md
--disable-gateGate to turn off. Repeatable: plan, dependency, completion
--forceReplace existing settings
--rootRepository root. Default: the enclosing git repository, else the working directory

coduck auth

coduck auth
coduck auth notion [--forget]
coduck auth atlassian [--forget]

coduck auth on its own asks which system to connect.

FlagWhat it does
--forgetOn notion or atlassian: drop the stored connection instead of making one

coduck review

coduck review --base <ref> [flags]
FlagWhat it does
--baseRequired. What the change is measured against
--targetBranch, tag or commit to review, in a temporary clone. Default: your working tree, including uncommitted work
--reviewersReviewers to run, comma separated or repeated. Default: all five: product, architecture, security, correctness, dry. The secret scan always runs
--ticketLink to the ticket in Notion, Jira or Confluence, fetched as you. Or a bare identifier such as ENG-123, which only labels the report
--requirementsLink to what was asked for in Notion, Jira or Confluence, fetched as you
--modelModel to review with, passed to your coding agent. Default: whatever it is set to use
--budget-dollarsMost one reviewer’s call may spend. Default 2.5
--timeoutHow long one reviewer’s call may run. Default 5m

coduck review-pull-request

coduck review-pull-request --url <pull request url> [flags]
FlagWhat it does
--urlRequired. The pull request’s full address
--reviewersReviewers to run, comma separated or repeated. Default: all five: product, architecture, security, correctness, dry. The secret scan always runs
--ticketLink to the ticket in Notion, Jira or Confluence, fetched as you. Or a bare identifier such as ENG-123, which only labels the report
--requirementsLink to what was asked for in Notion, Jira or Confluence, fetched as you
--modelModel to review with, passed to your coding agent. Default: whatever it is set to use
--budget-dollarsMost one reviewer’s call may spend. Default 2.5
--timeoutHow long one reviewer’s call may run. Default 5m

coduck post

coduck post --url <pull request url> [--run <id>]
FlagWhat it does
--urlRequired. The pull request to post to, as its full address
--runAn earlier run to post, named as it is under .coduck/runs. Default: the most recent review of that pull request

coduck gate

coduck gate <name> [--root <path>]

Called by your coding agent, not by you. A stub today; see Gates and hooks.

FlagWhat it does
--rootRepository root. Default: the repository containing the working directory

coduck completion

coduck completion bash
coduck completion zsh
coduck completion fish
coduck completion powershell

Prints a shell completion script, so Tab completes commands and flags. Run coduck completion zsh --help for where to put it.

Development

make test          # go test ./... -race
make lint          # go vet, gofmt check, goreleaser check
make build         # bin/coduck
make snapshot      # cross-compile all four platforms into dist/, publish nothing
make test-install  # exercise install.sh against dist/
make npm           # build the five npm packages into npm/build/, publish nothing
make test-npm      # package from a fake dist/, no goreleaser run needed

CODUCK_HOME relocates where connections are kept, which is what keeps a test suite from deleting the connection of whoever ran it.