JEV GIT 工作流指南

Jev Git 工作流教程:提交、PR 与 CI 门禁

在 Git 提交、Pull Request 和 CI 流程中使用 Jev 完成有界语义判断,同时把 push、merge 与分支保护权限留在确定性代码和人工审批中。

更新于 2026-09-2418–25 min
01

1. 先划清 Jev 的职责边界

只把精确 Git 数据无法直接回答的问题交给 Jev,例如变更分类、评审领域、风险等级或是否需要人工复核。文件存在、测试结果、权限、必要评审人和受保护分支规则仍由确定性代码检查。本教程不会让模型执行 push、merge、tag、release 或绕过审批。

02

2. 创建一个零依赖的 PR 判断脚本

示例只使用 Node 20 内置能力和官方 POST /v1/systemone 接口。本地默认读取 staged diff;CI 通过 JEV_BASE 指向 PR 基准分支。脚本限制补丁长度、记录是否截断,并且不会发送仓库凭据。

scripts/jev-pr-review.mjs
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');
}

为什么直接使用 HTTP:它与官方快速开始中的请求格式一致,示例不需要安装或猜测某个社区 SDK 的版本。Node 20 已内置 fetch 和 AbortSignal.timeout。

03

3. 一次请求询问多个封闭问题

脚本对同一 State 同时发送三个独立问题:Choice 判断变更类型,Score 输出有序风险,Noul 判断是否需要人工复核。TypeSafe 官方说明同一请求中的问题彼此独立,因此收到结果后仍要由应用策略组合答案。

Choicechange_type

只能返回 feature、fix、maintenance 或 unknown。

Scorerisk

沿四级风险量表返回可落在等级之间的分数。

Noulneeds_human_review

返回命题为真的概率;它没有单独的 confidence。

04

4. 校验响应后再执行策略

类型化结果仍需要运行时校验:Choice 必须属于原始选项,Score 必须是有限数且位于声明区间,Noul 必须在 0 到 1 之间。校验通过后再映射到清晰可见的策略,不要把权限隐藏在提示词里。

情况只报告阶段校准后的门禁
API 超时或响应无效记录 provider_failure,CI 继续按团队策略转人工,不自动放行
风险高或复核概率高在 Job Summary 中突出显示要求已有的人工审批规则
风险低仍然运行测试和普通评审不代表允许自动合并
05

5. 先在本地以只报告模式运行

先读取暂存区,让开发者在提交前核对模型看到的证据。此阶段只输出报告、不阻止提交,用它暴露超大 diff、含糊标签、敏感路径和问题设计缺陷。

# 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. 接入只读的 GitHub Actions

工作流拉取完整历史、获取 PR 基准分支、运行同一脚本,并把结果写入 Job Summary。权限保持只读,TYPESAFE_API_KEY 存在 Actions Secret。先报告还能积累后续校准所需的数据。

.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
        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
07

7. 用历史 PR 校准后再启用门禁

回放带标注的历史 Pull Request,统计错误放行、无效复核、覆盖率、延迟和 provider 失败。阈值应来自真实错误成本,而不是直觉。后续即便改成阻断,也只应对团队已经验证过的特定高风险条件 fail closed。

  • 至少准备一批人工标注的历史 PR,并保留低、中、高风险样本。
  • 记录问题版本、模型版本、完整分布、阈值、最终人工结论和覆盖原因。
  • 分别统计错误放行和过度拦截;两者成本通常不同。
  • 先运行只报告模式,再要求人工复核;不要从第一天自动合并。
  • 为 API 故障、超大 diff、敏感文件和 State 截断设置明确兜底。