JEV GIT WORKFLOW GUIDE

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.

Updated 2026-09-29Includes exercises; work at your own pace
01

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.

02

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 skips oversized patches and checks sensitive paths and common secret patterns before upload. These checks cannot guarantee all credentials are detected; inspect inputs before real use.

scripts/jev-pr-review.mjs
// Node 20+. Report only: never merges, publishes, or authorizes an action.
import { appendFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { pathToFileURL } from 'node:url';

export function parsePaths(output) {
  const fields = output.split('\0');
  if (fields.pop() !== '') throw new Error('Invalid NUL-terminated Git output');
  const paths = [];
  while (fields.length) {
    const status = fields.shift();
    if (!/^[ACDMRTUXB][0-9]*$/.test(status)) throw new Error('Invalid Git status');
    const count = /^[RC]/.test(status) ? 2 : 1;
    for (let i = 0; i < count; i++) {
      const path = fields.shift();
      if (!path) throw new Error('Missing Git path');
      paths.push(path);
    }
  }
  return paths;
}

export function containsSensitiveInput(paths, patch) {
  const sensitivePath = /(^|\/)(?:\.env(?:\.[^/]*)?|\.npmrc|\.pypirc|credentials|id_(?:rsa|ed25519|ecdsa))$|\.(?:pem|key|p12|pfx)$/i;
  // Defense in depth, not a guarantee: review the input before any real upload.
  const sensitiveContent = /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:gh[pousr]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{16,}|sk-[A-Za-z0-9_-]{16,}|AKIA[A-Z0-9]{16})\b|\bBearer\s+\S+|\b(?:[A-Z_]*(?:API_KEY|TOKEN|SECRET|PASSWORD))\s*[:=]\s*\S+/i;
  return paths.some(path => sensitivePath.test(path)) || sensitiveContent.test(patch);
}

export 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 behavior',
      maintenance: 'Refactor, documentation, dependencies or tooling', unknown: 'Insufficient evidence' },
  },
  risk: {
    type: 'score', instructions: 'How risky is this change to merge?',
    criteria: ['Low: isolated and reversible', 'Moderate: bounded shared behavior',
      'High: data, auth, payment or deployment changes', 'Critical: credible irreversible or security impact'],
  },
  needs_human_review: {
    type: 'noul', instructions: 'Does this change require deliberate human review before merge?',
    criteria: { true: 'Ambiguous, high-impact or security-sensitive', false: 'Low-impact and reversible' },
  },
};

const inRange = (value, max) => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= max;
export function validAnswers(answers) {
  const type = answers?.change_type;
  return type?.type === 'choice' && typeof type.choice === 'string'
    && Object.hasOwn(questions.change_type.criteria, type.choice)
    && inRange(type.confidence, 1) && answers?.risk?.type === 'score'
    && inRange(answers.risk.score, 3) && answers?.needs_human_review?.type === 'noul'
    && inRange(answers.needs_human_review.noul, 1);
}

export async function reviewDiff({ names, patch, apiKey, fetchImpl = fetch }) {
  let paths;
  try { paths = parsePaths(names); } catch { return { status: 'invalid_git_input' }; }
  if (containsSensitiveInput(paths, patch)) return { status: 'sensitive_input' };
  if (!patch.trim()) return { status: 'no_changes' };
  if (!apiKey) return { status: 'missing_credentials' };
  // Do not silently judge an incomplete patch or send a huge file list.
  if (patch.length > 14_000 || paths.length > 200) return { status: 'input_too_large' };
  try {
    const response = await fetchImpl('https://api.typesafe.ai/v1/systemone', {
      method: 'POST',
      headers: { authorization: 'Bearer ' + apiKey, 'content-type': 'application/json' },
      body: JSON.stringify({ model: 'jev-latest', state: { changed_files: paths, patch }, questions }),
      signal: AbortSignal.timeout(15_000),
    });
    if (!response.ok) return { status: 'provider_failure' };
    const { answers } = await response.json();
    if (!validAnswers(answers)) return { status: 'invalid_response' };
    return { status: 'report_only', answers };
  } catch { return { status: 'provider_failure' }; }
}

export function formatReport(result) {
  const lines = ['## Jev PR report', '- Status: ' + result.status,
    '- Advisory only. Keep tests and human approval; this does not authorize merging.'];
  if (result.status === 'report_only') {
    const a = result.answers;
    lines.push('- Change type: ' + a.change_type.choice,
      '- Type confidence: ' + a.change_type.confidence.toFixed(2),
      '- Risk score: ' + a.risk.score.toFixed(2) + ' / 3',
      '- P(human review): ' + a.needs_human_review.noul.toFixed(2));
  }
  // No patch, key, exception message, or provider response text in logs.
  return lines.join('\n');
}

export function exitCode(result, mode = 'report') {
  if (mode === 'report') return 0;
  // Conservative gate: any real change or uncertainty requires existing human approval.
  // No model answer grants permission to merge. Configure review in the repository rules.
  if (mode === 'blocking') return result.status === 'no_changes' ? 0 : 1;
  return 1;
}

export async function main(env = process.env) {
  const git = (...args) => execFileSync('git', args, { encoding: 'utf8', maxBuffer: 2_000_000 });
  let result;
  try {
    const base = env.JEV_BASE?.trim();
    if (base && !/^[A-Za-z0-9][A-Za-z0-9/_.-]*$/.test(base)) throw new Error('Invalid ref');
    const range = base ? [base + '...HEAD'] : ['--cached'];
    result = await reviewDiff({
      names: git('diff', '--name-status', '-z', '--no-ext-diff', '--no-textconv', ...range, '--'),
      patch: git('diff', '--unified=1', '--no-ext-diff', '--no-textconv', ...range, '--'),
      apiKey: env.TYPESAFE_API_KEY,
    });
  } catch { result = { status: 'git_input_failure' }; }
  const report = formatReport(result);
  console.log(report);
  if (env.GITHUB_STEP_SUMMARY) appendFileSync(env.GITHUB_STEP_SUMMARY, report + '\n');
  process.exitCode = exitCode(result, env.JEV_MODE || 'report');
  return result;
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) await main();
Download complete script (Node 20+)

Node 20 includes fetch. Save the download as scripts/jev-pr-review.mjs. JEV_MODE=report is the default and records sanitized provider failures. Optional JEV_MODE=blocking exits nonzero for every change or uncertainty, requiring existing repository human-approval procedures; no model score auto-approves a merge. Checked with offline mocks, not a live API.

03

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_type

Can only return feature, fix, maintenance, or unknown.

Scorerisk

Returns a position on the four-level risk scale, including between levels.

Noulneeds_human_review

Returns P(true) for the proposition and has no separate confidence field.

04

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.

ConditionReport-only phaseCalibrated gate
API timeout or invalid responseRecord provider_failure; CI continuesEscalate by team policy; never auto-approve
High risk or review probabilityHighlight in the job summaryRequire the existing human-approval rule
Low riskStill run tests and normal reviewDoes not imply permission to auto-merge
05

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
06

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.

.github/workflows/jev-pr-report.yml
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
        env:
          BASE_BRANCH: "${{ github.base_ref }}"
        run: git fetch origin "$BASE_BRANCH"

      - name: Skip fork report
        if: github.event.pull_request.head.repo.full_name != github.repository
        run: echo "Fork PR — remote analysis skipped."

      - name: Build Jev report
        if: github.event.pull_request.head.repo.full_name == github.repository
        env:
          TYPESAFE_API_KEY: "${{ secrets.TYPESAFE_API_KEY }}"
          JEV_BASE: "origin/${{ github.base_ref }}"
        run: node scripts/jev-pr-review.mjs
07

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.