Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 3 additions & 28 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,39 +40,14 @@ jobs:
- name: Format
run: bun run fmt:check

- name: Smoke the CLI
run: bun src/cli.ts --help

- name: Bundle the sweep engine
- name: Build the engine
run: |
bun build src/review-sweep.ts --target=bun --format=esm --outfile=/tmp/stupify-review-sweep.ts
if grep -E "from ['\"]@(stupify|bevyl-ai)/" /tmp/stupify-review-sweep.ts; then
bun run build
if grep -E "from ['\"]@(stupify|bevyl-ai)/" dist/review-sweep.ts; then
echo "::error::review-sweep bundle still imports an external package"
exit 1
fi

- name: Check package contents
run: |
set -euo pipefail
pack_tgz() {
dir="$1"
pattern="$2"
output="$(cd "$dir" && bun pm pack)"
printf '%s\n' "$output" >&2
tgz="$(printf '%s\n' "$output" | grep -E "$pattern" | tail -n 1)"
if [ -z "$tgz" ]; then
echo "::error::could not find packed tarball for $dir"
exit 1
fi
printf '%s/%s\n' "$dir" "$tgz"
}

ROOT_TGZ="$(pack_tgz . '^stupify-cli-[0-9].*\.tgz$')"
TMP="$(mktemp -d)"
npm install --prefix "$TMP" "$ROOT_TGZ"
npm exec --prefix "$TMP" -- stupify --help
rm -rf "$TMP" "$ROOT_TGZ"

- name: Install site
working-directory: site
run: bun install --frozen-lockfile
Expand Down
85 changes: 0 additions & 85 deletions .github/workflows/release.yml

This file was deleted.

77 changes: 0 additions & 77 deletions .review/CORPUS.template.md

This file was deleted.

27 changes: 8 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
# stupify — agent guide
# stupify

**stupify** is a code reviewer that runs on Codex and judges PRs against a curated "good code" corpus + an
anti-slop rubric. Read `README.md` and `docs/ARCHITECTURE.md` first.
The code is the spec; `src/review-sweep.ts` reads top to bottom as what one sweep does.

## Layout
- Codex through `@bevyl-ai/agent-tools` on the exe.dev gateway, never the Claude API. The verdict is a tool call, never parsed prose.
- One Bun file on a cron, `config.env` beside it, state in three JSON files. No server, no database, no CLI.
- Only real reviews reach a PR. Failures are logged and throttled locally, never posted.
- Every `gh --json` boundary is `Schema.parse`d; a malformed row throws, it does not skip.
- Smallest change that solves it. Before keeping anything, name its second reader or writer; otherwise delete it.

- `src/cli.ts` — the `stupify` command: a `@clack/prompts` setup wizard + `run`. The only interactive surface.
- `src/review-sweep.ts` — the engine. Bun; shells out to `git`/`gh`/`codex`. The CLI deploys
a copy to `~/.stupify/` and a cron runs it. Runs `main()` only when invoked directly (`if (import.meta.main)`),
so it stays importable for tests — but keep it standalone and spawn it from the CLI, never `import` it.
- `.review/` — the **taste templates** (`REVIEW-PROMPT.md`, `RUBRIC.md`, `CORPUS.md`). These get copied into
the _target_ repo and edited there; in this repo they're the starting point.

## Rules

- Smallest change that solves it; deleting/simplifying beats adding layers. Treat new code as a cost.
- `bun run typecheck` must pass (strict, `noUncheckedIndexedAccess`). No `as` assertions on external JSON —
`Schema.parse(JSON.parse(...))` at the boundary. Malformed `gh --json` throws; don't skip the row.
- The engine validates every `gh --json` boundary and fails LOUD (posts an error comment) rather than silently.
Keep that property.
- Never publish to npm or push public changes without the operator asking.
`bun run check` gates every commit: typecheck, lint, fmt, build. Deploy is `deploy/push.sh <vm>` (DEPLOY.md).
88 changes: 88 additions & 0 deletions DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Deploying stupify

stupify is one Bun file on a cron, next to a `config.env`. It rides [exe.dev](https://exe.dev): a GitHub
integration proxies `gh` for the repo and the `llm` integration fronts your ChatGPT plan for codex, so the box
holds no tokens. Onboard once with `ssh exe.dev`.

## Provision a reviewer for a repo

```sh
git clone https://github.com/Octember/stupify && cd stupify && bun install
ssh exe.dev integrations add github --name stupify-acme-widgets --repository acme/widgets
ssh exe.dev new --name stupify-acme-widgets --integration stupify-acme-widgets --setup-script /dev/stdin < deploy/vm-setup.sh
ssh exe.dev integrations attach llm vm:stupify-acme-widgets
deploy/push.sh stupify-acme-widgets acme/widgets stupify-acme-widgets.int.exe.xyz
```

`vm-setup.sh` writes the keyless codex config and installs bun. `push.sh` builds `src/review-sweep.ts` into one
file and copies it to `~/.stupify/review-sweep.ts`; a first push (three arguments) also writes `config.env` and
installs the minute cron, with `GH_HOST=<integration>.int.exe.xyz` on the cron line so `gh` talks to the
integration. The next cron tick runs it. Reviews land on open PRs within about a minute of a push.

## Taste

The reviewer reads `.review/` from the target repo's default branch: `REVIEW-PROMPT.md` (the spec),
`RUBRIC.md` (what counts as slop), `CORPUS.md` (the code yours should look like). Start from this repo's own
`.review/`. A repo without one falls back to `~/.stupify/.review` on the box, which you place by hand:

```sh
scp -r .review stupify-acme-widgets.exe.xyz:.stupify/.review
```

## config.env

Every knob is a line in `~/.stupify/config.env`, read fresh each sweep. A one-shot env var wins over the file.

| key | default | meaning |
| --------------------------- | ----------------- | ---------------------------------------------------------------------- |
| `REPO_SLUG` | required | `owner/repo` |
| `DEFAULT_BRANCH` | `main` | branch the checkout tracks and `.review/` is read from |
| `REVIEW_DIR` | `.review` | taste dir inside the repo |
| `SCOPE` | `auto` | `auto` reviews every non-draft, non-bot PR; `label` only labelled ones |
| `REVIEW_LABEL` | `codex-review` | force-include label: oversized diffs and bot PRs opt in with it |
| `DIFF_LINE_CAP` | `20000` | skip bigger diffs unless labelled |
| `MAX_PRS` | `15` | reviews per sweep, counted after dedup skips |
| `MAX_REVIEWS_PER_DAY` | `0` (off) | hard daily ceiling |
| `FAIL_RETRY_MIN` | `60` | wait before retrying a head whose review failed |
| `CODEX_JOBS` | `3` | concurrent codex sessions |
| `CODEX_MODEL` | codex default | `-c model=…` for the session |
| `CODEX_EFFORT` | `high` | `model_reasoning_effort` |
| `CODEX_GATEWAY_POOL` | (off) | ordered `llm` hosts to rotate through on a quota wall |
| `CODEX_ROTATE_COOLDOWN_MIN` | `10` | minimum minutes between rotations |
| `DRY_RUN` | `0` | list what would be reviewed, run no codex, post nothing |
| `STUPIFY_HOME` | beside the bundle | where `config.env`, `state/`, `repo/`, `worktrees/` live |

## Update

```sh
deploy/push.sh stupify-acme-widgets
```

## Watch it

```sh
ssh stupify-acme-widgets.exe.xyz tail -f .stupify/state/sweep.log
```

- `reviewing PR #N @ sha (base main)` then `#N done (2 inline, 1 blocking)`, `#N clean first pass — posted LGTM ✅`, or `#N nothing new — posted still ✅`: working.
- `review FAILED for #N — <reason>`: codex failed; the reason is the gateway's or the kit's. A usage wall ends the sweep and rotates the gateway if `CODEX_GATEWAY_POOL` is set.
- `gh pr list failed — …` / `gh api pulls failed — …`: the GitHub integration; run the same `gh` command on the box.
- `refresh failed`: the checkout; check `DEFAULT_BRANCH` and the integration.
- `skip #N — diff … > cap`: the size cap, not a fault.

State is three JSON files under `~/.stupify/state/`: `reviewed.json` (heads already reviewed), `failures.json`
(heads to leave alone until `FAIL_RETRY_MIN` passes), `daily.json`. Delete a PR's key from `failures.json` to
retry it on the next tick.

## Run it anywhere else

Any box with `bun`, `gh` (authed for the repo), and `codex` (logged in) works the same way: put the bundle and
`config.env` in a directory, run `bun review-sweep.ts` from cron. `STUPIFY_HOME` points it elsewhere. Behind an
exe.dev GitHub integration, put `GH_HOST=<integration>.int.exe.xyz` in the environment (the cron line), not
in `config.env`: `gh` reads it from the environment.

## Tear down

```sh
ssh exe.dev rm stupify-acme-widgets
```
51 changes: 12 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

**AI agents are rats in a maze. They reach for what they know.** And unless you show them better, what they know is slop: [most software is garbage](https://github.com/openai/codex/issues/28224), and they'll [happily](https://github.com/thesysdev/openui/issues/517) [imitate](https://github.com/RsyncProject/rsync/issues/929) [it](https://github.com/anthropics/claudes-c-compiler/issues/1).

[![npm](https://img.shields.io/npm/v/@stupify/cli?color=cb3837&label=%40stupify%2Fcli)](https://www.npmjs.com/package/@stupify/cli)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

![A real stupify review with four kinds of finding in one pass: a high-confidence bug, a fail-open footgun, a reinvented SDK primitive, and a dead config seam.](docs/proof/00-slop.png)
Expand All @@ -20,52 +19,26 @@ _actual issues, tells the coding agent exactly how + what to fix_ **[more catche
- **On your personal Codex plan.** stupify reviews with [Codex](https://github.com/openai/codex), running on the $20-$200/month plan. API usage is roughly 50x more expensive, enjoy the subsidized tokens while they last
- **Slop, named.** Code review is cheap. Taste is expensive. Codify the goodies, let the LLM pattern match

## Add the reviewer
## Run it

```bash
npx @stupify/cli
```

```
┌ stupify
◇ using integration acme-widgets
◇ VM stupify-acme-widgets created
└ stupify is provisioned for acme/widgets 👀
```

stupify rides on [exe.dev](https://exe.dev) with no keys or servers to run. Setup takes about two minutes and doesn't require payment.
stupify is one Bun file on a cron, next to a `config.env`. It rides [exe.dev](https://exe.dev): a GitHub
integration proxies `gh` and the `llm` integration fronts your ChatGPT plan for codex, so the box holds no tokens.

```bash
npx @stupify/cli <owner/repo> # provision for a specific repo
npx @stupify/cli setup # run the reviewer on this machine instead of a VM
npx @stupify/cli status # show the latest sweep as a workflow
ssh exe.dev rm stupify-<owner>-<repo> # tear it down
git clone https://github.com/Octember/stupify && cd stupify && bun install
ssh exe.dev integrations add github --name stupify-acme-widgets --repository acme/widgets
ssh exe.dev new --name stupify-acme-widgets --integration stupify-acme-widgets --setup-script /dev/stdin < deploy/vm-setup.sh
ssh exe.dev integrations attach llm vm:stupify-acme-widgets
deploy/push.sh stupify-acme-widgets acme/widgets stupify-acme-widgets.int.exe.xyz
```

Every live sweep also posts a GitHub commit status named `stupify/review` on the PR head commit: pending while
queued/running, success when reviewed or policy-skipped, failure when stupify posts findings, and error when the
reviewer itself failed. Set `GITHUB_STATUS=0` in `~/.stupify/config.env` to turn that off, or
`GITHUB_STATUS_CONTEXT=your/context` to rename it.

If the `gh` identity the sweep runs under can't write commit statuses (e.g. a proxy integration whose token is
statuses:read-only), give stupify its own GitHub App: create an App with **Commit statuses: Read & write**,
install it on the repo, then set `GITHUB_STATUS_APP_ID=<app id>` and `GITHUB_STATUS_APP_KEY=<path to the App's
.pem>` in `config.env`. Statuses then post via the App (short-lived installation tokens, minted and cached by the
sweep); everything else still goes through `gh`.

### Connect your accounts

The reviews run on Codex. On exe.dev that's a keyless **LLM integration**: it fronts your ChatGPT/Codex plan, so
the VM holds no API key and your plan is billed instead. Link one once at [exe.dev/integrations](https://exe.dev/integrations)
and provisioning attaches it for you
Open a PR and it's reviewed within a minute. Runbook, every knob, and how to read the log: [DEPLOY.md](DEPLOY.md).

## Your taste

Point stupify at the files you _wish_ all your code looked like, and it scaffolds a `.review/` in your repo:

```bash
npx @stupify/cli init src/best.ts src/clean-service.ts # inlines them; you add one line of "why" each
```
A `.review/` in the repo it reviews: `REVIEW-PROMPT.md` (the spec), `RUBRIC.md` (what counts as slop), and
`CORPUS.md` (the code yours should look like). Start from this repo's own, then point `CORPUS.md` at the files you
_wish_ all your code looked like, with one line each on why.

## License

Expand Down
Loading