crysk

agent
Guvenlik Denetimi
Uyari
Health Uyari
  • License — License: Apache-2.0
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Gecti
  • Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

Multi-agent PR review engine for Claude Code and OpenCode: parallel specialty hunters, an adversarial refuter that challenges every severe finding, and deterministic blocking/advisory tiers. High precision, narrow coverage — lifecycle and state-machine bugs, not style.

README.md

Crysk

crysk.dev · npm · skills

A multi-agent PR-review engine. Specialty hunter agents fan out in parallel over a repo checkout and
its diff, a mechanical dedupe merges their drafts, an adversarial refuter challenges every severe
finding, and deterministic code — never a model — assigns blocking/advisory tiers.

Its measured profile, stated up front: high precision, narrow coverage. What it reports is
almost always real (13% false positives measured). Its hunters check value contracts across the diff
(units, domain, conversions, what consumers assume), resilience (lookup and dedupe keys, retries and
locks, CI guards, test isolation), local logic errors, and access-control parity with sibling code;
stale state, races and component lifecycle have no dedicated hunter since 2026-11-08. It says nothing
about style or convention. Locally it is an assistant, not a merge gate: every report closes with that
disclaimer, and nothing it prints blocks a merge. In CI its commit status is a gate by default: require the
crysk check in branch protection, and blocking findings block the merge. mode: advisory keeps that check
green.

Renamed from pr-hero. Crysk used to be pr-hero. The package is now crysk (npm install -g crysk,
npx -y crysk), and the pr-hero command stays as an alias for one version. What an install made under the
old name wrote is still read for one version: /pr-hero … comments, PRHERO_* variables, <!-- pr-hero-…
markers, pr-hero commit statuses (read, no longer written), ~/.prhero, .prhero/ and .prheroignore. A workflow file scaffolded
before the rename only answers /pr-hero …: re-run crysk ci init --force for it to accept /crysk … too.
The repository moved to cryskai/crysk and new workflows use uses: cryskai/crysk@v0. juanmaagd/pr-hero stays as a mirror with the same tags and releases, so a workflow that still says uses: juanmaagd/pr-hero@v0 keeps working.

Two ways to use it

  • Local CLI: install Crysk on your machine and review branches or PRs from your terminal.
    Start at Requirements.
  • CI only, no local install: install the skills into your coding agent, add one workflow file
    to the repository, and every pull request is reviewed in GitHub Actions. Nothing below
    Requirements applies to this path. It is described next.

CI only: skills plus one workflow

You need Node (for npx), an authenticated gh, and a GitHub repository with Actions enabled. You do
not need Bun, the Claude Code CLI, or a global Crysk install: the review runs on the GitHub
runner, which installs its own runtime. npx -y crysk setup --ci checks gh for you: when it is
missing or logged out, it says why it is needed and prints the commands, and in a terminal it offers
to install it (macOS with Homebrew) and to run gh auth login. It never blocks the setup.

  1. Install the skills into your coding agent from skills.sh:

    npx skills add cryskai/crysk
    

    crysk-ci-setup sets up the workflow, crysk-pr opens a PR and waits for its review, and
    crysk-loop answers the findings. crysk covers the local CLI, which this path does not use.

  2. Ask your agent to set up Crysk CI in the repository. crysk-ci-setup first asks you how
    re-reviews should work: a level (minimal, balanced, thorough or manual), gate or advisory, how CI
    pays (a subscription gets no spend ceiling, an API key gets one you choose), the changed-lines
    threshold above which a PR is skipped (default 4000, on any billing), and whether to tune the
    advanced settings (score weights, observe-only, trusted actors, files limit, exclusions). Then it runs
    npx -y crysk setup --ci with your answers (writes .github/workflows/crysk.yml, or updates an existing pr-hero.yml in place, and the
    level's keys in .crysk/config.json, and prints a summary) and npx -y crysk init
    (writes a REVIEW.md scaffold when the repo has no review instructions), writes the repository's review instructions, and stores
    one secret with gh secret set: CRYSK_CLAUDE_OAUTH_TOKEN (subscription; claude setup-token
    prints it on any machine that has the Claude Code CLI, so this one does not need it) or
    CRYSK_CLAUDE_API_KEY (pay as you go). To run some steps on OpenCode models, it also sets the
    CRYSK_MODELS variable and one CRYSK_OPENCODE_* secret per provider. Without an agent, run the same
    commands yourself and follow the Quick path.

  3. Merge the workflow, .crysk/ and REVIEW.md into the default branch. A comment-triggered run
    (/crysk review) reads them from the base branch, so it starts nothing until the workflow is
    there, and reviews without your instructions until REVIEW.md (or .crysk/gotchas.md) is.
    Then require the crysk status check in branch protection if you want it to block merges. The legacy pr-hero status is no longer posted, so a rule that requires it must move to crysk.

  4. Open pull requests as usual. Every new PR gets a review posted as PR comments; later pushes
    follow the level you chose (balanced re-reviews a push only when it is risky or the last pass
    found enough, at most 2 automatic passes), and a PR above the size threshold is skipped. Comment
    /crysk review to review a skipped push anyway. Ask your agent
    to open the PR (crysk-pr), and it waits for the review with npx -y crysk status --pr <n> --wait --json, then hands the findings to crysk-loop, which replies to each one in its
    thread and pushes the fixes.

Everything about the workflow (credentials, models and OpenCode, spend limits, the gate, error codes)
is in docs/github-actions.md.

Requirements

These apply to the local CLI. The CI-only path needs none of them
on your machine.

Crysk does not call any model API of its own. It spawns the Claude Code CLI as a subprocess and
inherits that CLI's authentication. claude is the engine; everything else here is plumbing.

Dependency Needed for Without it
Claude Code CLI, installed and authenticated everything nothing runs — it is the engine
git everything nothing runs
Bun ≥ 1.3 on PATH install options B and C only option A carries its own runtime
GitHub CLI (gh), authenticated review --pr, --post, watch, reverts, corpus local review still works in full
codegraph optional code intelligence hunters navigate with git and grep, with less structural context

Run crysk doctor first: it checks every one of these and prints what is missing. The one thing it
cannot check is Bun — on an npm or source install the launcher itself is a Bun script, so a missing
Bun fails before any Crysk code runs, with env: bun: No such file or directory. Install Bun
(brew install oven-sh/bun/bun, or curl -fsSL https://bun.sh/install | bash) or use option A,
whose binary needs no Bun on your machine.

What a review bills against

A run uses exactly one Claude credential, chosen when it starts, in the order your
credentials.claude sets (Credentials):

  • Your Claude subscription (claude signed in on this machine, or CRYSK_CLAUDE_OAUTH_TOKEN from
    claude setup-token) → runs consume your subscription quota. No per-token invoice arrives.
  • An Anthropic API key (CRYSK_CLAUDE_API_KEY, or ANTHROPIC_API_KEY) → the same runs are billed as
    API usage
    .

The default order is the subscription first, so with both available the subscription runs and the key is
not used: on macOS a Claude login always counts. To bill the key, put it first in your person config,
{"credentials": {"claude": ["api_key", "subscription"]}}. The Claude CLI child sees only the chosen
credential.

The dollar figures Crysk prints — the cost band before a run, crysk usage after one — are
always a token-derived estimate, because that is the only unit comparable across both models. On
a subscription, read them as "how much of my window will this consume", not as a bill. A step on an
OpenCode model bills to that provider's credential instead (see docs/configuration.md).

Installation

# Option A: Standalone binary (macOS / Linux, bundles its own Bun — still needs claude and git)
curl -fsSL https://raw.githubusercontent.com/cryskai/crysk/main/install.sh | bash

# Option B: Global npm install (brings Bun 1.4.4 as an optional dependency, or uses
# the Bun >= 1.3 on PATH when npm skips optional dependencies)
npm install -g crysk

# Option D: No install, for an agent or a one-off run (needs only Node). It cannot
# register the MCP server or the launchd agents: npx's cache is temporary, so those
# refuse and ask for option B. The launcher uses the bundled Bun only when it runs
# (`bun --version` is checked first), otherwise the Bun on PATH, and exits 127 naming both if neither works.
npx crysk doctor

# Option C: From source / development (the bin runs on Node; on a machine with Bun
# and no Node, run `bun bin/pr-hero.js` or use option A)
git clone https://github.com/cryskai/crysk.git && cd crysk
bun install && bun link

See Requirements above — claude and git are needed by every option.

From install to first review

Only want the CI review? You do not need to install anything: see GitHub Actions — Quick path (npx -y crysk setup --ci, npx -y crysk init, one secret).

Seven steps, in this order. Steps 2, 5 and 6 are the ones that get skipped, and
each one breaks the step after it.

# 1. Install (option A shown; B and C put crysk on PATH themselves)
curl -fsSL https://raw.githubusercontent.com/cryskai/crysk/main/install.sh | bash

# 2. Put it on THIS shell's PATH. The installer edited your rc file, and an rc
#    file only reaches shells started after it — the one you are in is not one.
#    (The installer prints the exact line. It is ~/.crysk/bin, or ~/.prhero/bin
#    on a machine that already had a ~/.prhero from before the rename.)
export PATH="$HOME/.crysk/bin:$PATH"     # fish: set -gx PATH $HOME/.crysk/bin $PATH

# 3. What is installed, what is missing, what blocks a review.
crysk doctor

# 4. Skills + MCP registration, and — inside a git repo — scaffold .crysk/.
cd /path/to/your-repo && crysk setup

# 5. Track that scaffold, or ignore .crysk/. Local review refuses to run on a
#    dirty tree, and an untracked .crysk/ or REVIEW.md is a dirty tree.
git add .crysk && git add REVIEW.md   # REVIEW.md only if setup wrote one
git commit -m "chore: crysk config"

# 6. Replace the placeholder review instructions with real ones. Not paperwork — see below.
$EDITOR REVIEW.md   # or the .crysk/gotchas.md the repo already had

# 7. Review the checked-out branch against its merge base.
crysk review

On step 4. setup is fully non-interactive — it asks nothing and is safe to run
unattended. It is also optional for reviewing: review never checks whether it ran,
only crysk doctor reports it. What it buys is the skills, the MCP registration and
the repo scaffold; skip it and you scaffold the repo yourself with crysk init. It does
not commit or .gitignore what it scaffolds, which is why step 5 exists.

Skill drift. setup (and upgrade) installs the bundled skills into each agent's skills
directory. A skill copy that differs from the bundled one and has no recorded digest is kept as it is and reported as drift, never overwritten, so your local edits survive; crysk setup prints each kept skill, and crysk upgrade replaces it.

Skills without Crysk. The public skills are also on
skills.sh, installable into any agent with the
skills CLI:

npx skills add cryskai/crysk                            # pick interactively
npx skills add cryskai/crysk --skill crysk              # or crysk-pr, crysk-loop, crysk-ci-setup

On step 6. REVIEW.md is injected verbatim into every hunter's system prompt, and
what setup/init write there is a placeholder: - <subsystem>: <the thing that looks like a bug but is deliberate, and why>. A review that reads that is a review billed in
full by hunters who know nothing about your repo, and it comes back looking clean. So
Crysk refuses to run while the scaffold's human-attention-required marker line is
still present. Write a handful of true lines and delete the marker — or delete the file
to review with the default guidance only.

Other entry points, once the above works:

crysk                            # the interactive TUI menu, in a TTY
crysk review --pr                # the current branch's PR, in an isolated worktree
crysk review --pr 123            # any PR by number

Breaking change in v2.0: Invoking crysk with zero arguments in a TTY now opens the interactive TUI menu instead of implicitly starting a local review. In non-interactive environments, it prints the help text and exits with code 2. Set CRYSK_NO_TUI=1 (PRHERO_NO_TUI=1 still works) to bypass the TUI in automation.

Every paid run prints a plan and a cost band, then asks for confirmation (--yes skips the prompt).

For AI agents

You are most likely here because someone told you to install Crysk and review a repo.
Four things go wrong when an agent runs the sequence above:

  1. Step 2 is not optional, and not deferrable. If each of your commands runs in its own
    shell, an export PATH=... in one does not reach the next. Either prepend the export to
    every Crysk invocation, or call the binary by absolute path: ~/.crysk/bin/crysk (~/.prhero/bin/pr-hero on an install from before the rename).
  2. Step 5 is yours. crysk setup scaffolds .crysk/ and REVIEW.md and neither commits
    nor ignores them, so the next crysk review fails the clean-tree gate — on files setup
    itself created. Commit them (or add .crysk/ to .gitignore) before reviewing.
  3. Write the review instructions yourself (step 6). You have the repository in context, which
    makes you the best author available. Read the code and write the real ones: the
    deliberate oddity that looks like a bug, the invariant enforced in a file the diff never
    shows, the ownership rule a newcomer would violate. Leaving the placeholder is not a
    softer option — the review is refused until you replace it and delete the
    human-attention-required marker line.
  4. You cannot authenticate claude. Stop and ask the human. Crysk spawns the Claude
    Code CLI and inherits its authentication; there is no Crysk-side key to set. If
    crysk doctor reports claude missing or unauthenticated, say so and wait — every
    step after it fails until a person fixes it.

Configuration — two layers

There are two config files, and the difference between them is who they belong to:

File Whose Committed?
~/.crysk/config.json (or ~/.prhero/config.json on an install from before the rename) yours, across every repo on this machine no — it is your machine
<repo>/.crysk/config.json the team's yes, normally — it is shared through git

crysk init creates the repo file and, when the repo has no review instructions, a root
REVIEW.md; it never overwrites existing files, and a repo that already has
.crysk/gotchas.md keeps it and gets no REVIEW.md (one would shadow it). crysk setup
scaffolds the same files when it runs inside a git repo, so a repo you set up needs no separate
init. (Running init afterwards is harmless: it keeps what exists and reprints the git
reminder.) Commit .crysk/ (or add it to .gitignore) and REVIEW.md — an untracked one trips
local mode's clean-tree gate.

Old directory names. The directories used to be ~/.prhero/ and <repo>/.prhero/, and the ignore file
.prheroignore. Those are still read, for one version, when the new name does not exist. Nothing is moved for
you: crysk doctor prints the full procedure for your machine. For the home that is the mv ~/.prhero ~/.crysk
plus whatever still points at the old path (the .prhero/bin PATH line, the launchd agents, the MCP registration),
and for a repo git mv .prhero .crysk (plain mv plus a .gitignore edit if the directory is not tracked) and
git mv .prheroignore .cryskignore. A new-named directory only wins while it holds Crysk's own files
(~/.crysk needs a config.json, prhero.db, repos/ and the like; <repo>/.crysk needs config.json or
gotchas.md), so an empty ~/.crysk never hides a ~/.prhero in use. Do not create ~/.crysk by hand while
~/.prhero is in use. When both hold files, the .crysk one wins and the old one is ignored.

Run crysk config to see which layer decided what. It lists every key with its value, the
layer it came from, and both file paths whether or not they exist. It never edits anything.

Which model each step runs on (models, per role, with fallbacks and effort), and how Crysk reads
credentials for each backend, are their own topic — see docs/configuration.md.

The one rule

The more specific layer wins — except where the less specific one is protecting your money.

So the team file usually beats your global one. The exception exists because a committed file must
never be able to enlarge your bill: on the two keys that spend, the team can only ask for less.

config.json keys

Key Where it can live What it does
agents_dir both Directory holding the agent prompt files (deep-review-*.md, review-*.md). Relative paths resolve against the file that named it.
default_base repo only Base ref when --base is not given (e.g. "dev"). Without it: the remote head (origin/HEAD), then main. Rejected in the global file on purpose — see below.
parity_trigger_paths repo only Glob list. The parity hunter runs only when a changed path matches one — no globs, no parity hunter.
suspicion_priors repo only [{path, weight, reason}] hints injected into hunter prompts — point them at your known hotspots.
summary.enabled both — team may only turn it off Whether the engine-owned summarizer runs. Defaults to true, because it spends money and a silent opt-out would make the bill differ from the plan.
models global only The models per role (default, hunter.<key>, refuter, consolidate, scout, summary, judge), each a list of one to three tried in order, e.g. {"summary": ["claude:haiku-latest"]}. Unset, each step runs the model its prompt declares. See Models. summary.model is gone: "haiku" still reads, with a warning; anything else is refused.
credentials global only Which Claude credential a run uses, in order: {"claude": ["subscription", "api_key"]} (the default). See Credentials.
max_verification_steps both — team may only lower it Cap on re-review verification spawns (item 7). Defaults to 8; 0 is legal and pauses verification.
max_concurrent_steps both — team may only lower it Cap on steps (hunters, refuters, verify, scout, summarizer) in flight at once. Unset means unlimited. Set it below a provider's concurrency limit (e.g. 8 for one that allows 10 in-flight requests) so a refuter fan-out does not draw HTTP 429s.
max_changed_lines both — team may only lower it Line budget threshold before the size gate skips a review. Defaults to 4000; 0 disables line budget check.
max_changed_files both — team may only lower it File count budget threshold before the size gate skips a review. Defaults to 150; 0 disables file budget check.
scout both — team may only turn it off Whether the reconnaissance scout hunter runs before review. Defaults to false.
materiality_gate both — team may only turn it off Defaults to false. When true, an extra isolated judge checks each blocking finding against Crysk's posting policy; a finding judged not material under normal use is posted as advisory instead of blocking. Findings about security, credentials or data loss are never demoted. Adds one judge call per blocking finding. Claude route only: on other routes findings stay blocking. Known limitation: on a re-review, a finding that was blocking on an earlier head still counts toward the gate.
post both — team may only turn it off Whether review reports publish as GitHub PR comments by default. Defaults to false.
post_min_severity both — the repo file wins either way The lowest finding a PR review posts: suggestion (everything), warning (hides advisory suggestions) or blocking (hides every advisory finding). A blocking finding always posts. Hidden findings get no comment; the summary counts them in one line. The gate and local reports still see every finding; re-review admission scores only what was posted, so a hidden finding never triggers a re-review. Unset posts everything; setup --ci writes the recommended warning when the file has no value.

Why default_base is repo-only, since it is the one restriction that looks arbitrary: the
resolver checks the config value before the remote head. A global default_base would therefore
preempt autodetection on every repo that stays quiet, so the first repo whose default branch differs
would silently review the wrong commit range — with a plausible branch name on the plan card.

Flags always win, and may exceed a cap. A flag is you typing an intent in front of the cost band,
which is a different thing from a committed file asking on your behalf. --agents overrides
agents_dir (then the repo file, then the global, then CRYSK_AGENTS_DIR, or PRHERO_AGENTS_DIR on an install from before the rename); --base overrides
default_base; --summary / --no-summary and --model override the summary keys.

REVIEW.md and gotchas.md

Repo-specific facts the hunters must know (fragile invariants, deliberate oddities, past incidents),
in a root REVIEW.md — the file name Claude Code Review reads. Without one, Crysk reads
.crysk/gotchas.md (or the legacy .prhero/gotchas.md); when both exist, REVIEW.md wins and
the run and crysk doctor say gotchas.md ignored: REVIEW.md takes precedence. The hunters and
the refuter read the file as facts about the codebase (the refuter still needs code to refute a
claim). With materiality_gate on, the materiality judge also applies its "Not blocking here" rules
to blocking findings, but only for style, naming, test hygiene, generated code and dev scripts: it
never demotes a logic, correctness, data, contract or security finding, whatever the file says.
Nothing filters advisory findings by it.

Optional, but a file that exists must be real — not merely non-empty. With neither file the
review runs on the default guidance and doctor reports it as degraded. The file init/setup
scaffold carries a human-attention-required marker line, and review and doctor both refuse it
by name, as they refuse an empty file: a placeholder passes any emptiness check while telling every
hunter nothing, which buys a full-price review that reports clean. Replace the <subsystem> lines
with facts about your repo, then delete the marker line. Reviewing a tree you cannot write to?
Supply a file from outside with --gotchas <file> (and --config <file>); it overrides both.

Where it is read from. Local crysk review reads the working tree. crysk review --pr and the
watcher read the operator checkout (--repo), never the PR worktree. In GitHub Actions it is the
workflow's checkout: a pull_request run reads the PR merged onto its base, so a PR that edits the
file is reviewed with its own edit; a /crysk review comment run on the generated workflow reads the
default branch.

Commands

Command What it does
crysk / crysk menu Open the interactive terminal user interface (TUI) with context-aware navigation, review launcher, and submenus.
crysk review Review the checked-out branch against the merge base with --base. Requires a clean tree sitting on --head (default HEAD).
crysk review --pr [n] Review a GitHub PR: by number, or — bare, no number — the PR that belongs to the current branch (errors loudly if it has none). Resolves the range through gh, runs in a detached worktree with its own codegraph index, then records the result in the run's findings.json.
crysk review --pr <n> --post Same, then publish the report as one marked PR comment — re-runs update it in place, never stack.
crysk doctor Run diagnostic environment and dependency checks (git, claude auth, gh, codegraph, config). Works inside or outside a repository.
crysk setup Machine-level onboarding: skill synchronization and MCP registration — and, when run inside a git repo, it also scaffolds that repo's .crysk/config.json and, when the repo has no review instructions, a root REVIEW.md, so no separate init is needed there. Fully non-interactive. It does not commit or .gitignore what it scaffolds; do that yourself before reviewing.
crysk activity [--kill <pid>] Inspect running reviews (with elapsed time, PID, repo/PR), watcher spend against daily cap, and completed run history. --kill <pid> safely terminates a running review.
crysk post --pr <n> --from <run-dir> Replay-publish a previous run's findings onto the PR (same plan as --pr --post). --dry-run previews at $0.
crysk triage reply --pr <n> --from <run-dir> --finding F00N --tag <tag> --body-file <path> Post one triage reply. The driver picks the parent from the posted <!-- crysk-finding marker (never path/line), renders the marker + badge, posts, and resolves the inline review thread. Reads anchor.patch (else diff.patch) from the run dir to remap post lines the same way --post does. --body-file is reasoning only.
crysk status --pr <n> [--wait] [--json] Read-only, through gh only: the PR's Crysk gate, its posted findings, and a next_action (wait, review, triage, fix, recheck, override-or-fix, done). --wait polls until an in-flight or newly-triggered review settles before emitting (a review that has not posted its first status within 3 minutes is not waited for: counted from the /crysk (or /pr-hero) comment, or, after a push, from when --wait starts; a run still queued after that returns the current status); --json prints one pr-hero.status/v1 object on stdout. See docs/github-actions.md.
crysk config Read-only, $0: every config key with its value and which layer decided it (repo / global / capped / default), plus both file paths whether or not they exist.
crysk config set <key> <val> [--person|--team|--watch] Set a scalar configuration value in the selected layer (defaults to --person). Capped team keys accept and annotate over-ceiling values.
crysk config unset <key> [--person|--team|--watch] Remove a configuration value from the selected layer.
crysk config --edit Open the interactive configuration editor across Person, Team, and Watcher layers with buffered draft editing, Save/Discard/Clear actions, and capped ceiling annotations.
crysk upgrade [--check] Upgrade crysk to the latest release and reconcile installed skills and MCP registrations. --check checks for updates without installing.
crysk uninstall [--purge] Managed uninstallation: unloads launchd background daemons, cleans up skills, unregisters MCPs, and optionally purges ~/.crysk with --purge.
crysk init Scaffold .crysk/ in the current repo — the command for every repo after the one you ran setup in. Omits keys your global file already supplies, so a new repo does not re-state what you have already said once. Idempotent: existing files are kept untouched, and it reprints the commit-or-ignore reminder.
crysk watch add Opt the current repo (or --repo <path>) into the watcher; --post makes its reviews publish to the PR. Idempotent — re-adding updates the post flag. See Watching PRs automatically.
crysk watch remove Take the current repo (or --repo <path>) back out. Idempotent — removing what is not listed just says so.
crysk watch status Read-only, $0: config summary, today's launches vs the cap, launchd state, lock, last activity.
crysk watch --once Run ONE watcher tick: scan the watched repos, pick the next unreviewed open PR, review it. --dry-run shows the whole decision for $0.
crysk watch install Install the macOS launchd agent that runs a tick every --interval minutes (default 15). This is the opt-in to automatic spend.
crysk watch uninstall Unload and remove that agent.
crysk gc Collect worktrees under ~/.crysk that are merged/closed or idle >72h. --dry-run prints the table and removes nothing. --repo <clone> scopes to one origin; default is the whole home. Also removes leftover verified execution snapshots under $TMPDIR/prhero-exec-snapshots (dead owner process or older than 6h).
crysk gc install Install the macOS launchd agent that runs crysk gc every --interval minutes (default 360 = 6h). No reviews, no watch.json, no daily cap — the $0 sweeper so trees do not wait for a review or the watcher.
crysk gc uninstall Unload and remove that GC agent.
crysk gc status Read-only, $0: whether the GC agent is installed, its interval, plist, and tick log.
crysk usage [--all] Read-only, $0: print the observability store's per-run rows (cost, tokens, findings) — every completed review, local and PR, auto-ingests into one global ~/.crysk/metrics.db. Scoped to the current checkout's origin by default; --all shows every origin instead.

Flags worth knowing: --dry-run (plan + cost band, creates nothing), --yes, --model <m>,
--scout / --no-scout (toggle pre-hunt reconnaissance scout),
--no-shared-prefix (turn off the default shared hunter prefix: hunters share one cached system prompt
holding the patch, their roles move to the user message and a one-turn warm-up writes the cache first; Anthropic routes only,
since other providers already cache each hunter's own loop),
--post / --no-post (toggle PR comment publishing),
--force (local only: review this diff even when the size gate would skip it;
in GitHub Actions the same forced re-review is a /crysk review comment on the pull request,
which runs on the Actions runner; /crysk override instead unlocks a red gate with no review),
--full (run un-capped refuter verification steps),
--no-summary (skip the summarizer; see summary in config above),
--out <dir> (run dir for review),
--all (usage only: every origin instead of just this checkout's).
crysk --help lists everything.

PR mode, in one paragraph

--pr resolves the range from the PR itself: a merged PR diffs from its merge commit's first parent
(the base as it was when the PR landed — merge, squash and rebase all converge at the fork point),
an open one from the recorded base tip. The review runs in a worktree at
~/.crysk/repos/<origin>/worktrees/pr-<n> — your checkout, your index and your uncommitted work are
never touched, and reviews run while you keep working. Two checkouts of the same GitHub repo share
one worktree. Worktrees are kept and reused across re-runs until GC (crysk gc: merged/closed or
72h idle). That collector is not a background timer — it runs when you invoke it, at the end of
review --pr, on a watcher tick, or on a schedule via crysk gc install (macOS launchd, default
every 6h, no reviews, no watch.json). Each run prints the cleanup command (git worktree remove --force … — never rm -rf, a live
codegraph daemon holds a socket in there).

When to run it

Reviews launch two ways: by hand, or through the opt-in local watcher (crysk watch, below)
— nothing else ever fires on its own. The two moments where a manual run makes sense:

  1. Before opening the PR — crysk review on your branch. The cheapest moment to catch
    something: you are still in context and nothing is published yet.

  2. On an existing PR — the moment it opens, standing on its branch, the whole command is:

    crysk review --pr --post
    

    Bare --pr resolves the branch's own PR (loud error if there is none), the review runs in its
    worktree while you keep working, and --post publishes the one marked comment. A PR number works
    anywhere (--pr <n>), no matter what you have checked out.

Automation and CI — status

Stage Status
Manual CLI (review, --pr, --post) Available now — this README.
Auto-trigger on new PRs / new pushes (local watcher) Available now — crysk watch, below. Deliberately opt-in: every review spends real money, so nothing fires until you watch add a repo AND watch install the agent.
CI mode (GitHub Actions) Available now — official action.yml at the repo root, canonical .github/workflows/crysk.yml (a repo that already has pr-hero.yml keeps it), full setup in docs/github-actions.md. The commit-status gate (mode: gate, the default; mode: advisory opts out), exit 0 on findings, the size/budget gates, and crysk setup --ci scaffolding all apply.
Required status check that blocks merges Available in CI — the crysk commit status is the check (mode: gate, the default). The legacy pr-hero status is no longer posted. Require it in branch protection. Fail-closed for a head with no run stays deferred.

Watching PRs automatically — crysk watch

The watcher is a tick, not a daemon: crysk watch --once makes one pass — list open PRs in
each configured repo, skip what is already covered, launch at most one review — and exits.
launchd (installed below) fires a tick every N minutes and is the supervisor. Drafts are skipped,
and each PR is reviewed once by default — see the on_push knob below.

Opt in

cd /path/to/your-repo
crysk init               # once per repo, if .crysk/ does not exist yet
crysk watch add --post # opt THIS repo in; --post publishes each review to its PR
crysk watch install      # start ticking — this is the moment automatic spending starts
crysk watch status       # anytime: config, cap usage, launchd, lock, last activity

A repo is watched only if added (--repo <path> works from anywhere; watch remove takes it
back out; both are idempotent). Preview any tick for $0 first: crysk watch --once --dry-run
prints every candidate, every skip and its reason (draft / reviewed-local / reviewed-remote /
in-flight / attempts / cap / window), and the one (pr, head) a real tick would launch. install accepts
--interval <min> (default 15) and captures your current PATH (launchd's own PATH knows nothing
of bun, gh, claude or codegraph) — re-run install after moving tools around;
watch uninstall stops the schedule.

Worktree GC is a separate agent: crysk gc install (default every 6 hours, --interval in
minutes). It runs crysk gc — no reviews, no watch.json, no daily cap. Preview with
crysk gc --dry-run; gc status / gc uninstall match the watcher verbs.

The underlying file — ~/.crysk/watch.json

watch add/watch remove own the repo list — there is no need to edit the file by hand for
membership, and their rewrites preserve any keys they do not know. The two global knobs are still
plain JSON you may tune directly:

{
  "repos": [{ "path": "~/Desktop/your-repo", "post": true }],
  "daily_cap": 5,
  "window": { "start": "09:00", "end": "19:00" }
}
Key Default What it does
repos[].path — Operator checkout of a repo to watch (its .crysk/ config and review instructions are used, exactly like review --pr). Managed by watch add/remove.
repos[].post false Pass --post to the spawned review — publish each result as the PR's one marked comment. Managed by watch add [--post].
repos[].on_push false Re-review the repo's PRs on every new push. Default: one review per PR — see below. Managed by watch add [--on-push].
daily_cap 5 Global max reviews launched per local calendar day, across all repos. 0 pauses launching entirely.
window null (always) Local-time window outside which ticks do nothing, e.g. {"start":"09:00","end":"19:00"}. Overnight windows (start > end) work.

One review per PR — the on_push knob

By default a push to an already-reviewed PR does not re-trigger a review: an active PR takes
many pushes, and re-billing $2–5 for each would drain the cap on one PR's churn. The posted
comment names the exact sha it reviewed, so it never lies about coverage — a reader sees at a
glance that later pushes are unreviewed. When the new head deserves a fresh pass, run it by hand
(crysk review --pr <n> --post); crysk watch add --on-push restores per-push re-review for
a repo that wants it. Planned follow-up: a cheap independent judge that reacts to pushes by
checking whether the new head addressed the posted findings and replies in-thread, instead of
re-reviewing from scratch.

What keeps the bill bounded

  • The cap and the window exist because the watcher and your interactive Claude sessions share
    the same subscription quota — a runaway watcher would starve the work you are actually doing.
    The cap is counted from ~/.crysk/watch.log (the launched lines ARE the counter), and a
    launch is logged before it starts, so even a crashed review counts.
  • Max 2 attempts per (pr, head): a PR that keeps killing the review is dropped until it gets
    a new push, instead of eating the cap every day.
  • Cross-machine guard: a posted Crysk comment declares which head it reviewed; any watcher
    that sees the current head already declared skips the PR (and under the one-review-per-PR
    default, any Crysk comment at all is proof enough). A fresh pending crysk commit status
    on the head (the yellow dot while a review is running) skips as in-flight.

Operating model and honest limits

Run one watcher per repo per team. The comment-based cross-machine guard only protects repos
with post: true, and only after the first comment lands. A pending crysk commit status
closes the rest of the race once either review has started — two watchers can still duplicate
if both launch before either posts pending. With post: true the PR still converges to a
single comment. Not macOS? watch --once from cron works the same —
the PID lockfile keeps overlapping ticks from doubling up. Logs: structured events in
~/.crysk/watch.log, raw tick/review output in ~/.crysk/launchd.log.

What a run produces

Run artifacts land outside the repo, in ~/.crysk/repos/<origin>/runs/<run>/:

File What it is
report.md The human-readable review.
findings.json The machine artifact (schema v1.0.0): findings, tiers, refuter verdicts, usage, provenance.
diff.patch, pipeline.json, steps/ The exact diff reviewed — after exclusions, so it is byte-for-byte what the hunters read — plus pipeline provenance (including the excluded paths), per-agent drafts and logs.
diff.raw.patch The unfiltered diff of the same range. Written only when exclusions actually dropped a file, since otherwise it would duplicate diff.patch.
anchor.patch The full PR diff (merge-base..head, after exclusions) that inline comments anchor against. On a first review it equals diff.patch; on an incremental re-review diff.patch is only the push delta, and this file still covers the whole PR, so a finding on an untouched line of the PR gets a review thread.

Cost and expectations

  • $2–5 per review of a typical PR-sized diff — the floor is real, because hunters read the tree,
    not just the diff — and larger trees cost more (a 45-file tree measured $11–15). Wall time is
    minutes, not seconds: ~10 for a small diff, up to ~25 measured on a large tree. The printed band
    is an order-of-magnitude guide, not a quote — the same tree has billed 34% apart across runs.

The size gate — a cost gate, not a quality gate

Past a size threshold Crysk skips the review instead of running it. Small trees bill $1.9–$4.8;
the 45-file / +2775 −1237 bench tree billed $6.58–$17.92 across 18 iterations — the cost roughly
triples and its spread widens to ~2.7x. The gate exists because that bill is unpredictable, and
because an unattended watcher must not be the thing that discovers it.

It is not a claim about quality. We have no evidence that a bigger diff reviews worse: attention
dilution was tested and falsified (fixtures/scale-probe.ts), and the one measured Greptile-only
miss came from a 7-file PR. If a large diff is worth its price, review it anyway.
Locally that is --force. In GitHub Actions it is a pull request comment, /crysk review,
from a user with write access (a workflow scaffolded before the comment trigger uses
gh workflow run pr-hero-force.yml -f pr=<n> instead). The comment does not review on your
machine; the Actions runner does. It also clears the CI admission rules and the budget ceiling for
that one run. gh run rerun on a skipped run does not: it measures the gates again and skips again.

The gate counts effective changed lines (insertions + deletions) and files — excluded content is
subtracted first, so a regenerated lockfile beside a ten-line change does not trip it. Excluded by
default: bun.lock, package-lock.json, yarn.lock, pnpm-lock.yaml, Cargo.lock, go.sum,
*.min.js, *.min.css, *.snap. A repo can add its own exclusions with .cryskignore — see
below.

Those exclusions come out of the reviewed diff itself: diff.patch is what the hunters are
handed, so the number the gate measures is the number that gets paid for. If every changed file is
excluded, there is nothing to review — Crysk exits without spawning anything.

.cryskignore — repo-defined exclusions

A .cryskignore file at the repo root adds your own exclusions on top of the 9 defaults above.
The dialect is a subset of gitignore(5), not a full reimplementation — most everyday patterns
work exactly like .gitignore, but two things are worth knowing before you rely on it:

  • Only one file, at the repo root, is read. Nested per-directory .cryskignore files (the way
    git supports a .gitignore in every directory) are not read. A file at
    openspec/.cryskignore is silently ignored — put every rule in the one at the repo root instead.
  • Matching is case-sensitive, matching git's own behavior. This is a deliberate departure from
    the ignore npm package (the common JS gitignore implementation), which is case-insensitive by
    default — if that is where your intuition comes from, README.md will not match readme.md here.

Supported: comments (#) and blank lines, negation (!pattern re-includes), anchoring
(a leading or interior /), trailing-slash directory-only rules, */?/[a-z]-style classes,
** at any position (including matching zero directories), and literal {/} (this dialect has no
brace-alternation syntax — braces are always literal characters, unlike some glob libraries).
The last matching rule wins, builtins evaluated first, then your file's rules in order — so a
rule can re-include one of the 9 defaults (!bun.lock) or exclude something the defaults do not.

One documented deviation from real git: unlike git, which cannot re-include a file whose parent
directory is itself excluded (gitignore(5) states this outright — it is a tree-walk optimization),
this engine matches a finite diff file list, not a tree walk, so docs/ then !docs/keep.md DOES
re-include docs/keep.md here.

Under CI (GitHub Actions, or any CI/GITHUB_ACTIONS environment), .cryskignore is read from
the PR's base ref, never the PR's own branch — a PR cannot widen its own exclusions to hide
changes from its own review. Local review, and local --pr <n> review without CI, read it from your
working tree instead. A malformed file aborts the whole review before anything is spent, naming the
file, line number, and offending text.

crysk watch honors .cryskignore too, in its per-tick scheduling filter (both the aggregate
check and the per-file rescue), and in a one-call pre-launch check that catches a PR whose files are
all excluded but whose aggregate alone would not have flagged it. It reads .cryskignore from the
watched repo's own working tree on the machine running the watcher — never a base ref — because the
watcher runs from an operator's own checkout of a repo that operator explicitly opted in, with no PR
author supplying that checkout the way CI's PR-triggered runs have one.

The count is also whitespace-blind wherever git is reachable (local mode and PR mode count from
git diff -w --ignore-blank-lines --numstat), so a formatter or linter sweep does not consume the
budget. Two paths cannot be: --pr <n> --dry-run and the watcher's per-tick checks read GitHub's own
counters, which carry no whitespace information — both label their verdict an estimate, and both can
only ever over-count, never under-count.

Profile Lines Files For
Conservative --max-changed-lines 800 --max-changed-files 150 Tight budget; only small PRs auto-review.
Default (shipped) 4000 150 Everyday PRs pass; only very large trees are skipped.
Permissive --max-changed-lines 0 --max-changed-files 150 You would rather pay than skip — disables the line limit.
crysk review --pr 42 --dry-run            # prints the gate verdict, spends nothing
crysk review --pr 42 --force              # local: review it anyway (does NOT skip the cost prompt)
gh pr comment 42 --body "/crysk review"  # CI: same forced re-review, on the Actions runner
gh pr comment 42 --body "/crysk override"  # CI: unlock a red gate on this head, no review, $0
gh pr comment 42 --body "/crysk stop"      # CI: skip automatic reviews on later pushes, until /crysk review
crysk review --pr 42 --max-changed-lines 0     # 0 disables that limit entirely
crysk watch add --max-changed-lines 800         # per-repo threshold for the watcher

A skipped review exits 1 with a one-line reason and no stack. In watch mode it logs
skipped … reason=too-large (or reason=nothing-to-review, when every changed file is excluded)
and costs nothing further: it does not consume a poison-PR attempt,
writes no review marker, and does not arm the one-review-per-PR state — a force-push that shrinks the
PR makes it eligible again on the next tick, because the gate is recomputed every tick.

  • Findings vary run to run on the same tree. That is the nature of the instrument: treat single
    runs as evidence, not verdicts, and triage before acting.
  • Verify what it reports. Findings are claims with cited code, tiered by deterministic rules — the
    report says so itself. An advisory report reads assistant report, not a merge gate; every line is a claim to
    verify.
    A CI report in gate mode backs the crysk commit status, the required check: a blocking finding
    sets that status to failure. A finding the PR did not introduce (pre-existing or base-only) is
    reported as advisory and does not block it, unless it touches auth, secrets, isolation or data loss,
    which keep blocking.

Isolation model

Spawned agents run with a read-only tool surface (Read/Grep/Glob plus the codegraph MCP server and
nothing else): no Write, no Bash, no network tools, --strict-mcp-config, no user settings. The
driver owns every file write and all gh/git I/O. In PR mode the reviewed tree is a detached
worktree containing only tracked bytes at the PR's head — agents never see your working copy.

Development

bun test               # offline test suite — no network, no spawns, fake runners
bun run typecheck      # tsc --noEmit, strict
bun run check          # biome over src + test

The architecture in one line each lives in CLAUDE.md; the stage plan lives in ROADMAP.md,
and the earlier phases in docs/history/. The short version: the flow is data (ReviewSpec), agents are
prompt files, orchestration is testable TypeScript — models judge, code governs.

Yorumlar (0)

Sonuc bulunamadi