Jev for Git workflows: commits, pull requests, and CI gates
Use Jev for bounded semantic decisions around Git commits, pull requests, and CI without giving a probabilistic model permission to push, merge, or bypass branch protection.
1. Decide what Jev owns—and what it never owns
Use Jev where exact Git data is insufficient: classify a change, select a review area, estimate a risk band, or decide whether a human review is required. File existence, tests, permissions, required reviewers, and protected-branch policy remain deterministic checks. This tutorial never lets the model push, merge, tag, release, or bypass an approval.
2. Create a dependency-free PR decision script
The example uses Node 20 built-ins and the official POST /v1/systemone endpoint. Locally it reads the staged diff; in CI, JEV_BASE points it at the pull request base branch. It limits the patch, records whether truncation happened, and never sends repository credentials.
import { appendFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
const apiKey = process.env.TYPESAFE_API_KEY;
if (!apiKey) throw new Error('TYPESAFE_API_KEY is required');
const base = process.env.JEV_BASE?.trim();
const range = base ? [base + '...HEAD'] : ['--cached'];
const git = (...args) => execFileSync('git', args, {
encoding: 'utf8', maxBuffer: 2_000_000,
});
const names = git('diff', '--name-status', ...range);
const rawDiff = git('diff', '--unified=1', '--no-ext-diff', ...range);
if (!rawDiff.trim()) {
console.log('No changes to review.');
process.exit(0);
}
const sensitivePath = /(^|\t)(\.env($|\.)|.*\.(pem|key|p12)$)/m;
if (sensitivePath.test(names)) {
throw new Error('Refusing to send a diff containing a sensitive path');
}
const MAX_DIFF = 14_000;
const state = {
comparison: base ? base + '...HEAD' : 'staged changes',
changed_files: names.trim().split('\n').slice(0, 200),
patch_excerpt: rawDiff.slice(0, MAX_DIFF),
patch_truncated: rawDiff.length > MAX_DIFF,
};
const questions = {
change_type: {
type: 'choice',
instructions: 'What is the primary purpose of this Git change?',
criteria: {
feature: 'Adds user-visible behavior',
fix: 'Corrects broken or incorrect behavior',
maintenance: 'Refactor, dependency, documentation, or tooling',
unknown: 'Evidence is insufficient or does not fit another option',
},
},
risk: {
type: 'score',
instructions: 'How risky is this change to merge?',
criteria: [
'Low: isolated and easy to reverse',
'Moderate: touches shared behavior but has bounded impact',
'High: affects data, auth, payments, deployment, or broad behavior',
'Critical: credible irreversible or security-sensitive impact',
],
},
needs_human_review: {
type: 'noul',
instructions: 'Does this change require deliberate human review before merge?',
criteria: {
true: 'Ambiguous, high-impact, security-sensitive, or difficult to reverse',
false: 'Low-impact, well-bounded, and straightforward to reverse',
},
},
};
const response = await fetch('https://api.typesafe.ai/v1/systemone', {
method: 'POST',
headers: {
authorization: 'Bearer ' + apiKey,
'content-type': 'application/json',
},
body: JSON.stringify({ model: 'jev-latest', state, questions }),
signal: AbortSignal.timeout(15_000),
});
if (!response.ok) {
throw new Error('Jev request failed: ' + response.status);
}
const result = await response.json();
const { change_type: type, risk, needs_human_review: review } = result.answers ?? {};
const allowedTypes = new Set(Object.keys(questions.change_type.criteria));
if (!type || !allowedTypes.has(type.choice)) throw new Error('Invalid Choice answer');
if (!risk || !Number.isFinite(risk.score) || risk.score < 0 || risk.score > 3) {
throw new Error('Invalid Score answer');
}
if (!review || !Number.isFinite(review.noul) || review.noul < 0 || review.noul > 1) {
throw new Error('Invalid Noul answer');
}
// Example thresholds only. Calibrate them against your repository history.
const outcome = review.noul >= 0.65 || risk.score >= 2
? 'HUMAN REVIEW RECOMMENDED'
: 'REPORT ONLY';
const report = [
'## Jev PR decision report',
'- Outcome: **' + outcome + '**',
'- Change type: **' + type.choice + '**',
'- Type confidence: **' + type.confidence.toFixed(2) + '**',
'- Risk score: **' + risk.score.toFixed(2) + ' / 3**',
'- P(human review): **' + review.noul.toFixed(2) + '**',
'- Diff truncated: **' + state.patch_truncated + '**',
].join('\n');
console.log(report);
if (process.env.GITHUB_STEP_SUMMARY) {
appendFileSync(process.env.GITHUB_STEP_SUMMARY, report + '\n');
} Why direct HTTP: it follows the request shape in the official quickstart and avoids pinning this tutorial to a community wrapper. Node 20 includes fetch and AbortSignal.timeout.
3. Ask several bounded questions in one request
The script sends one state and three independent questions: Choice for change type, Score for ordered risk, and Noul for whether human review is required. TypeSafe documents that questions against the same state are evaluated independently, so application policy must combine the answers after the response arrives.
Choicechange_typeCan only return feature, fix, maintenance, or unknown.
ScoreriskReturns a position on the four-level risk scale, including between levels.
Noulneeds_human_reviewReturns P(true) for the proposition and has no separate confidence field.
4. Validate the response before applying policy
A typed response still needs runtime validation. Check that Choice returned an allowed key, Score is finite and inside the declared range, and Noul is between zero and one. Then map the validated values to a visible policy; do not hide authority inside prompt wording.
| Condition | Report-only phase | Calibrated gate |
|---|---|---|
| API timeout or invalid response | Record provider_failure; CI continues | Escalate by team policy; never auto-approve |
| High risk or review probability | Highlight in the job summary | Require the existing human-approval rule |
| Low risk | Still run tests and normal review | Does not imply permission to auto-merge |
5. Run locally in report-only mode
Start with staged changes so a developer can inspect the exact evidence before committing. Report the result without blocking. This reveals oversized diffs, ambiguous labels, sensitive paths, and question-design problems before the workflow affects anyone else.
# Review exactly what is staged for the next commit.
export TYPESAFE_API_KEY='your-server-side-key'
node scripts/jev-pr-review.mjs
# Inspect the same range a pull request would use.
git fetch origin main
JEV_BASE=origin/main node scripts/jev-pr-review.mjs 6. Add a GitHub Actions reporter
The workflow checks out full history, fetches the pull request base, runs the same script, and appends the result to the job summary. Keep permissions read-only and store TYPESAFE_API_KEY as an Actions secret. Reporting first also gives you data for later calibration.
name: Jev PR report
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Fetch base branch
run: git fetch origin "${{ github.base_ref }}"
- name: Build Jev report
env:
TYPESAFE_API_KEY: "${{ secrets.TYPESAFE_API_KEY }}"
JEV_BASE: "origin/${{ github.base_ref }}"
run: node scripts/jev-pr-review.mjs 7. Calibrate before enabling a gate
Replay labeled historical pull requests and measure false approvals, unnecessary reviews, coverage, latency, and provider failures. Choose thresholds from observed error cost, not intuition. If you later make the job blocking, fail closed only for the specific high-risk condition your team has validated.
- Prepare a human-labeled historical PR set with low-, medium-, and high-risk examples.
- Log question version, model version, full distributions, thresholds, final human outcome, and overrides.
- Measure false approvals and unnecessary blocks separately; their costs are rarely equal.
- Move from report-only to required human review; do not begin with auto-merge.
- Define fallbacks for provider failure, oversized diffs, sensitive paths, and truncated state.