jevtriage 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jevtriage-0.1.0/.env.example +12 -0
- jevtriage-0.1.0/LICENSE +21 -0
- jevtriage-0.1.0/MANIFEST.in +9 -0
- jevtriage-0.1.0/PKG-INFO +204 -0
- jevtriage-0.1.0/README.md +181 -0
- jevtriage-0.1.0/action.yml +148 -0
- jevtriage-0.1.0/fixtures/pr-triage.contract.json +95 -0
- jevtriage-0.1.0/fixtures/replay-ready.json +50 -0
- jevtriage-0.1.0/fixtures/replay-single-ready.json +16 -0
- jevtriage-0.1.0/pyproject.toml +45 -0
- jevtriage-0.1.0/setup.cfg +4 -0
- jevtriage-0.1.0/src/jevtriage/__init__.py +18 -0
- jevtriage-0.1.0/src/jevtriage/__main__.py +4 -0
- jevtriage-0.1.0/src/jevtriage/answers.py +174 -0
- jevtriage-0.1.0/src/jevtriage/cli.py +236 -0
- jevtriage-0.1.0/src/jevtriage/client.py +103 -0
- jevtriage-0.1.0/src/jevtriage/gate.py +32 -0
- jevtriage-0.1.0/src/jevtriage/github.py +192 -0
- jevtriage-0.1.0/src/jevtriage/pinning.py +69 -0
- jevtriage-0.1.0/src/jevtriage/py.typed +0 -0
- jevtriage-0.1.0/src/jevtriage/questions.py +54 -0
- jevtriage-0.1.0/src/jevtriage/state.py +94 -0
- jevtriage-0.1.0/src/jevtriage.egg-info/PKG-INFO +204 -0
- jevtriage-0.1.0/src/jevtriage.egg-info/SOURCES.txt +35 -0
- jevtriage-0.1.0/src/jevtriage.egg-info/dependency_links.txt +1 -0
- jevtriage-0.1.0/src/jevtriage.egg-info/entry_points.txt +2 -0
- jevtriage-0.1.0/src/jevtriage.egg-info/requires.txt +4 -0
- jevtriage-0.1.0/src/jevtriage.egg-info/top_level.txt +1 -0
- jevtriage-0.1.0/tests/test_answers.py +65 -0
- jevtriage-0.1.0/tests/test_cli.py +162 -0
- jevtriage-0.1.0/tests/test_client.py +108 -0
- jevtriage-0.1.0/tests/test_gate.py +26 -0
- jevtriage-0.1.0/tests/test_github.py +100 -0
- jevtriage-0.1.0/tests/test_golden_fixture.py +39 -0
- jevtriage-0.1.0/tests/test_pinning.py +53 -0
- jevtriage-0.1.0/tests/test_questions.py +21 -0
- jevtriage-0.1.0/tests/test_state.py +61 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Required for live TypeSafe Jev calls. Never commit a real key.
|
|
2
|
+
TYPESAFE_API_KEY=
|
|
3
|
+
|
|
4
|
+
# Production pin. Rejected if empty or a floating alias (latest / preview).
|
|
5
|
+
# A documented catalog pin as of 2026-09-19 is jev-1.13.0.
|
|
6
|
+
TYPESAFE_DEFAULT_MODEL=jev-1.13.0
|
|
7
|
+
|
|
8
|
+
# Optional API root override (typesafe-sdk).
|
|
9
|
+
# TYPESAFE_BASE_URL=https://api.typesafe.ai
|
|
10
|
+
|
|
11
|
+
# Optional. Used to fetch the PR and apply jev:* labels.
|
|
12
|
+
# GITHUB_TOKEN=
|
jevtriage-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nithilan Kumaran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT WITHOUT LIMITATION THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
jevtriage-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jevtriage
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Triage GitHub PRs with TypeSafe Jev — ready / needs_review / risky, with a confidence gate
|
|
5
|
+
Author: Nithilan Kumaran
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sathariels/jevtriage
|
|
8
|
+
Project-URL: Documentation, https://github.com/sathariels/jevtriage
|
|
9
|
+
Keywords: jev,typesafe,github-action,pr,triage
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: typesafe-sdk>=0.7.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# jevtriage
|
|
25
|
+
|
|
26
|
+
**Triage gate for pull requests.** A GitHub Action and small Python CLI that asks [TypeSafe Jev](https://typesafe.ai) (System One) one `Choice` question:
|
|
27
|
+
|
|
28
|
+
| Verdict | Meaning |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `ready` | Looks safe to merge with normal review depth |
|
|
31
|
+
| `needs_review` | Needs human eyes (unclear risk, large or ambiguous change) |
|
|
32
|
+
| `risky` | Elevated risk (security-sensitive, destructive, or high blast radius) |
|
|
33
|
+
|
|
34
|
+
The official `ChoiceAnswer.confidence` is a gate: **`ready` only exits 0 when confidence ≥ threshold** (default `0.8`). Low confidence fails closed to `needs_review` (exit 1).
|
|
35
|
+
|
|
36
|
+
v0.1 is a **drop-in Action**. Copy one workflow YAML, set `TYPESAFE_API_KEY`, pin a model, get a verdict on every PR.
|
|
37
|
+
|
|
38
|
+
## Not these nearby tools
|
|
39
|
+
|
|
40
|
+
| Tool | Niche |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| **[HexyeDEV/JevPR](https://github.com/HexyeDEV/JevPR)** | GitHub App + policy router (`LOW` / `NORMAL` / `SPECIALIST` → approve or assign reviewers). A review-routing service, not a CI gate. |
|
|
43
|
+
| **[1jehuang/jev-pr-labeler](https://github.com/1jehuang/jev-pr-labeler)** | Semantic labels (`type:`, `area:`, `size:`) via OpenRouter. Taxonomy, not merge readiness. |
|
|
44
|
+
| **jevtriage (this repo)** | **Triage gate**: `ready` / `needs_review` / `risky` + confidence fail + marketplace-friendly Action + CLI exit codes. |
|
|
45
|
+
|
|
46
|
+
Pair with [`jevcheck`](https://github.com/sathariels/jevcheck) contracts if you want upgrades to fail when this question flips. **jevcheck is not a v0.1 dependency** — `fixtures/pr-triage.contract.json` and `fixtures/replay-ready.json` are the replay shape.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install -e ".[dev]"
|
|
52
|
+
export TYPESAFE_API_KEY=... # live Jev only; never commit this
|
|
53
|
+
export TYPESAFE_DEFAULT_MODEL=jev-1.13.0
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Auth is **`TYPESAFE_API_KEY` only** (`typesafe-sdk`). Unit tests mock the client. This repo does not publish to PyPI in v0.1; the Action installs the checked-out tree.
|
|
57
|
+
|
|
58
|
+
## GitHub Action
|
|
59
|
+
|
|
60
|
+
Marketplace-friendly root `action.yml`:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
# .github/workflows/pr-triage.yml
|
|
64
|
+
name: pr-triage
|
|
65
|
+
on: pull_request
|
|
66
|
+
permissions:
|
|
67
|
+
contents: read
|
|
68
|
+
pull-requests: write
|
|
69
|
+
issues: write
|
|
70
|
+
jobs:
|
|
71
|
+
triage:
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- uses: sathariels/jevtriage@v0.1.0 # pin a tag or commit SHA once cut; @main until then
|
|
75
|
+
id: triage
|
|
76
|
+
env:
|
|
77
|
+
TYPESAFE_API_KEY: ${{ secrets.TYPESAFE_API_KEY }}
|
|
78
|
+
with:
|
|
79
|
+
model: jev-1.13.0
|
|
80
|
+
apply-labels: true
|
|
81
|
+
- run: echo "${{ steps.triage.outputs.verdict }} ${{ steps.triage.outputs.confidence }}"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Until a `v0.1.0` tag exists, use `sathariels/jevtriage@main` (or this PR’s SHA). The step **fails** on exit 1 or 2 — that is the gate. Use `continue-on-error: true` only if you want labels/comments after a non-ready verdict.
|
|
85
|
+
|
|
86
|
+
This repository’s [example workflow](.github/workflows/example-pr-triage.yml) runs the same Action with `answers: fixtures/replay-single-ready.json` so default CI never calls TypeSafe.
|
|
87
|
+
|
|
88
|
+
### Inputs
|
|
89
|
+
|
|
90
|
+
| Input | Default | Notes |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `model` | `TYPESAFE_DEFAULT_MODEL` | Required pin. `latest` / `preview` / empty rejected |
|
|
93
|
+
| `min-confidence` | `0.8` | Official `ChoiceAnswer.confidence` floor for exit 0 |
|
|
94
|
+
| `allow-unpinned` | `false` | Production stay `false` |
|
|
95
|
+
| `apply-labels` | `false` | Writes `jev:ready`, `jev:needs-review`, `jev:risky` |
|
|
96
|
+
| `github-token` | `${{ github.token }}` | PR fetch + optional labels |
|
|
97
|
+
| `pull-request` | event PR number | Used to fetch title/body/diff |
|
|
98
|
+
| `repository` | `${{ github.repository }}` | `owner/name` |
|
|
99
|
+
| `title` / `body` / `diff-path` | empty | Overrides; skip GitHub fetch when you pass them |
|
|
100
|
+
| `answers` / `case` | empty | Fixture replay (CI / no key) |
|
|
101
|
+
| `max-diff-chars` | `32000` | Unified diff cap (~20–40k) |
|
|
102
|
+
| `python-version` | `3.12` | |
|
|
103
|
+
|
|
104
|
+
### Outputs
|
|
105
|
+
|
|
106
|
+
| Output | Source |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `verdict` | `ChoiceAnswer.choice` (`ready` / `needs_review` / `risky`) |
|
|
109
|
+
| `confidence` | official `ChoiceAnswer.confidence` in `[0, 1]` |
|
|
110
|
+
| `model` | `SystemOneResponse.model` |
|
|
111
|
+
|
|
112
|
+
## CLI
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Live (needs TYPESAFE_API_KEY). Fetches the PR via GITHUB_TOKEN.
|
|
116
|
+
jevtriage --repo owner/name --pr 123 --model jev-1.13.0
|
|
117
|
+
|
|
118
|
+
# Local files
|
|
119
|
+
jevtriage --title "Fix typo" --body "nits" --diff pr.diff --model jev-1.13.0
|
|
120
|
+
|
|
121
|
+
# Replay (no key) — jevcheck-style fixture
|
|
122
|
+
jevtriage --answers fixtures/replay-single-ready.json --model jev-1.13.0
|
|
123
|
+
jevtriage --answers fixtures/replay-ready.json --case auth-bypass --model jev-1.13.0
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Stdout is one JSON object (`verdict`, `confidence`, `probabilities`, `model`, `usage`, `truncated`). A one-line summary goes to stderr. When `GITHUB_OUTPUT` is set, the Action outputs are appended.
|
|
127
|
+
|
|
128
|
+
### Environment
|
|
129
|
+
|
|
130
|
+
| Variable | Role |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `TYPESAFE_API_KEY` | **Required** for live calls. The only auth env. |
|
|
133
|
+
| `TYPESAFE_DEFAULT_MODEL` | Pin when `--model` / Action `model` is empty |
|
|
134
|
+
| `TYPESAFE_BASE_URL` | Optional SDK API root |
|
|
135
|
+
| `GITHUB_TOKEN` / `GH_TOKEN` | Fetch the PR; apply labels if requested |
|
|
136
|
+
| `GITHUB_REPOSITORY` | Default `--repo` |
|
|
137
|
+
| `GITHUB_OUTPUT` | Write Action outputs |
|
|
138
|
+
|
|
139
|
+
## Exit codes
|
|
140
|
+
|
|
141
|
+
| Code | When |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| **0** | Verdict is `ready` **and** `confidence` ≥ `--min-confidence` |
|
|
144
|
+
| **1** | Verdict is `needs_review`, **or** `ready`/`needs_review` below the confidence threshold |
|
|
145
|
+
| **2** | Verdict is `risky`, **or** a config/API/identity error |
|
|
146
|
+
|
|
147
|
+
Exit **2** is shared on purpose (fail closed). Distinguish them from stdout: a successful call has `verdict`; a failure has `"error"`. `risky` still writes `verdict` + Action outputs before exiting 2. Config errors (missing key, unpinned model, bad replay) also exit 2 and print `{"error": "..."}`.
|
|
148
|
+
|
|
149
|
+
Label apply failures are logged to stderr and **do not** change the verdict exit code.
|
|
150
|
+
|
|
151
|
+
## What Jev sees
|
|
152
|
+
|
|
153
|
+
System One `state` is a JSON object (official SDK: string or object):
|
|
154
|
+
|
|
155
|
+
- `title`, `body` (body capped at 8k chars)
|
|
156
|
+
- `diff` — unified diff, capped at 32k chars by default, with a visible truncation note
|
|
157
|
+
- `files` + `stats` when provided or when the GitHub compare payload is used
|
|
158
|
+
- `jevtriage_notes` when anything was truncated or the diff was omitted
|
|
159
|
+
|
|
160
|
+
The only question is a `Choice` named `triage` with **exactly** the three criteria above. Official fields only (`instructions`, `criteria`). The gate uses official `choice` + `confidence` + `probabilities` — no invented confidence field.
|
|
161
|
+
|
|
162
|
+
## Model pinning
|
|
163
|
+
|
|
164
|
+
Production mode rejects empty names and floating aliases (`jev-latest`, `jev-preview`, or any name containing `latest` or `preview`). A documented TypeSafe catalog pin as of 2026-09-19 is `jev-1.13.0`. Pass `--allow-unpinned` only for experiments.
|
|
165
|
+
|
|
166
|
+
The response `model` must match the requested pin (exact string). An opted-in alias may resolve to a concrete version.
|
|
167
|
+
|
|
168
|
+
## Optional labels
|
|
169
|
+
|
|
170
|
+
With `--apply-labels` / `apply-labels: true` and `GITHUB_TOKEN`:
|
|
171
|
+
|
|
172
|
+
- `jev:ready`
|
|
173
|
+
- `jev:needs-review`
|
|
174
|
+
- `jev:risky`
|
|
175
|
+
|
|
176
|
+
Other `jev:*` triage labels on the PR are removed so only one remains.
|
|
177
|
+
|
|
178
|
+
## jevcheck (optional)
|
|
179
|
+
|
|
180
|
+
`fixtures/pr-triage.contract.json` is a v0.1 jevcheck contract for this exact question. `fixtures/replay-ready.json` is a keyed replay (`docs-typo`, `large-ambiguous`, `auth-bypass`). After you add `jevcheck` to a repo:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
jevcheck eval fixtures/pr-triage.contract.json \
|
|
184
|
+
--candidate-model jev-1.13.0 \
|
|
185
|
+
--answers fixtures/replay-ready.json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
v0.1 of jevtriage does not install or invoke jevcheck.
|
|
189
|
+
|
|
190
|
+
## Develop
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
pip install -e ".[dev]"
|
|
194
|
+
pytest
|
|
195
|
+
python -m jevtriage --answers fixtures/replay-single-ready.json --model jev-1.13.0
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
This repo’s default CI runs unit tests (mocked client) and a wheel/sdist smoke on the fixture. No live TypeSafe call.
|
|
199
|
+
|
|
200
|
+
Verified System One contract: [docs.typesafe.ai/sdk/python.md](https://docs.typesafe.ai/sdk/python.md). Package: `typesafe-sdk`.
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# jevtriage
|
|
2
|
+
|
|
3
|
+
**Triage gate for pull requests.** A GitHub Action and small Python CLI that asks [TypeSafe Jev](https://typesafe.ai) (System One) one `Choice` question:
|
|
4
|
+
|
|
5
|
+
| Verdict | Meaning |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `ready` | Looks safe to merge with normal review depth |
|
|
8
|
+
| `needs_review` | Needs human eyes (unclear risk, large or ambiguous change) |
|
|
9
|
+
| `risky` | Elevated risk (security-sensitive, destructive, or high blast radius) |
|
|
10
|
+
|
|
11
|
+
The official `ChoiceAnswer.confidence` is a gate: **`ready` only exits 0 when confidence ≥ threshold** (default `0.8`). Low confidence fails closed to `needs_review` (exit 1).
|
|
12
|
+
|
|
13
|
+
v0.1 is a **drop-in Action**. Copy one workflow YAML, set `TYPESAFE_API_KEY`, pin a model, get a verdict on every PR.
|
|
14
|
+
|
|
15
|
+
## Not these nearby tools
|
|
16
|
+
|
|
17
|
+
| Tool | Niche |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| **[HexyeDEV/JevPR](https://github.com/HexyeDEV/JevPR)** | GitHub App + policy router (`LOW` / `NORMAL` / `SPECIALIST` → approve or assign reviewers). A review-routing service, not a CI gate. |
|
|
20
|
+
| **[1jehuang/jev-pr-labeler](https://github.com/1jehuang/jev-pr-labeler)** | Semantic labels (`type:`, `area:`, `size:`) via OpenRouter. Taxonomy, not merge readiness. |
|
|
21
|
+
| **jevtriage (this repo)** | **Triage gate**: `ready` / `needs_review` / `risky` + confidence fail + marketplace-friendly Action + CLI exit codes. |
|
|
22
|
+
|
|
23
|
+
Pair with [`jevcheck`](https://github.com/sathariels/jevcheck) contracts if you want upgrades to fail when this question flips. **jevcheck is not a v0.1 dependency** — `fixtures/pr-triage.contract.json` and `fixtures/replay-ready.json` are the replay shape.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install -e ".[dev]"
|
|
29
|
+
export TYPESAFE_API_KEY=... # live Jev only; never commit this
|
|
30
|
+
export TYPESAFE_DEFAULT_MODEL=jev-1.13.0
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Auth is **`TYPESAFE_API_KEY` only** (`typesafe-sdk`). Unit tests mock the client. This repo does not publish to PyPI in v0.1; the Action installs the checked-out tree.
|
|
34
|
+
|
|
35
|
+
## GitHub Action
|
|
36
|
+
|
|
37
|
+
Marketplace-friendly root `action.yml`:
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
# .github/workflows/pr-triage.yml
|
|
41
|
+
name: pr-triage
|
|
42
|
+
on: pull_request
|
|
43
|
+
permissions:
|
|
44
|
+
contents: read
|
|
45
|
+
pull-requests: write
|
|
46
|
+
issues: write
|
|
47
|
+
jobs:
|
|
48
|
+
triage:
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
steps:
|
|
51
|
+
- uses: sathariels/jevtriage@v0.1.0 # pin a tag or commit SHA once cut; @main until then
|
|
52
|
+
id: triage
|
|
53
|
+
env:
|
|
54
|
+
TYPESAFE_API_KEY: ${{ secrets.TYPESAFE_API_KEY }}
|
|
55
|
+
with:
|
|
56
|
+
model: jev-1.13.0
|
|
57
|
+
apply-labels: true
|
|
58
|
+
- run: echo "${{ steps.triage.outputs.verdict }} ${{ steps.triage.outputs.confidence }}"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Until a `v0.1.0` tag exists, use `sathariels/jevtriage@main` (or this PR’s SHA). The step **fails** on exit 1 or 2 — that is the gate. Use `continue-on-error: true` only if you want labels/comments after a non-ready verdict.
|
|
62
|
+
|
|
63
|
+
This repository’s [example workflow](.github/workflows/example-pr-triage.yml) runs the same Action with `answers: fixtures/replay-single-ready.json` so default CI never calls TypeSafe.
|
|
64
|
+
|
|
65
|
+
### Inputs
|
|
66
|
+
|
|
67
|
+
| Input | Default | Notes |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `model` | `TYPESAFE_DEFAULT_MODEL` | Required pin. `latest` / `preview` / empty rejected |
|
|
70
|
+
| `min-confidence` | `0.8` | Official `ChoiceAnswer.confidence` floor for exit 0 |
|
|
71
|
+
| `allow-unpinned` | `false` | Production stay `false` |
|
|
72
|
+
| `apply-labels` | `false` | Writes `jev:ready`, `jev:needs-review`, `jev:risky` |
|
|
73
|
+
| `github-token` | `${{ github.token }}` | PR fetch + optional labels |
|
|
74
|
+
| `pull-request` | event PR number | Used to fetch title/body/diff |
|
|
75
|
+
| `repository` | `${{ github.repository }}` | `owner/name` |
|
|
76
|
+
| `title` / `body` / `diff-path` | empty | Overrides; skip GitHub fetch when you pass them |
|
|
77
|
+
| `answers` / `case` | empty | Fixture replay (CI / no key) |
|
|
78
|
+
| `max-diff-chars` | `32000` | Unified diff cap (~20–40k) |
|
|
79
|
+
| `python-version` | `3.12` | |
|
|
80
|
+
|
|
81
|
+
### Outputs
|
|
82
|
+
|
|
83
|
+
| Output | Source |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `verdict` | `ChoiceAnswer.choice` (`ready` / `needs_review` / `risky`) |
|
|
86
|
+
| `confidence` | official `ChoiceAnswer.confidence` in `[0, 1]` |
|
|
87
|
+
| `model` | `SystemOneResponse.model` |
|
|
88
|
+
|
|
89
|
+
## CLI
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Live (needs TYPESAFE_API_KEY). Fetches the PR via GITHUB_TOKEN.
|
|
93
|
+
jevtriage --repo owner/name --pr 123 --model jev-1.13.0
|
|
94
|
+
|
|
95
|
+
# Local files
|
|
96
|
+
jevtriage --title "Fix typo" --body "nits" --diff pr.diff --model jev-1.13.0
|
|
97
|
+
|
|
98
|
+
# Replay (no key) — jevcheck-style fixture
|
|
99
|
+
jevtriage --answers fixtures/replay-single-ready.json --model jev-1.13.0
|
|
100
|
+
jevtriage --answers fixtures/replay-ready.json --case auth-bypass --model jev-1.13.0
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Stdout is one JSON object (`verdict`, `confidence`, `probabilities`, `model`, `usage`, `truncated`). A one-line summary goes to stderr. When `GITHUB_OUTPUT` is set, the Action outputs are appended.
|
|
104
|
+
|
|
105
|
+
### Environment
|
|
106
|
+
|
|
107
|
+
| Variable | Role |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `TYPESAFE_API_KEY` | **Required** for live calls. The only auth env. |
|
|
110
|
+
| `TYPESAFE_DEFAULT_MODEL` | Pin when `--model` / Action `model` is empty |
|
|
111
|
+
| `TYPESAFE_BASE_URL` | Optional SDK API root |
|
|
112
|
+
| `GITHUB_TOKEN` / `GH_TOKEN` | Fetch the PR; apply labels if requested |
|
|
113
|
+
| `GITHUB_REPOSITORY` | Default `--repo` |
|
|
114
|
+
| `GITHUB_OUTPUT` | Write Action outputs |
|
|
115
|
+
|
|
116
|
+
## Exit codes
|
|
117
|
+
|
|
118
|
+
| Code | When |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| **0** | Verdict is `ready` **and** `confidence` ≥ `--min-confidence` |
|
|
121
|
+
| **1** | Verdict is `needs_review`, **or** `ready`/`needs_review` below the confidence threshold |
|
|
122
|
+
| **2** | Verdict is `risky`, **or** a config/API/identity error |
|
|
123
|
+
|
|
124
|
+
Exit **2** is shared on purpose (fail closed). Distinguish them from stdout: a successful call has `verdict`; a failure has `"error"`. `risky` still writes `verdict` + Action outputs before exiting 2. Config errors (missing key, unpinned model, bad replay) also exit 2 and print `{"error": "..."}`.
|
|
125
|
+
|
|
126
|
+
Label apply failures are logged to stderr and **do not** change the verdict exit code.
|
|
127
|
+
|
|
128
|
+
## What Jev sees
|
|
129
|
+
|
|
130
|
+
System One `state` is a JSON object (official SDK: string or object):
|
|
131
|
+
|
|
132
|
+
- `title`, `body` (body capped at 8k chars)
|
|
133
|
+
- `diff` — unified diff, capped at 32k chars by default, with a visible truncation note
|
|
134
|
+
- `files` + `stats` when provided or when the GitHub compare payload is used
|
|
135
|
+
- `jevtriage_notes` when anything was truncated or the diff was omitted
|
|
136
|
+
|
|
137
|
+
The only question is a `Choice` named `triage` with **exactly** the three criteria above. Official fields only (`instructions`, `criteria`). The gate uses official `choice` + `confidence` + `probabilities` — no invented confidence field.
|
|
138
|
+
|
|
139
|
+
## Model pinning
|
|
140
|
+
|
|
141
|
+
Production mode rejects empty names and floating aliases (`jev-latest`, `jev-preview`, or any name containing `latest` or `preview`). A documented TypeSafe catalog pin as of 2026-09-19 is `jev-1.13.0`. Pass `--allow-unpinned` only for experiments.
|
|
142
|
+
|
|
143
|
+
The response `model` must match the requested pin (exact string). An opted-in alias may resolve to a concrete version.
|
|
144
|
+
|
|
145
|
+
## Optional labels
|
|
146
|
+
|
|
147
|
+
With `--apply-labels` / `apply-labels: true` and `GITHUB_TOKEN`:
|
|
148
|
+
|
|
149
|
+
- `jev:ready`
|
|
150
|
+
- `jev:needs-review`
|
|
151
|
+
- `jev:risky`
|
|
152
|
+
|
|
153
|
+
Other `jev:*` triage labels on the PR are removed so only one remains.
|
|
154
|
+
|
|
155
|
+
## jevcheck (optional)
|
|
156
|
+
|
|
157
|
+
`fixtures/pr-triage.contract.json` is a v0.1 jevcheck contract for this exact question. `fixtures/replay-ready.json` is a keyed replay (`docs-typo`, `large-ambiguous`, `auth-bypass`). After you add `jevcheck` to a repo:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
jevcheck eval fixtures/pr-triage.contract.json \
|
|
161
|
+
--candidate-model jev-1.13.0 \
|
|
162
|
+
--answers fixtures/replay-ready.json
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
v0.1 of jevtriage does not install or invoke jevcheck.
|
|
166
|
+
|
|
167
|
+
## Develop
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pip install -e ".[dev]"
|
|
171
|
+
pytest
|
|
172
|
+
python -m jevtriage --answers fixtures/replay-single-ready.json --model jev-1.13.0
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
This repo’s default CI runs unit tests (mocked client) and a wheel/sdist smoke on the fixture. No live TypeSafe call.
|
|
176
|
+
|
|
177
|
+
Verified System One contract: [docs.typesafe.ai/sdk/python.md](https://docs.typesafe.ai/sdk/python.md). Package: `typesafe-sdk`.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
name: jevtriage
|
|
2
|
+
description: Triage a pull request with TypeSafe Jev (ready / needs_review / risky)
|
|
3
|
+
author: sathariels
|
|
4
|
+
branding:
|
|
5
|
+
icon: git-pull-request
|
|
6
|
+
color: purple
|
|
7
|
+
|
|
8
|
+
inputs:
|
|
9
|
+
model:
|
|
10
|
+
description: Pinned Jev model (for example jev-1.13.0). Rejects empty / latest / preview. Falls back to TYPESAFE_DEFAULT_MODEL.
|
|
11
|
+
required: false
|
|
12
|
+
default: ""
|
|
13
|
+
min-confidence:
|
|
14
|
+
description: Minimum official ChoiceAnswer.confidence required for exit 0 when the verdict is ready
|
|
15
|
+
required: false
|
|
16
|
+
default: "0.8"
|
|
17
|
+
allow-unpinned:
|
|
18
|
+
description: Opt in to floating latest/preview aliases. Keep false in production.
|
|
19
|
+
required: false
|
|
20
|
+
default: "false"
|
|
21
|
+
apply-labels:
|
|
22
|
+
description: Apply jev:ready / jev:needs-review / jev:risky when GITHUB_TOKEN is set
|
|
23
|
+
required: false
|
|
24
|
+
default: "false"
|
|
25
|
+
github-token:
|
|
26
|
+
description: Token used to read the pull request and optionally apply labels
|
|
27
|
+
required: false
|
|
28
|
+
default: ${{ github.token }}
|
|
29
|
+
pull-request:
|
|
30
|
+
description: Pull request number. Defaults to the triggering pull_request event.
|
|
31
|
+
required: false
|
|
32
|
+
default: ${{ github.event.pull_request.number }}
|
|
33
|
+
repository:
|
|
34
|
+
description: owner/name repository. Defaults to github.repository.
|
|
35
|
+
required: false
|
|
36
|
+
default: ${{ github.repository }}
|
|
37
|
+
title:
|
|
38
|
+
description: Override PR title (skips GitHub title fetch when set with body/diff)
|
|
39
|
+
required: false
|
|
40
|
+
default: ""
|
|
41
|
+
body:
|
|
42
|
+
description: Override PR body
|
|
43
|
+
required: false
|
|
44
|
+
default: ""
|
|
45
|
+
diff-path:
|
|
46
|
+
description: Path to a unified diff file
|
|
47
|
+
required: false
|
|
48
|
+
default: ""
|
|
49
|
+
answers:
|
|
50
|
+
description: Replay JSON (single System One response or jevcheck-style keyed replay). Skips the live TypeSafe call.
|
|
51
|
+
required: false
|
|
52
|
+
default: ""
|
|
53
|
+
case:
|
|
54
|
+
description: Case id when answers is a keyed replay file
|
|
55
|
+
required: false
|
|
56
|
+
default: ""
|
|
57
|
+
max-diff-chars:
|
|
58
|
+
description: Unified diff character cap (default 32000)
|
|
59
|
+
required: false
|
|
60
|
+
default: "32000"
|
|
61
|
+
python-version:
|
|
62
|
+
description: Python version for the composite action
|
|
63
|
+
required: false
|
|
64
|
+
default: "3.12"
|
|
65
|
+
|
|
66
|
+
outputs:
|
|
67
|
+
verdict:
|
|
68
|
+
description: ready | needs_review | risky
|
|
69
|
+
value: ${{ steps.run.outputs.verdict }}
|
|
70
|
+
confidence:
|
|
71
|
+
description: Official ChoiceAnswer.confidence from 0 to 1
|
|
72
|
+
value: ${{ steps.run.outputs.confidence }}
|
|
73
|
+
model:
|
|
74
|
+
description: response.model returned by TypeSafe (or the replay)
|
|
75
|
+
value: ${{ steps.run.outputs.model }}
|
|
76
|
+
|
|
77
|
+
runs:
|
|
78
|
+
using: composite
|
|
79
|
+
steps:
|
|
80
|
+
- name: Set up Python
|
|
81
|
+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
82
|
+
with:
|
|
83
|
+
python-version: ${{ inputs.python-version }}
|
|
84
|
+
|
|
85
|
+
- name: Install jevtriage
|
|
86
|
+
shell: bash
|
|
87
|
+
run: python -m pip install --disable-pip-version-check "${{ github.action_path }}"
|
|
88
|
+
|
|
89
|
+
- name: Run jevtriage
|
|
90
|
+
id: run
|
|
91
|
+
shell: bash
|
|
92
|
+
env:
|
|
93
|
+
GITHUB_TOKEN: ${{ inputs.github-token }}
|
|
94
|
+
GH_TOKEN: ${{ inputs.github-token }}
|
|
95
|
+
JEVTRIAGE_MODEL: ${{ inputs.model }}
|
|
96
|
+
JEVTRIAGE_MIN_CONFIDENCE: ${{ inputs.min-confidence }}
|
|
97
|
+
JEVTRIAGE_ALLOW_UNPINNED: ${{ inputs.allow-unpinned }}
|
|
98
|
+
JEVTRIAGE_APPLY_LABELS: ${{ inputs.apply-labels }}
|
|
99
|
+
JEVTRIAGE_PR: ${{ inputs.pull-request }}
|
|
100
|
+
JEVTRIAGE_REPO: ${{ inputs.repository }}
|
|
101
|
+
JEVTRIAGE_TITLE: ${{ inputs.title }}
|
|
102
|
+
JEVTRIAGE_BODY: ${{ inputs.body }}
|
|
103
|
+
JEVTRIAGE_DIFF: ${{ inputs.diff-path }}
|
|
104
|
+
JEVTRIAGE_ANSWERS: ${{ inputs.answers }}
|
|
105
|
+
JEVTRIAGE_CASE: ${{ inputs.case }}
|
|
106
|
+
JEVTRIAGE_MAX_DIFF: ${{ inputs.max-diff-chars }}
|
|
107
|
+
run: |
|
|
108
|
+
set -euo pipefail
|
|
109
|
+
args=()
|
|
110
|
+
if [[ -n "${JEVTRIAGE_MODEL}" ]]; then
|
|
111
|
+
args+=(--model "${JEVTRIAGE_MODEL}")
|
|
112
|
+
fi
|
|
113
|
+
args+=(--min-confidence "${JEVTRIAGE_MIN_CONFIDENCE}")
|
|
114
|
+
args+=(--max-diff-chars "${JEVTRIAGE_MAX_DIFF}")
|
|
115
|
+
if [[ "${JEVTRIAGE_ALLOW_UNPINNED}" == "true" ]]; then
|
|
116
|
+
args+=(--allow-unpinned)
|
|
117
|
+
elif [[ "${JEVTRIAGE_ALLOW_UNPINNED}" != "false" ]]; then
|
|
118
|
+
echo "allow-unpinned must be true or false" >&2
|
|
119
|
+
exit 2
|
|
120
|
+
fi
|
|
121
|
+
if [[ "${JEVTRIAGE_APPLY_LABELS}" == "true" ]]; then
|
|
122
|
+
args+=(--apply-labels)
|
|
123
|
+
elif [[ "${JEVTRIAGE_APPLY_LABELS}" != "false" ]]; then
|
|
124
|
+
echo "apply-labels must be true or false" >&2
|
|
125
|
+
exit 2
|
|
126
|
+
fi
|
|
127
|
+
if [[ -n "${JEVTRIAGE_PR}" ]]; then
|
|
128
|
+
args+=(--pr "${JEVTRIAGE_PR}")
|
|
129
|
+
fi
|
|
130
|
+
if [[ -n "${JEVTRIAGE_REPO}" ]]; then
|
|
131
|
+
args+=(--repo "${JEVTRIAGE_REPO}")
|
|
132
|
+
fi
|
|
133
|
+
if [[ -n "${JEVTRIAGE_TITLE}" ]]; then
|
|
134
|
+
args+=(--title "${JEVTRIAGE_TITLE}")
|
|
135
|
+
fi
|
|
136
|
+
if [[ -n "${JEVTRIAGE_BODY}" ]]; then
|
|
137
|
+
args+=(--body "${JEVTRIAGE_BODY}")
|
|
138
|
+
fi
|
|
139
|
+
if [[ -n "${JEVTRIAGE_DIFF}" ]]; then
|
|
140
|
+
args+=(--diff "${JEVTRIAGE_DIFF}")
|
|
141
|
+
fi
|
|
142
|
+
if [[ -n "${JEVTRIAGE_ANSWERS}" ]]; then
|
|
143
|
+
args+=(--answers "${JEVTRIAGE_ANSWERS}")
|
|
144
|
+
fi
|
|
145
|
+
if [[ -n "${JEVTRIAGE_CASE}" ]]; then
|
|
146
|
+
args+=(--case "${JEVTRIAGE_CASE}")
|
|
147
|
+
fi
|
|
148
|
+
jevtriage "${args[@]}"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1",
|
|
3
|
+
"name": "jevtriage-pr-gate",
|
|
4
|
+
"baseline_model": "jev-1.13.0",
|
|
5
|
+
"defaults": {
|
|
6
|
+
"min_confidence": 0.8,
|
|
7
|
+
"confidence_tolerance": 0.1
|
|
8
|
+
},
|
|
9
|
+
"cases": [
|
|
10
|
+
{
|
|
11
|
+
"id": "docs-typo",
|
|
12
|
+
"state": {
|
|
13
|
+
"title": "Fix typo in README",
|
|
14
|
+
"body": "Correct a spelling error in the install section.",
|
|
15
|
+
"diff": "diff --git a/README.md b/README.md\n--- a/README.md\n+++ b/README.md\n@@ -1,3 +1,3 @@\n-instal\n+install\n",
|
|
16
|
+
"diff_truncated": false,
|
|
17
|
+
"diff_original_chars": 120
|
|
18
|
+
},
|
|
19
|
+
"questions": {
|
|
20
|
+
"triage": {
|
|
21
|
+
"type": "choice",
|
|
22
|
+
"instructions": "Triage this pull request for merge readiness. Choose exactly one label. Use the pull request title, body, and the provided diff (or file list and stats when the diff is truncated or omitted).",
|
|
23
|
+
"criteria": {
|
|
24
|
+
"ready": "Looks safe to merge with normal review depth. The change is focused, has low blast radius, and is not security-sensitive or destructive.",
|
|
25
|
+
"needs_review": "Needs human eyes. Risk is unclear, or the change is large, ambiguous, or hard to assess from the provided context.",
|
|
26
|
+
"risky": "Elevated risk. Security-sensitive, destructive, or high blast radius."
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"expect": {
|
|
31
|
+
"triage": {
|
|
32
|
+
"choice": "ready",
|
|
33
|
+
"min_confidence": 0.8,
|
|
34
|
+
"baseline_confidence": 0.94
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "large-ambiguous",
|
|
40
|
+
"state": {
|
|
41
|
+
"title": "Refactor billing pipeline",
|
|
42
|
+
"body": "Move several services around. Behavior should be the same.",
|
|
43
|
+
"diff": "",
|
|
44
|
+
"diff_truncated": false,
|
|
45
|
+
"stats": {"additions": 1800, "deletions": 1400, "changed_files": 47},
|
|
46
|
+
"jevtriage_notes": ["unified diff omitted; using file list and stats"]
|
|
47
|
+
},
|
|
48
|
+
"questions": {
|
|
49
|
+
"triage": {
|
|
50
|
+
"type": "choice",
|
|
51
|
+
"instructions": "Triage this pull request for merge readiness. Choose exactly one label. Use the pull request title, body, and the provided diff (or file list and stats when the diff is truncated or omitted).",
|
|
52
|
+
"criteria": {
|
|
53
|
+
"ready": "Looks safe to merge with normal review depth. The change is focused, has low blast radius, and is not security-sensitive or destructive.",
|
|
54
|
+
"needs_review": "Needs human eyes. Risk is unclear, or the change is large, ambiguous, or hard to assess from the provided context.",
|
|
55
|
+
"risky": "Elevated risk. Security-sensitive, destructive, or high blast radius."
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"expect": {
|
|
60
|
+
"triage": {
|
|
61
|
+
"choice": "needs_review",
|
|
62
|
+
"min_confidence": 0.75,
|
|
63
|
+
"baseline_confidence": 0.88
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "auth-bypass",
|
|
69
|
+
"state": {
|
|
70
|
+
"title": "Skip auth on internal admin routes",
|
|
71
|
+
"body": "Temporary workaround so the dashboard loads.",
|
|
72
|
+
"diff": "diff --git a/app/auth.py b/app/auth.py\n--- a/app/auth.py\n+++ b/app/auth.py\n@@ -1,6 +1,3 @@\n-def require_admin(user):\n- if not user.is_admin:\n- raise PermissionError\n+def require_admin(user):\n+ return True\n",
|
|
73
|
+
"diff_truncated": false
|
|
74
|
+
},
|
|
75
|
+
"questions": {
|
|
76
|
+
"triage": {
|
|
77
|
+
"type": "choice",
|
|
78
|
+
"instructions": "Triage this pull request for merge readiness. Choose exactly one label. Use the pull request title, body, and the provided diff (or file list and stats when the diff is truncated or omitted).",
|
|
79
|
+
"criteria": {
|
|
80
|
+
"ready": "Looks safe to merge with normal review depth. The change is focused, has low blast radius, and is not security-sensitive or destructive.",
|
|
81
|
+
"needs_review": "Needs human eyes. Risk is unclear, or the change is large, ambiguous, or hard to assess from the provided context.",
|
|
82
|
+
"risky": "Elevated risk. Security-sensitive, destructive, or high blast radius."
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"expect": {
|
|
87
|
+
"triage": {
|
|
88
|
+
"choice": "risky",
|
|
89
|
+
"min_confidence": 0.85,
|
|
90
|
+
"baseline_confidence": 0.96
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|