Guides
Running locally & offline testing
The entire service builds, runs, and tests offline — no network, no credentials, no Azure. Everything infra-shaped sits behind an interface with an in-process implementation, selected by environment variables:
| Switch | Effect |
|---|---|
DATABASE_URL=memory |
In-memory state stores instead of Postgres |
QUEUE_MODE=memory |
In-process queue instead of Azure Service Bus |
MOCK_ADO=1 |
FakeAdoClient with a seeded demo PR instead of the ADO REST API |
MOCK_RUNNER=1 |
Deterministic MockRunner instead of the Claude Agent SDK |
CHECKOUT_MODE=fixture |
Copy a fixture directory instead of git clone |
The full mock loop
Requires Node 22+. With QUEUE_MODE=memory, dev:ingress also runs the
worker consume loop in-process, giving the complete loop — webhook POST → job
→ review → fake-ADO threads — in one terminal:
npm install && npm run build
export ADO_ORG_URL=https://dev.azure.com/example-org
export ARGUS_IDENTITY_ID=argus-identity-guid
export WEBHOOK_BASIC_USER=argus-hooks WEBHOOK_BASIC_PASS=change-me WEBHOOK_SHARED_SECRET=sekrit
export DATABASE_URL=memory QUEUE_MODE=memory MOCK_ADO=1 MOCK_RUNNER=1
export CHECKOUT_MODE=fixture FIXTURE_CHECKOUT_DIR=examples/demo-workspace
npm run dev:ingress
Then simulate an ADO service-hook delivery:
curl -i -X POST http://127.0.0.1:3000/webhook \
-u argus-hooks:change-me -H 'x-argus-webhook-secret: sekrit' \
-H 'content-type: application/json' \
-d '{"subscriptionId":"sub-1","notificationId":1,
"eventType":"git.pullrequest.created",
"resource":{"pullRequestId":1,
"repository":{"id":"demo-repo","name":"demo-repo",
"project":{"name":"demo-project"}}}}'
You get 202 {"outcome":"queued","jobType":"review.full"}; the inline worker
picks up the job, "reviews" the demo workspace, and posts an inline thread on
the fake PR. Replaying the same delivery is deduplicated; an update event with
an unchanged head is dropped as vote churn.
The values exported above are mock-mode placeholders, not credentials — nothing here talks to a real system.
The four-ring ladder
Confidence builds in four rings, each wider than the last, all offline:
Ring 1 — Unit tests
npm test
330+ vitest tests across every package, fully offline. Tests live in
src/__tests__/ directories (never beside production code), run on in-memory
fakes (pg-mem for Postgres, FakeAdoClient for ADO), and touch no network and
no API keys.
Ring 2 — Evals
npm run evals
The golden-PR harness replays a corpus of curated PRs through the same
worker pipeline as production with the deterministic MockRunner and scores
findings against known expectations, gating on regression against a recorded
baseline. See Evals & quality.
Ring 3 — Full mock loop in a container
npm run docker:up # = docker compose up --build
curl http://127.0.0.1:3000/health
# then the webhook curl above, against the container
The same image that ships to production runs the complete loop with the
mock-mode environment baked into docker-compose.yml — fake ADO, mock runner,
in-memory state and queue, fixture checkout — plus a /health healthcheck.
docker compose up also starts the docs site you are reading on port
4100, served by the same image.
Ring 4 — Swap real pieces one at a time
Every mock is an env switch, so realism arrives incrementally, not as one big bang:
DATABASE_URL=postgres://…— real Postgres (docker compose --profile postgres upprovides a local one), plusnpm run db:migrate.QUEUE_MODE=servicebus+SERVICEBUS_NAMESPACE=…— real Azure Service Bus (queue must have sessions enabled); rundev:workeras a separate process.MOCK_ADO=0— the real ADO REST client (Entra ID primary, PAT fallback) against a test project.MOCK_RUNNER=0— the real Claude Agent SDK runner; needs an inference endpoint (ANTHROPIC_API_KEY, or the Foundry/Bedrock/Vertex switches) and fails jobs cleanly when none is configured.
Each swap changes exactly one variable of the experiment; everything else stays mocked until you choose otherwise.
Working on the docs site
npm run docs:build # render markdown → static HTML in docs-site/dist
npm run docs:serve # what production serves: build + bin/argus-docs.ts on :4100
The configuration reference is generated from
.env.example and the config schemas at build time — edit those sources, not
the generated page.
