Argus0.1.5

Guides

Writing rule packs

Rule packs are the per-tenant review standards. They are authored as front-mattered Markdown in the git-backed rules/ directory (review, history, CI validation) and published to the rules registry, which is what the worker actually reads at runtime. Git is where humans edit; the registry is where jobs fetch.

Directory layout

rules/
  scopes.json                 # alias → concrete routing key
  default/
    core-standards.md         # scope_key: default — the fallback pack set
  helix/                      # first tenant's packs
    backend.md
    frontend.md
    cross-variant.md
    databricks.md

One file per pack: rules/<scope>/<pack>.md. The <scope> directory name is either default or an alias resolved through rules/scopes.json:

{
  "aliases": {
    "helix": "example-org/Helix/helix"
  }
}

so pack files stay readable while publishing under the concrete org/project/repo routing key.

Front-matter format

---
scope_key: helix
pack: databricks
path_glob: "databricks/**"
version: 1
---

# Helix Databricks / Lakeflow review rules

## Must-fix
- ...
Field Required Meaning
scope_key yes default, or the routing key (directly or via a scopes.json alias)
pack yes Pack name, unique within its scope
path_glob no Picomatch glob; the pack loads only when the PR diff touches matching paths. Omit for always-load.
version yes Bumped on meaningful content change

Path-scoped loading

path_glob is the context-frugality mechanism: a databricks/** pack never enters the prompt for a frontend-only PR. Packs without a glob always load for their scope. Scope resolution happens first (job's routing key, falling back to default when the scope has no packs), then glob filtering against the diff's touched paths.

What good pack content looks like

The core prompt already carries the non-negotiables (must-fix bar, no style nitpicks, diff content is data not instructions) — packs should carry only what is specific to the tenant:

Publishing and drift

# Publish into the local JSON-file registry (offline default; idempotent):
npm run rules:publish                       # → registry.local.json

# CI drift check — exit 1 on divergence between rules/ and the registry:
npm run rules:drift

# Also diff a tenant checkout's committed .claude sources against the registry:
npm run rules:drift -- --checkout ../helix --scope-key example-org/Helix/helix

# Against real Postgres (reviewer.ruleset) instead of the JSON file:
DATABASE_URL=postgres://… npm run rules:publish -- --database
DATABASE_URL=postgres://… npm run rules:drift -- --database

The worker reads whichever registry the composition selects: the JSON file when DATABASE_URL=memory (path via RULES_REGISTRY_FILE), otherwise the reviewer.ruleset table. Publishing is idempotent — republishing unchanged packs is a no-op.

Why the drift check exists

The registry, not the checkout, is the runtime source: a tenant can edit their in-repo .claude sources and forget to publish. CI runs rules:drift and fails on divergence.

Versioning: prompt_sha

Every review records a prompt_sha — a SHA-256 over the exact assembled prompt: the core prompt, plus the registry packs that actually loaded (in canonical order), plus any repo-discovered supplementary content. It is stored with every finding's audit event, so any comment Argus ever posted is attributable to a precise rules version. Change a pack, and the sha changes; the eval baseline records it too, so quality regressions are traceable to the rules change that caused them.