reqfile

REQFILE DOCS

Checks

A check verifies a requirement: a command when the rule is mechanical, a decision when it needs judgment. A check that cannot run is never reported as passing.

Command checks

A command check runs an existing tool: a linter, a test runner, a script.

FieldRequiredMeaning
runyesShell command, run from the folder of its Reqfile
fix_hintyesOne line telling how to fix a violation
filesnoGlob relative to that folder; the command runs only if a target file matches
pass_filesnoAppend the matching files as arguments (default false)
formatnoexit (default), sarif or junit
violation_codesnoExit codes meaning “violations found” (default [1]; [101] for cargo test)
timeoutnoSeconds, default 60
fastnoQuick enough for reqfile check --fast, such as in an edit hook (default false)
thresholdsno{ violation_above, pass_below } for SARIF results carrying a probability (default 0.8 and 0.2)
modenoblocking (default), or advisory: violations are reported without failing the run, and a failure to run is an advisory error

A command without files always runs. With pass_files, the files are appended at the end of the whole command, so a pipeline such as tool "$@" | filter receives nothing; put pipelines in a script.

Commands reach the files of their requirement through $REQFILE_ASSETS, the absolute path of its .reqfile/<ID>/ folder (or the declaring adapter directory for an adapter leaf), as in run: $REQFILE_ASSETS/check.sh. $REQFILE is the reqfile executable running the check, for commands that inspect the requirements themselves, such as $REQFILE list --format json.

Composable adapters

A requirement owns one invariant. Language adapters own language tooling; library adapters own compatibility and relevant scenarios; project bindings supply real operations and assertions. The four roles compose without duplicating a requirement for every stack. Commands and decisions remain the execution leaves.

Reqfile.yaml check
- adapter:
    use: ./adapters/typescript
    check: replay-safe
    with: { binding: .reqfile/REPLAY_SAFE/binding.json }

The selected directory contains Adapter.yaml: adapter: 1, kind: language|library|project, optional inputs: {required: [...], optional: [...]}, and checks, a mapping from export names to check lists. An export may compose other adapters. Paths resolve from the declaring manifest; a pinned Git use may add path to select its directory. Cycles, unknown inputs or exports, missing required inputs and unsupported versions are errors.

Each leaf receives its own asset directory in REQFILE_ASSETS and structured JSON inputs in REQFILE_BINDING. Values are never interpolated into command text. reqfile explain retains check provenance. Changing a transitive adapter or the conventional project .reqfile/<ID>/binding.json invalidates changed selection.

Reliability adapters execute named scenarios through project bindings. They reject missing, skipped, duplicate or malformed evidence. Passing says those scenarios succeeded, not that their assertions or production fidelity were proved. Read the adapter contracts and runnable examples before adoption.

Output formats

format: exit: exit 0 passes, and a code listed in violation_codes reports one violation carrying the last 50 lines of output.

format: sarif: the report is parsed on both exit 0 and a listed violation code. Each kind: fail result is a violation; an absent kind defaults to fail. Other kinds and suppressed results are ignored. A suppression with no status or status: accepted suppresses the result; rejected and underReview do not. This catches findings from pipelines such as clippy | clippy-sarif even when the final command exits 0.

format: junit: the command prints a JUnit XML report, as cargo-nextest, pytest, vitest, bun and gotestsum write. Each failed or errored test case is a violation named after the test, located by its file and line attributes when the runner gives them. A report with no test case, or a violation code with no failed test, is a tool error.

Invalid SARIF (including on exit 0), a violation code with no SARIF results, any unexpected exit code, a signal or a timeout is a tool error. A report containing only ignored results passes on exit 0 or a listed violation code.

Probabilities from commands

A SARIF result may carry properties.probability, the probability that it is a violation, so heuristics and models speak the same protocol as linters. Such a result is judged by it, whatever its kind: above violation_above a violation, below pass_below nothing, and in between an uncertain finding, always advisory. A probability outside 0 to 1 is a tool error. A checker can report every file it judged, with low probabilities for the fine ones.

A checker is any program. When a requirement is shared, its command fetches the checker with its ecosystem's runner at an exact version, such as bunx @scope/reqfile-colocation@0.1.3, uvx tool==1.2.0 or go run example.com/tool@v1.2.0, so nothing needs installing and reqfile manages no packages.

Decision checks

A decision check (or decision: { mode: blocking }) asks Jev a question about each unit of code it selects. It reads $REQFILE_ASSETS/decision.yaml, the .reqfile/<ID>/decision.yaml of its requirement:

.reqfile/FAIL_FAST/decision.yaml
units:                      # ast-grep rules selecting the code to ask about
  - { language: typescript, rule: { kind: catch_clause } }
  - { language: tsx, rule: { kind: catch_clause } }
  - { language: python, rule: { kind: except_clause } }
  - { language: rust, rule: { pattern: $X.unwrap_or_default() } }
context: enclosing          # optional: also send the enclosing function
question: Does this error handler hide the failure?
violation_when: It discards the error, only logs it, or replaces it with a default value, and execution continues as if nothing failed.
ok_when: It rethrows, returns an explicit error, or handles a specific expected error in a way the caller can observe.
fix_hint: Rethrow, return an explicit error, or narrow the handler to the expected error and handle it visibly.
thresholds: { violation_above: 0.8, pass_below: 0.2 }

Units accept ast-grep's rule, constraints and utils, plus files and ignores globs (relative to the Reqfile's folder). A unit rule selects files of exactly its language: a typescript rule never selects .tsx files, so TSX needs its own tsx rules. For each unit, reqfile asks Jev the question with the language, path, unit source and, with context: enclosing, the enclosing function.

A probability above violation_above is a violation, below pass_below a pass, and anything in between an uncertain finding. Give each check one precise question about the must; when a principle needs several questions, write several checks, each with its own examples.

Advisory or blocking

Decision checks are advisory unless mode: blocking; uncertain findings are always advisory. A blocking decision check needs a pinned model (an exact version, such as typesafe/jev-1.13-20260917, set in .reqfile/config.yaml), so its thresholds stay tied to the version they were measured with. Label a few dozen units of your codebase and measure the check before making it blocking.

Command checks are blocking by default; mode: advisory reports their violations without failing the run.

Cost and cache

Each unit is sent once, with the questions of every decision check that selects it: Jev charges input tokens and reads the state once per request, so several checks on the same code cost little more than one. Answers are cached in a local file in the git folder (.git/reqfile/jev-cache), shared by every worktree, so checking never modifies tracked files. An answer is reused while the unit, the requirement's question and the exact model behind latest are unchanged, and a full run drops answers no unit uses any more. In CI, keep that file between runs.

The Jev key

Decision checks call Jev, TypeSafe's decision model, with an API key read from the environment. Mechanical checks need no key at all.

VariableWhere to get itEndpoint and model used
OPENROUTER_API_KEYopenrouter.ai/settings/keyshttps://openrouter.ai/api/v1, ~typesafe/jev-latest
TYPESAFE_API_KEYconsole.typesafe.ai/keyshttps://api.typesafe.ai/v1, jev-latest

Set either one: with both set, OpenRouter is used. A .reqfile/config.yaml that sets decision.endpoint or decision.api_key_env picks the provider itself.

TypeSafe charges $42 per billion input tokens and nothing for output: a million tokens of code, about a thousand functions of typical size, costs around 4 cents. Answers are cached, so later runs only pay for the code that changed. OpenRouter sets its own price for Jev.

reqfile reads keys from the environment only: fill it with the secret manager you use. When the key is missing, the run says so first, naming the variable and the requirements whose decision checks will not judge code. An advisory decision check that cannot run, such as in a pull request from a fork without secrets, is an advisory error that leaves the exit code unchanged; a blocking one exits 3.

The selected source and its context are sent to the configured provider: use decision checks only where that is acceptable for the repository.

Contradictions between requirements

Requirements will contradict each other. For each violation and advisory finding, reqfile asks Jev whether making the fix it asks for would break another requirement that applies to the same file. Each one it would break above 0.7 is listed under the finding (breaks: CORE (p=0.90)), and the summary groups them by pair of requirements as decisions to make:

terminal
decide  COLOCATION vs FUNCTIONAL_CORE  p=0.85  fixing COLOCATION in 4 files would break FUNCTIONAL_CORE
    src/core/cache.rs, …
    Reword one of the two requirements, or say which one wins where they meet.

A contradiction is a product decision, not a fix: an agent would undo one fix with the next. JSON marks each finding's conflicts so hooks can send them to a human. Without a Jev key the summary says conflicts were not checked, and failing to ask never changes the exit code.

Something missing or wrong? Open an issue ↗ · Agents: read the skill.