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.
Files changed (37) hide show
  1. jevtriage-0.1.0/.env.example +12 -0
  2. jevtriage-0.1.0/LICENSE +21 -0
  3. jevtriage-0.1.0/MANIFEST.in +9 -0
  4. jevtriage-0.1.0/PKG-INFO +204 -0
  5. jevtriage-0.1.0/README.md +181 -0
  6. jevtriage-0.1.0/action.yml +148 -0
  7. jevtriage-0.1.0/fixtures/pr-triage.contract.json +95 -0
  8. jevtriage-0.1.0/fixtures/replay-ready.json +50 -0
  9. jevtriage-0.1.0/fixtures/replay-single-ready.json +16 -0
  10. jevtriage-0.1.0/pyproject.toml +45 -0
  11. jevtriage-0.1.0/setup.cfg +4 -0
  12. jevtriage-0.1.0/src/jevtriage/__init__.py +18 -0
  13. jevtriage-0.1.0/src/jevtriage/__main__.py +4 -0
  14. jevtriage-0.1.0/src/jevtriage/answers.py +174 -0
  15. jevtriage-0.1.0/src/jevtriage/cli.py +236 -0
  16. jevtriage-0.1.0/src/jevtriage/client.py +103 -0
  17. jevtriage-0.1.0/src/jevtriage/gate.py +32 -0
  18. jevtriage-0.1.0/src/jevtriage/github.py +192 -0
  19. jevtriage-0.1.0/src/jevtriage/pinning.py +69 -0
  20. jevtriage-0.1.0/src/jevtriage/py.typed +0 -0
  21. jevtriage-0.1.0/src/jevtriage/questions.py +54 -0
  22. jevtriage-0.1.0/src/jevtriage/state.py +94 -0
  23. jevtriage-0.1.0/src/jevtriage.egg-info/PKG-INFO +204 -0
  24. jevtriage-0.1.0/src/jevtriage.egg-info/SOURCES.txt +35 -0
  25. jevtriage-0.1.0/src/jevtriage.egg-info/dependency_links.txt +1 -0
  26. jevtriage-0.1.0/src/jevtriage.egg-info/entry_points.txt +2 -0
  27. jevtriage-0.1.0/src/jevtriage.egg-info/requires.txt +4 -0
  28. jevtriage-0.1.0/src/jevtriage.egg-info/top_level.txt +1 -0
  29. jevtriage-0.1.0/tests/test_answers.py +65 -0
  30. jevtriage-0.1.0/tests/test_cli.py +162 -0
  31. jevtriage-0.1.0/tests/test_client.py +108 -0
  32. jevtriage-0.1.0/tests/test_gate.py +26 -0
  33. jevtriage-0.1.0/tests/test_github.py +100 -0
  34. jevtriage-0.1.0/tests/test_golden_fixture.py +39 -0
  35. jevtriage-0.1.0/tests/test_pinning.py +53 -0
  36. jevtriage-0.1.0/tests/test_questions.py +21 -0
  37. 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=
@@ -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.
@@ -0,0 +1,9 @@
1
+ include LICENSE
2
+ include README.md
3
+ include .env.example
4
+ include action.yml
5
+ include pyproject.toml
6
+ recursive-include fixtures *.json
7
+ recursive-include tests *.py
8
+ graft src/jevtriage
9
+ global-exclude __pycache__ *.py[cod]
@@ -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
+ }