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
50 changes: 50 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Docs
# Builds the documentation site (site/build.sh) and publishes it to GitHub
# Pages from main. Pull requests that touch what the site is made from only
# build it, so a broken page or include fails before it merges.
on:
push:
branches: [main]
pull_request:
paths: ["site/**", "README.md", "CHANGELOG.md", "docs/classification-cascade.md", "jevgate.schema.json", ".github/workflows/docs.yml"]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: true
env:
MDBOOK_VERSION: 0.5.4
MDBOOK_SHA256: 5222beabd3e37dc5be0d18ff99b79058469354db5c220153a1b92db5ba12be89
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust
run: rustup toolchain install stable --profile minimal
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
- name: Install mdBook
run: |
archive="mdbook-v$MDBOOK_VERSION-x86_64-unknown-linux-musl.tar.gz"
curl -fsSL -o "$RUNNER_TEMP/$archive" "https://github.com/rust-lang/mdBook/releases/download/v$MDBOOK_VERSION/$archive"
echo "$MDBOOK_SHA256 $RUNNER_TEMP/$archive" | sha256sum -c -
mkdir -p "$RUNNER_TEMP/bin" && tar -xzf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP/bin"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
- run: site/build.sh
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: site/book
deploy:
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ GEMINI.md
*.log
/video/
/roadmap.md

# The documentation site is built by site/build.sh; its reference pages are generated.
/site/book/
/site/src/reference/
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ JevGate asks the model small questions and decides findings in code. When you ch

[docs/classification-cascade.md](docs/classification-cascade.md) describes the evidence units and composition rules.

## Documentation site

The site at <https://tech-byte-frontier.github.io/jevgate/> is built from `site/` with [mdBook](https://rust-lang.github.io/mdBook/): `site/build.sh` generates the rules, configuration and command-line reference pages from a release build, then builds the book into `site/book`. Guide pages are in `site/src`; the reference pages are generated, so change the rule catalog, the configuration types or the `--help` text instead.

## Pull requests

- Keep a pull request to one change, with tests.
Expand Down
19 changes: 19 additions & 0 deletions site/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[book]
title = "JevGate"
description = "A code-review gate for CI and coding agents"
authors = ["Tech Byte Frontier"]
language = "en"
src = "src"

[build]
build-dir = "book"
create-missing = false

[output.html]
site-url = "/jevgate/"
git-repository-url = "https://github.com/Tech-Byte-Frontier/jevgate"
edit-url-template = "https://github.com/Tech-Byte-Frontier/jevgate/edit/main/site/{path}"
no-section-label = true

[output.html.search]
limit-results = 20
9 changes: 9 additions & 0 deletions site/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Build the documentation site into site/book: the reference pages are
# generated from a release build of JevGate, so they match its --help,
# `jevgate rules` and jevgate.schema.json. Needs mdbook on PATH.
set -euo pipefail
cd "$(dirname "$0")/.."
cargo build --release --locked --quiet
python3 site/generate.py target/release/jevgate jevgate.schema.json site/src/reference
mdbook build site
154 changes: 154 additions & 0 deletions site/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""Write the site's reference pages from JevGate itself.

generate.py JEVGATE SCHEMA OUT_DIR

rules.md comes from `jevgate rules --format json`, configuration.md from
jevgate.schema.json, and cli.md from each command's --help, so the pages
always describe the binary they were built with.
"""
import json
import subprocess
import sys
from pathlib import Path

COMMANDS = ["auth", "check", "baseline", "rules", "init", "completions", "man", "serve"]
GROUPS = {
"maintainability": "On by default.",
"tests": "On by default; judged with `--include-tests` or `include_tests = true`.",
"security": "Opt-in: `--rule security`, or a level in `[rules]`.",
"documentation": "Opt-in: `--rule documentation`, or a level in `[rules]`.",
}


def run(binary, *args):
return subprocess.run([binary, *args], check=True, capture_output=True, text=True).stdout


def rules_page(binary):
rules = json.loads(run(binary, "rules", "--format", "json"))
version = run(binary, "--version").strip()
lines = [
"# Rules reference",
"",
f"Generated from `jevgate rules --format json` ({version}). A rule is named by its ID,",
"its key or its group anywhere a rule is accepted: `--rule`, `--skip-rule`,",
"`--fail-on TARGET=LEVEL`, `[rules]` and `[[scope]]`.",
"",
"| Rule | Key | Default | Question |",
"|---|---|---|---|",
]
for rule in rules:
anchor = rule["id"].replace("/", "-")
default = "yes" if rule["default_enabled"] else "opt-in"
lines.append(
f"| [`{rule['id']}`](#{anchor}) | `{rule['key']}` | {default} | {cell(rule['inspection'])} |"
)
group = None
for rule in rules:
if rule["group"] != group:
group = rule["group"]
lines += ["", f"## {group.capitalize()}", "", GROUPS.get(group, "")]
anchor = rule["id"].replace("/", "-")
lines += [
"",
f'<a id="{anchor}"></a>',
f"### `{rule['id']}`",
"",
f"**Question:** {rule['inspection']}",
"",
f"- **Key:** `{rule['key']}` · **Version:** {rule['version']}"
+ (" · **Needs tests:** yes" if rule["requires_tests"] else ""),
f"- **Looks at:** {rule['scope']}",
f"- **Evidence unit:** {rule['unit']}",
f"- **Acceptable:** {rule['acceptable_example']}",
]
policy = rules[0]["decision_policy"]
lines += [
"",
"## Decision policy",
"",
"Answers become findings in code, at the same thresholds for every rule:",
"",
"| Setting | Value |",
"|---|---|",
]
lines += [f"| `{name}` | {value:g} |" for name, value in sorted(policy.items())]
return "\n".join(lines) + "\n"

Check warning on line 77 in site/generate.py

View workflow job for this annotation

GitHub Actions / review

JevGate consider [security/injection]

`rules_page` places its parameters into markup without binding, escaping or checking them; a caller passing outside input would make it exploitable (0.91). → Escape the value or render it as text


def configuration_page(schema_path):
schema = json.loads(Path(schema_path).read_text())
lines = [
"# Configuration reference",
"",
"Generated from [`jevgate.schema.json`](https://github.com/Tech-Byte-Frontier/jevgate/blob/main/jevgate.schema.json),",
"which is generated from the configuration types. [Configuration](../configuration.md) explains",
"how the keys work together.",
"",
"| Key | Type | Meaning |",
"|---|---|---|",
]
for name, spec in sorted(schema["properties"].items()):
lines.append(f"| `{name}` | {kind(spec, schema)} | {cell(spec.get('description', ''))} |")
scope = schema["$defs"]["Scope"]
lines += ["", "## `[[scope]]`", "", cell(scope.get("description", "")), "", "| Key | Type | Meaning |", "|---|---|---|"]
for name, spec in sorted(scope["properties"].items()):
lines.append(f"| `{name}` | {kind(spec, schema)} | {cell(spec.get('description', ''))} |")
levels = schema["$defs"]["Level"]["anyOf"][0]["enum"]
names = schema["$defs"]["Scope"]["properties"]["rules"]["propertyNames"]["enum"]
lines += [
"",
"## Levels",
"",
", ".join(f"`{level}`" for level in levels) + ". `off` is accepted in `[rules]` only.",
"",
"## Rule names",
"",
", ".join(f"`{name}`" for name in names) + ".",
]
return "\n".join(lines) + "\n"


def kind(spec, schema):
if "$ref" in spec:
return "rules list or table"
if spec.get("type") == "array":
items = spec.get("items", {})
return "list of tables" if "$ref" in items else "list of " + items.get("type", "value") + "s"
return spec.get("type", "value")


def cli_page(binary):
lines = [
"# Command-line reference",
"",
f"Generated from `--help` ({run(binary, '--version').strip()}). "
"`jevgate man COMMAND` prints the same text as a man page.",
"",
"## `jevgate`",
"",
"```text",
run(binary, "--help").rstrip(),
"```",
]
for command in COMMANDS:
lines += ["", f"## `jevgate {command}`", "", "```text", run(binary, command, "--help").rstrip(), "```"]
return "\n".join(lines) + "\n"


def cell(text):
return text.replace("|", "\\|").replace("\n", " ")


def main():
binary, schema, out = sys.argv[1:4]
out = Path(out)
out.mkdir(parents=True, exist_ok=True)
(out / "rules.md").write_text(rules_page(binary))
(out / "configuration.md").write_text(configuration_page(schema))
(out / "cli.md").write_text(cli_page(binary))


if __name__ == "__main__":
main()
31 changes: 31 additions & 0 deletions site/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Summary

[JevGate](introduction.md)

# Getting started

- [Install](install.md)
- [Quick start](quick-start.md)
- [What it finds](what-it-finds.md)
- [Supported languages and frameworks](languages.md)

# Using JevGate

- [Continuous integration](ci.md)
- [Coding agents](coding-agents.md)
- [Configuration](configuration.md)
- [Output and exit codes](output.md)
- [Privacy and cost](privacy-and-cost.md)
- [Troubleshooting](troubleshooting.md)

# Background

- [How it works](how-it-works.md)
- [Limits](limits.md)
- [Changelog](changelog.md)

# Reference

- [Rules](reference/rules.md)
- [Configuration keys](reference/configuration.md)
- [Command line](reference/cli.md)
3 changes: 3 additions & 0 deletions site/src/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Changelog

{{#include ../../CHANGELOG.md:3:}}
52 changes: 52 additions & 0 deletions site/src/ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Continuous integration

A pull request review on GitHub Actions, with the [JevGate action](https://github.com/Tech-Byte-Frontier/jevgate-action):

```yaml
name: JevGate
on: pull_request
permissions:
contents: read
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # --base compares with the fork point
- uses: Tech-Byte-Frontier/jevgate-action@v1
with:
api-key: ${{ secrets.TYPESAFE_API_KEY }}
version: 0.17.0
```

The action installs a checked release binary, keeps `.jevgate/cache` in the Actions cache and runs `jevgate check --base <pull request base> --format github`; `args` passes more flags, such as `--rule security`. It runs on Linux, macOS and Windows runners.

`--format github` annotates the changed lines with each finding. A finding that fails the gate is an error; the others are warnings. A Markdown table goes to the job summary, and the usual text goes to the log. The full JSON report is always at `.jevgate/latest.json` if you want to keep it as an artifact.

- **Changed files only:** `--base` reviews what changed since the fork point with that revision, the same files a pull request diff shows, plus uncommitted and untracked files. It needs the history, so check out with `fetch-depth: 0`. When no supported file changed, the run passes without any request.
- **Cache:** answers are stored under a hash of the exact request: source, questions and model. Restoring an older cache is always safe, and unchanged code costs nothing on the next run.
- **Advisory or blocking:** `fail_on = ["none"]` in `jevgate.toml` or `--fail-on none` reports findings without failing. A run that could not finish (missing key, provider rejection, request budget reached) still exits 2, so an outage never passes as a clean review.
- **A policy the change cannot edit:** a pull request can edit `jevgate.toml`. To apply the reviewed policy of the base branch instead, read it with `--config`:

```sh
git show "$BASE_SHA:jevgate.toml" > "$RUNNER_TEMP/jevgate.toml"
jevgate check --config "$RUNNER_TEMP/jevgate.toml" --base "$BASE_SHA" --format github
```

- **Forks:** GitHub withholds secrets from pull requests opened from forks, so there the run exits 2 with "No API key configured". Skip the job for forks, or run it only on branches of the repository.
- **Budgets:** `max_requests` caps the API attempts of one run. Reaching it leaves the run incomplete instead of passing on partial evidence. `--dry-run` counts the planned requests the cache already answers, so its estimate covers only what the cache lacks; follow-ups depend on answers and are not counted.
- **Transient failures:** rate limits, overload and server or edge errors (HTTP 408, 429, 500, 502–504, 520–524, 529) are retried up to four attempts; a timeout or dropped connection is retried once, since the first send may have run.
- **Report-only paths:** give tooling its own level with `[[scope]]` (below), so scripts are reported while product code gates.

Before each commit, with [pre-commit](https://pre-commit.com), review what is staged:

```yaml
repos:
- repo: https://github.com/Tech-Byte-Frontier/jevgate
rev: v0.18.0
hooks:
- id: jevgate-system # the jevgate on PATH; `jevgate` builds it with Rust instead
```

Other CI systems work the same way: install with `install.sh` or `cargo binstall`, set `TYPESAFE_API_KEY`, keep `.jevgate/cache` between runs, and read the exit code or the JSON report.
41 changes: 41 additions & 0 deletions site/src/coding-agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Coding agents

JevGate's default output is written for coding agents as much as for people: ranked findings, each with a location, a probability and a next step, and nothing hidden when an answer stays undecided.

## Check before finishing

Ask the agent to review its own change before it reports back, for example in `AGENTS.md` or `CLAUDE.md`:

```markdown
Before finishing, run `jevgate check --base origin/main`. Fix each `review` finding;
for a `consider`, fix it or say why the code should stay as it is.
```

`--base` limits the review to the files changed since that revision, plus uncommitted and untracked files, so a check costs only what the change touches, and cached answers make reruns free. The exit code says what to do next:

| Exit code | Meaning for the agent |
|---|---|
| 0 | The gate passed; `consider` findings may still be worth a look |
| 1 | The gate failed: act on the findings listed |
| 2 | The run could not finish (no key, provider rejection, request budget); report it, don't treat it as a pass |

## Structured output

`--format json` prints the full report: every file, finding, raw answer and probability, and the gate. The same report is always written to `.jevgate/latest.json`, whatever the output format, so an agent can run the check once and read the details after. `jevgate check --help` explains its fields.

`jevgate rules --format json` lists every rule with the question it asks, so an agent can tell what a finding means without guessing.

## Watching while editing

`jevgate check --watch` re-checks the selected files after each save and prints one JSON report per line. Alongside it, `jevgate serve` answers local tools, never browser pages, with read-only JSON:

| Path | What it returns |
|---|---|
| `/snapshot` | The full latest report |
| `/evidence` | Findings and context per file |
| `/context-requests` | Evidence a file still needs |
| `/changes?since=GENERATION` | What changed since a report generation |

## Documentation for agents

The opt-in documentation rules judge the instruction files agents load at the start of every session (`AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, and Cursor, Copilot, Windsurf, Cline, Kiro, Junie and Roo Code rules): sections that only restate the manifest or generic advice, and text loaded in every session that applies to one directory. `jevgate check --rule documentation` also estimates the tokens each harness loads.
Loading
Loading