sooth 0.2.1__tar.gz → 0.2.3__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 (38) hide show
  1. {sooth-0.2.1 → sooth-0.2.3}/.github/workflows/ci.yml +1 -0
  2. sooth-0.2.3/PKG-INFO +174 -0
  3. sooth-0.2.3/README.md +151 -0
  4. sooth-0.2.3/action.sh +29 -0
  5. sooth-0.2.3/action.yml +35 -0
  6. {sooth-0.2.1 → sooth-0.2.3}/pyproject.toml +1 -1
  7. {sooth-0.2.1 → sooth-0.2.3}/src/sooth/cli.py +28 -3
  8. sooth-0.2.3/src/sooth/demo.json +1 -0
  9. {sooth-0.2.1 → sooth-0.2.3}/src/sooth/report.py +24 -0
  10. sooth-0.2.3/tests/test_action.sh +86 -0
  11. {sooth-0.2.1 → sooth-0.2.3}/tests/test_core.py +20 -1
  12. sooth-0.2.1/PKG-INFO +0 -110
  13. sooth-0.2.1/README.md +0 -87
  14. {sooth-0.2.1 → sooth-0.2.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  15. {sooth-0.2.1 → sooth-0.2.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  16. {sooth-0.2.1 → sooth-0.2.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {sooth-0.2.1 → sooth-0.2.3}/.gitignore +0 -0
  18. {sooth-0.2.1 → sooth-0.2.3}/LICENSE +0 -0
  19. {sooth-0.2.1 → sooth-0.2.3}/docs/DESIGN.md +0 -0
  20. {sooth-0.2.1 → sooth-0.2.3}/docs/DOGFOOD.md +0 -0
  21. {sooth-0.2.1 → sooth-0.2.3}/docs/PRD.md +0 -0
  22. {sooth-0.2.1 → sooth-0.2.3}/docs/TESTING.md +0 -0
  23. {sooth-0.2.1 → sooth-0.2.3}/examples/calibration.json +0 -0
  24. {sooth-0.2.1 → sooth-0.2.3}/examples/calibration.md +0 -0
  25. {sooth-0.2.1 → sooth-0.2.3}/examples/draft.md +0 -0
  26. {sooth-0.2.1 → sooth-0.2.3}/examples/news-1-evil.md +0 -0
  27. {sooth-0.2.1 → sooth-0.2.3}/examples/news-1-output.md +0 -0
  28. {sooth-0.2.1 → sooth-0.2.3}/examples/news-1-summ.md +0 -0
  29. {sooth-0.2.1 → sooth-0.2.3}/examples/news-1.md +0 -0
  30. {sooth-0.2.1 → sooth-0.2.3}/examples/policy.md +0 -0
  31. {sooth-0.2.1 → sooth-0.2.3}/examples/pricing.md +0 -0
  32. {sooth-0.2.1 → sooth-0.2.3}/examples/source.md +0 -0
  33. {sooth-0.2.1 → sooth-0.2.3}/src/sooth/__init__.py +0 -0
  34. {sooth-0.2.1 → sooth-0.2.3}/src/sooth/claims.py +0 -0
  35. {sooth-0.2.1 → sooth-0.2.3}/src/sooth/verify.py +0 -0
  36. {sooth-0.2.1 → sooth-0.2.3}/tests/calibrate.py +0 -0
  37. {sooth-0.2.1 → sooth-0.2.3}/tests/conftest.py +0 -0
  38. {sooth-0.2.1 → sooth-0.2.3}/tests/smoke.sh +0 -0
@@ -19,3 +19,4 @@ jobs:
19
19
  - run: pip install -e ".[dev]"
20
20
  - run: ruff check src tests
21
21
  - run: pytest
22
+ - run: bash tests/test_action.sh
sooth-0.2.3/PKG-INFO ADDED
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.5
2
+ Name: sooth
3
+ Version: 0.2.3
4
+ Summary: Verify AI-generated text against source material. Claim-by-claim trust reports powered by Jev (TypeSafe System One).
5
+ Project-URL: Homepage, https://github.com/naufalhilmiaji/sooth
6
+ Project-URL: Documentation, https://github.com/naufalhilmiaji/sooth/tree/main/docs
7
+ Author-email: Naufal Hilmiaji <nhilmiaji@gmail.com>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai,jev,llm,trust,typesafe,verification
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: typesafe-sdk
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == 'dev'
21
+ Requires-Dist: ruff; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Sooth
25
+
26
+ **LLMs generate. Sooth verifies.**
27
+
28
+ Claim-by-claim fact-checking for AI output, designed for CI. Sooth checks every sentence in a draft against your source material and returns `PASS` / `FAIL` / `REVIEW` with calibrated probabilities and the exact source line behind each verdict.
29
+
30
+ [![PyPI](https://img.shields.io/pypi/v/sooth.svg)](https://pypi.org/project/sooth/)
31
+ [![Python](https://img.shields.io/pypi/pyversions/sooth.svg)](https://pypi.org/project/sooth/)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/naufalhilmiaji/sooth/blob/main/LICENSE)
33
+ [![CI](https://github.com/naufalhilmiaji/sooth/actions/workflows/ci.yml/badge.svg)](https://github.com/naufalhilmiaji/sooth/actions/workflows/ci.yml)
34
+
35
+ ## See it catch a hallucination
36
+
37
+ ```bash
38
+ pip install sooth
39
+ sooth demo # no API key needed
40
+ ```
41
+
42
+ Real output — seven claims in an AI-written news summary, three numbers quietly wrong:
43
+
44
+ ```
45
+ # Sooth
46
+
47
+ **PASS 2 · FAIL 3 · REVIEW 1 · UNCHECKABLE 1** — threshold 0.70
48
+
49
+ | # | Claim | Verdict | P | Why (P distribution) | Evidence |
50
+ |---|-------|---------|---|----------------------|----------|
51
+ | 1 | Aturan baru BEI menurunkan batas bawah harga saham menjadi Rp 1 per saham mulai 28 September 2026. | ✅ PASS | ████████ 1.00 | supports 1.00 / not_found 0.00 / contradicts 0.00 · details 0.98 | `examples/news-1.md:3` "Saham-saham Grup Bakrie kembali menjadi sorotan menjelang…" |
52
+ | 2 | Menurut analis Bumiputera Sekuritas, aturan ini berlaku untuk seluruh emiten di BEI. | ✅ PASS | ████████ 1.00 | supports 1.00 / not_found 0.00 / contradicts 0.00 · details 0.82 | `examples/news-1.md:7` "Ia menyebut aturan ini berlaku untuk seluruh saham di BEI…" |
53
+ | 3 | BNBR baru menuntaskan rights issue bernilai besar di harga Rp 33. | ❌ FAIL | ████████ 1.00 | supports 0.00 / not_found 0.00 / contradicts 1.00 · details 0.01 · missing #s: 33 | `examples/news-1.md:25` "Saham ini juga baru menyelesaikan rights issue dalam juml…" |
54
+ | 4 | Saham VIVA disuspensi di harga Rp 50. | ❌ FAIL | ████████ 1.00 | supports 0.00 / not_found 0.00 / contradicts 1.00 · details 0.02 | |
55
+ | 5 | BUMI hanya perlu turun sekitar 10% untuk menyentuh level Rp 50. | ❌ FAIL | ████████ 1.00 | supports 0.00 / not_found 0.00 / contradicts 1.00 · details 0.01 · missing #s: 10 | `examples/news-1.md:23` "Adapun PT Bumi Resources Tbk (BUMI) di sekitar Rp192 haru…" |
56
+ | 6 | BEI juga menetapkan batas atas harga saham Rp 5.000 per saham di pasar reguler. | ⚠️ REVIEW | ████████ 1.00 | supports 0.00 / not_found 1.00 / contradicts 0.00 · details 0.02 · missing #s: 5.000 | |
57
+ | 7 | Para investor ritel sangat senang dengan aturan baru ini. | ➖ UNCHECKABLE | ██░░░░░░ 0.25 | P(checkable)=0.25 | |
58
+
59
+ ## Needs review
60
+
61
+ - line 13: BEI juga menetapkan batas atas harga saham Rp 5.000 per saham di pasar reguler.
62
+ - line 15: Para investor ritel sangat senang dengan aturan baru ini.
63
+ ```
64
+
65
+ Every verdict carries its probability distribution and the source span it was judged against. When Sooth is unsure, it says `REVIEW` instead of guessing.
66
+
67
+ ## Why
68
+
69
+ AI writes fast, nobody checks. Claims ship wrong — usually a number, a hedge, or a name that drifted. Asking another LLM "is this right?" just produces more prose.
70
+
71
+ Sooth takes a different bet: **the model makes typed judgments, code makes the decision.** Every rule that turns a probability into a verdict is a readable line of Python, not a prompt.
72
+
73
+ ## Install
74
+
75
+ ```bash
76
+ pip install sooth
77
+ export TYPESAFE_API_KEY=... # get one at console.typesafe.ai
78
+ ```
79
+
80
+ ## Usage
81
+
82
+ ```bash
83
+ sooth --source docs/policy.md --source tickets/t123.md --text draft-reply.md
84
+
85
+ # CI-friendly exit codes
86
+ sooth --source policy.md --text draft.md --format plain
87
+ # 0 = clean · 1 = any FAIL · 2 = any REVIEW · 3 = usage/config error
88
+
89
+ # Options
90
+ # --confidence T REVIEW below this confidence (default 0.7)
91
+ # --format md|plain
92
+ # -o FILE write report to file
93
+ # --log FILE append full judgment trace (one JSONL line per run)
94
+ ```
95
+
96
+ Use it in CI as a quality gate for generated content:
97
+
98
+ ```yaml
99
+ - name: Verify AI output
100
+ uses: naufalhilmiaji/sooth@v0.2.3
101
+ with:
102
+ source: docs/policy.md
103
+ text: generated-reply.md
104
+ env:
105
+ TYPESAFE_API_KEY: ${{ secrets.TYPESAFE_API_KEY }}
106
+ ```
107
+
108
+ `source` takes newline-separated paths when you have several documents. The step fails the build on any `FAIL` — and on any `REVIEW`, so add `continue-on-error: true` if you only want to block on hard contradictions. The full report is appended to the job summary.
109
+
110
+ Prefer plain shell? `sooth --source policy.md --text draft.md` gives the same exit codes.
111
+
112
+ ## How it works
113
+
114
+ 1. Draft is split into claims (one sentence each).
115
+ 2. Each claim gets four questions to Jev, fanned out in parallel batches: *is this checkable?*, *does the source support it?* (`supports` / `contradicts` / `not_found`), *do all details match exactly?*, and *which source span is the evidence?*
116
+ 3. Verdicts are mapped in code: uncheckable → `UNCHECKABLE`; low confidence → `REVIEW`; then `PASS` / `FAIL`. Safeguards demote `PASS` to `REVIEW` when details drift or claim numbers are absent from the source (checked in plain code).
117
+ 4. The report shows the full probability distribution per claim — not just a label.
118
+
119
+ The decision logic lives in [`src/sooth/verify.py`](https://github.com/naufalhilmiaji/sooth/blob/main/src/sooth/verify.py) in a dozen readable lines. Change thresholds and rules there, not in prompts.
120
+
121
+ Judgments come from [Jev](https://docs.typesafe.ai) (TypeSafe System One), which returns typed distributions instead of generated prose.
122
+
123
+ ## Why not just use an LLM judge?
124
+
125
+ Not claiming Sooth is universally more accurate. Claiming it gives you **architectural guarantees** a prompt cannot:
126
+
127
+ | | Sooth | Generic LLM judge |
128
+ |---|-------|-------------------|
129
+ | Claim-level verification | ✅ | Sometimes |
130
+ | Source span behind each verdict | ✅ | Sometimes |
131
+ | `PASS` / `FAIL` / `REVIEW` | ✅ fixed vocabulary | Generated labels |
132
+ | Probability distribution | ✅ | Usually none |
133
+ | Verdict rules in readable code | ✅ | Prompt-dependent |
134
+ | CI exit codes | ✅ | ❌ |
135
+ | Audit log (`--log` JSONL) | ✅ | DIY |
136
+
137
+ ## Who it's for
138
+
139
+ Developers building systems that generate text from trusted material:
140
+
141
+ - **RAG pipelines** — retrieved the right document, still wrote the wrong number
142
+ - **AI agents** — before an agent's reply reaches a human
143
+ - **Support / ops copilots** — policy-backed answers, checked against the policy
144
+ - **Report & summary generation** — figures that must match the source
145
+ - **Compliance workflows** — evidence trail for every claim, not vibes
146
+
147
+ ## Known limits (alpha)
148
+
149
+ - Safeguards catch most drift and smuggling: claim numbers absent from the source demote `PASS` to `REVIEW`, and a detail-match gate flags altered hedges ("about 74%" → "over 74%"). Not perfect — read the `Why` column before trusting a verdict.
150
+ - Derived numbers (totals, values computed outside the source) look "missing" and land in `REVIEW`.
151
+ - Source + questions must fit ~64k tokens — split long documents yourself.
152
+
153
+ ## Development
154
+
155
+ ```bash
156
+ pip install -e ".[dev]"
157
+ python3 tests/test_core.py # pure checks, no network
158
+ pytest # same suite
159
+ bash tests/smoke.sh # live smoke (needs TYPESAFE_API_KEY)
160
+ PYTHONPATH=src python3 tests/calibrate.py # live calibration, 30 labeled claims
161
+ ```
162
+
163
+ Docs: [PRD](https://github.com/naufalhilmiaji/sooth/blob/main/docs/PRD.md) · [Design](https://github.com/naufalhilmiaji/sooth/blob/main/docs/DESIGN.md) · [Testing](https://github.com/naufalhilmiaji/sooth/blob/main/docs/TESTING.md)
164
+
165
+ ## Roadmap
166
+
167
+ - v0.2.3 shipped: GitHub Action — CI quality gate in one `uses:` line
168
+ - v0.2.2 shipped: `sooth demo` (offline, no API key)
169
+ - v0.2.1 shipped: source-span evidence, published on PyPI
170
+ - later: hosted web app — paste UI, history, team review queues
171
+
172
+ ## License
173
+
174
+ MIT © Naufal Hilmiaji. Powered by [TypeSafe](https://typesafe.ai) / Jev.
sooth-0.2.3/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # Sooth
2
+
3
+ **LLMs generate. Sooth verifies.**
4
+
5
+ Claim-by-claim fact-checking for AI output, designed for CI. Sooth checks every sentence in a draft against your source material and returns `PASS` / `FAIL` / `REVIEW` with calibrated probabilities and the exact source line behind each verdict.
6
+
7
+ [![PyPI](https://img.shields.io/pypi/v/sooth.svg)](https://pypi.org/project/sooth/)
8
+ [![Python](https://img.shields.io/pypi/pyversions/sooth.svg)](https://pypi.org/project/sooth/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/naufalhilmiaji/sooth/blob/main/LICENSE)
10
+ [![CI](https://github.com/naufalhilmiaji/sooth/actions/workflows/ci.yml/badge.svg)](https://github.com/naufalhilmiaji/sooth/actions/workflows/ci.yml)
11
+
12
+ ## See it catch a hallucination
13
+
14
+ ```bash
15
+ pip install sooth
16
+ sooth demo # no API key needed
17
+ ```
18
+
19
+ Real output — seven claims in an AI-written news summary, three numbers quietly wrong:
20
+
21
+ ```
22
+ # Sooth
23
+
24
+ **PASS 2 · FAIL 3 · REVIEW 1 · UNCHECKABLE 1** — threshold 0.70
25
+
26
+ | # | Claim | Verdict | P | Why (P distribution) | Evidence |
27
+ |---|-------|---------|---|----------------------|----------|
28
+ | 1 | Aturan baru BEI menurunkan batas bawah harga saham menjadi Rp 1 per saham mulai 28 September 2026. | ✅ PASS | ████████ 1.00 | supports 1.00 / not_found 0.00 / contradicts 0.00 · details 0.98 | `examples/news-1.md:3` "Saham-saham Grup Bakrie kembali menjadi sorotan menjelang…" |
29
+ | 2 | Menurut analis Bumiputera Sekuritas, aturan ini berlaku untuk seluruh emiten di BEI. | ✅ PASS | ████████ 1.00 | supports 1.00 / not_found 0.00 / contradicts 0.00 · details 0.82 | `examples/news-1.md:7` "Ia menyebut aturan ini berlaku untuk seluruh saham di BEI…" |
30
+ | 3 | BNBR baru menuntaskan rights issue bernilai besar di harga Rp 33. | ❌ FAIL | ████████ 1.00 | supports 0.00 / not_found 0.00 / contradicts 1.00 · details 0.01 · missing #s: 33 | `examples/news-1.md:25` "Saham ini juga baru menyelesaikan rights issue dalam juml…" |
31
+ | 4 | Saham VIVA disuspensi di harga Rp 50. | ❌ FAIL | ████████ 1.00 | supports 0.00 / not_found 0.00 / contradicts 1.00 · details 0.02 | |
32
+ | 5 | BUMI hanya perlu turun sekitar 10% untuk menyentuh level Rp 50. | ❌ FAIL | ████████ 1.00 | supports 0.00 / not_found 0.00 / contradicts 1.00 · details 0.01 · missing #s: 10 | `examples/news-1.md:23` "Adapun PT Bumi Resources Tbk (BUMI) di sekitar Rp192 haru…" |
33
+ | 6 | BEI juga menetapkan batas atas harga saham Rp 5.000 per saham di pasar reguler. | ⚠️ REVIEW | ████████ 1.00 | supports 0.00 / not_found 1.00 / contradicts 0.00 · details 0.02 · missing #s: 5.000 | |
34
+ | 7 | Para investor ritel sangat senang dengan aturan baru ini. | ➖ UNCHECKABLE | ██░░░░░░ 0.25 | P(checkable)=0.25 | |
35
+
36
+ ## Needs review
37
+
38
+ - line 13: BEI juga menetapkan batas atas harga saham Rp 5.000 per saham di pasar reguler.
39
+ - line 15: Para investor ritel sangat senang dengan aturan baru ini.
40
+ ```
41
+
42
+ Every verdict carries its probability distribution and the source span it was judged against. When Sooth is unsure, it says `REVIEW` instead of guessing.
43
+
44
+ ## Why
45
+
46
+ AI writes fast, nobody checks. Claims ship wrong — usually a number, a hedge, or a name that drifted. Asking another LLM "is this right?" just produces more prose.
47
+
48
+ Sooth takes a different bet: **the model makes typed judgments, code makes the decision.** Every rule that turns a probability into a verdict is a readable line of Python, not a prompt.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install sooth
54
+ export TYPESAFE_API_KEY=... # get one at console.typesafe.ai
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ```bash
60
+ sooth --source docs/policy.md --source tickets/t123.md --text draft-reply.md
61
+
62
+ # CI-friendly exit codes
63
+ sooth --source policy.md --text draft.md --format plain
64
+ # 0 = clean · 1 = any FAIL · 2 = any REVIEW · 3 = usage/config error
65
+
66
+ # Options
67
+ # --confidence T REVIEW below this confidence (default 0.7)
68
+ # --format md|plain
69
+ # -o FILE write report to file
70
+ # --log FILE append full judgment trace (one JSONL line per run)
71
+ ```
72
+
73
+ Use it in CI as a quality gate for generated content:
74
+
75
+ ```yaml
76
+ - name: Verify AI output
77
+ uses: naufalhilmiaji/sooth@v0.2.3
78
+ with:
79
+ source: docs/policy.md
80
+ text: generated-reply.md
81
+ env:
82
+ TYPESAFE_API_KEY: ${{ secrets.TYPESAFE_API_KEY }}
83
+ ```
84
+
85
+ `source` takes newline-separated paths when you have several documents. The step fails the build on any `FAIL` — and on any `REVIEW`, so add `continue-on-error: true` if you only want to block on hard contradictions. The full report is appended to the job summary.
86
+
87
+ Prefer plain shell? `sooth --source policy.md --text draft.md` gives the same exit codes.
88
+
89
+ ## How it works
90
+
91
+ 1. Draft is split into claims (one sentence each).
92
+ 2. Each claim gets four questions to Jev, fanned out in parallel batches: *is this checkable?*, *does the source support it?* (`supports` / `contradicts` / `not_found`), *do all details match exactly?*, and *which source span is the evidence?*
93
+ 3. Verdicts are mapped in code: uncheckable → `UNCHECKABLE`; low confidence → `REVIEW`; then `PASS` / `FAIL`. Safeguards demote `PASS` to `REVIEW` when details drift or claim numbers are absent from the source (checked in plain code).
94
+ 4. The report shows the full probability distribution per claim — not just a label.
95
+
96
+ The decision logic lives in [`src/sooth/verify.py`](https://github.com/naufalhilmiaji/sooth/blob/main/src/sooth/verify.py) in a dozen readable lines. Change thresholds and rules there, not in prompts.
97
+
98
+ Judgments come from [Jev](https://docs.typesafe.ai) (TypeSafe System One), which returns typed distributions instead of generated prose.
99
+
100
+ ## Why not just use an LLM judge?
101
+
102
+ Not claiming Sooth is universally more accurate. Claiming it gives you **architectural guarantees** a prompt cannot:
103
+
104
+ | | Sooth | Generic LLM judge |
105
+ |---|-------|-------------------|
106
+ | Claim-level verification | ✅ | Sometimes |
107
+ | Source span behind each verdict | ✅ | Sometimes |
108
+ | `PASS` / `FAIL` / `REVIEW` | ✅ fixed vocabulary | Generated labels |
109
+ | Probability distribution | ✅ | Usually none |
110
+ | Verdict rules in readable code | ✅ | Prompt-dependent |
111
+ | CI exit codes | ✅ | ❌ |
112
+ | Audit log (`--log` JSONL) | ✅ | DIY |
113
+
114
+ ## Who it's for
115
+
116
+ Developers building systems that generate text from trusted material:
117
+
118
+ - **RAG pipelines** — retrieved the right document, still wrote the wrong number
119
+ - **AI agents** — before an agent's reply reaches a human
120
+ - **Support / ops copilots** — policy-backed answers, checked against the policy
121
+ - **Report & summary generation** — figures that must match the source
122
+ - **Compliance workflows** — evidence trail for every claim, not vibes
123
+
124
+ ## Known limits (alpha)
125
+
126
+ - Safeguards catch most drift and smuggling: claim numbers absent from the source demote `PASS` to `REVIEW`, and a detail-match gate flags altered hedges ("about 74%" → "over 74%"). Not perfect — read the `Why` column before trusting a verdict.
127
+ - Derived numbers (totals, values computed outside the source) look "missing" and land in `REVIEW`.
128
+ - Source + questions must fit ~64k tokens — split long documents yourself.
129
+
130
+ ## Development
131
+
132
+ ```bash
133
+ pip install -e ".[dev]"
134
+ python3 tests/test_core.py # pure checks, no network
135
+ pytest # same suite
136
+ bash tests/smoke.sh # live smoke (needs TYPESAFE_API_KEY)
137
+ PYTHONPATH=src python3 tests/calibrate.py # live calibration, 30 labeled claims
138
+ ```
139
+
140
+ Docs: [PRD](https://github.com/naufalhilmiaji/sooth/blob/main/docs/PRD.md) · [Design](https://github.com/naufalhilmiaji/sooth/blob/main/docs/DESIGN.md) · [Testing](https://github.com/naufalhilmiaji/sooth/blob/main/docs/TESTING.md)
141
+
142
+ ## Roadmap
143
+
144
+ - v0.2.3 shipped: GitHub Action — CI quality gate in one `uses:` line
145
+ - v0.2.2 shipped: `sooth demo` (offline, no API key)
146
+ - v0.2.1 shipped: source-span evidence, published on PyPI
147
+ - later: hosted web app — paste UI, history, team review queues
148
+
149
+ ## License
150
+
151
+ MIT © Naufal Hilmiaji. Powered by [TypeSafe](https://typesafe.ai) / Jev.
sooth-0.2.3/action.sh ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ # Wire SOOTH_* inputs into the sooth CLI, surface the report, propagate the exit code.
3
+ # Inputs arrive via env (never interpolated into this script) so untrusted input
4
+ # cannot be executed as shell.
5
+ set -uo pipefail
6
+
7
+ args=(--format md)
8
+ while IFS= read -r path; do
9
+ [ -z "$path" ] && continue
10
+ args+=(--source "$path")
11
+ done <<< "$SOOTH_SOURCE"
12
+
13
+ args+=(--text "$SOOTH_TEXT")
14
+ if [ -n "${SOOTH_CONFIDENCE:-}" ]; then
15
+ args+=(--confidence "$SOOTH_CONFIDENCE")
16
+ fi
17
+
18
+ report="${RUNNER_TEMP:-/tmp}/sooth-report.md"
19
+ sooth "${args[@]}" -o "$report"
20
+ status=$?
21
+
22
+ if [ -f "$report" ]; then
23
+ cat "$report"
24
+ if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
25
+ cat "$report" >> "$GITHUB_STEP_SUMMARY"
26
+ fi
27
+ fi
28
+
29
+ exit "$status"
sooth-0.2.3/action.yml ADDED
@@ -0,0 +1,35 @@
1
+ name: "Sooth"
2
+ description: "Claim-by-claim verification of AI-generated text against source material. Fails CI on hallucinations."
3
+ author: "Naufal Hilmiaji"
4
+ branding:
5
+ icon: "check-circle"
6
+ color: "green"
7
+
8
+ inputs:
9
+ source:
10
+ description: "Ground-truth source file(s). Repeat by separating paths with newlines."
11
+ required: true
12
+ text:
13
+ description: "AI-generated draft to check."
14
+ required: true
15
+ confidence:
16
+ description: "REVIEW below this confidence."
17
+ required: false
18
+ default: "0.7"
19
+
20
+ runs:
21
+ using: "composite"
22
+ steps:
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.12"
26
+ - name: Install sooth
27
+ shell: bash
28
+ run: python -m pip install --quiet sooth
29
+ - name: Verify draft
30
+ shell: bash
31
+ env:
32
+ SOOTH_SOURCE: ${{ inputs.source }}
33
+ SOOTH_TEXT: ${{ inputs.text }}
34
+ SOOTH_CONFIDENCE: ${{ inputs.confidence }}
35
+ run: bash "$GITHUB_ACTION_PATH/action.sh"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sooth"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "Verify AI-generated text against source material. Claim-by-claim trust reports powered by Jev (TypeSafe System One)."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -9,9 +9,11 @@ from datetime import UTC, datetime
9
9
  from pathlib import Path
10
10
 
11
11
  from sooth.claims import split_claims
12
- from sooth.report import exit_code, log_record, render_markdown, render_plain
12
+ from sooth.report import exit_code, log_record, render_markdown, render_plain, verdicts_from_record
13
13
  from sooth.verify import DEFAULT_THRESHOLD, VerifyError, verify_claims
14
14
 
15
+ DEMO_RECORD = Path(__file__).with_name("demo.json")
16
+
15
17
 
16
18
  def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
17
19
  p = argparse.ArgumentParser(
@@ -19,9 +21,11 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
19
21
  description="Verify AI-generated text against source material. "
20
22
  "Each claim gets PASS / FAIL / REVIEW with calibrated probabilities.",
21
23
  )
22
- p.add_argument("--source", action="append", required=True, metavar="FILE",
24
+ p.add_argument("command", nargs="?", choices=["demo"],
25
+ help="run the offline demo against a bundled recorded run (no API key)")
26
+ p.add_argument("--source", action="append", metavar="FILE",
23
27
  help="ground-truth source file (repeatable)")
24
- p.add_argument("--text", required=True, metavar="FILE", help="AI-generated draft to check")
28
+ p.add_argument("--text", metavar="FILE", help="AI-generated draft to check")
25
29
  p.add_argument("--confidence", type=float, default=DEFAULT_THRESHOLD, metavar="T",
26
30
  help=f"REVIEW below this confidence (default {DEFAULT_THRESHOLD})")
27
31
  p.add_argument("--format", choices=["md", "plain"], default="md", help="report format")
@@ -30,8 +34,29 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
30
34
  return p.parse_args(argv)
31
35
 
32
36
 
37
+ def run_demo(fmt: str) -> int:
38
+ """Replay a bundled judgment trace through the real report code. No network."""
39
+ record = json.loads(DEMO_RECORD.read_text(encoding="utf-8"))
40
+ print(
41
+ "demo: replaying a recorded run "
42
+ f"({record['draft']} vs {', '.join(record['sources'])}) — no API key needed",
43
+ file=sys.stderr,
44
+ )
45
+ verdicts = verdicts_from_record(record)
46
+ report = (render_markdown if fmt == "md" else render_plain)(
47
+ verdicts, threshold=record["threshold"]
48
+ )
49
+ print(report, end="")
50
+ return exit_code(verdicts)
51
+
52
+
33
53
  def main(argv: list[str] | None = None) -> int:
34
54
  args = parse_args(argv)
55
+ if args.command == "demo":
56
+ return run_demo(args.format)
57
+ if not args.source or not args.text:
58
+ print("error: --source and --text are required (or try: sooth demo)", file=sys.stderr)
59
+ return 3
35
60
  try:
36
61
  sources = [(path, Path(path).read_text(encoding="utf-8")) for path in args.source]
37
62
  draft = Path(args.text).read_text(encoding="utf-8")
@@ -0,0 +1 @@
1
+ {"model": "jev-1.13.0", "threshold": 0.7, "sources": ["examples/news-1.md"], "draft": "examples/news-1-evil.md", "usage": {"input_tokens": 11111, "output_tokens": 1107}, "results": [{"id": "c1", "text": "Aturan baru BEI menurunkan batas bawah harga saham menjadi Rp 1 per saham mulai 28 September 2026.", "line": 3, "kind": "PASS", "p_checkable": 0.98, "choice": "supports", "probabilities": {"supports": 1.0, "not_found": 0.0, "contradicts": 0.0}, "confidence": 1.0, "details_p": 0.98, "missing_numbers": [], "evidence": {"id": "s1", "text": "Saham-saham Grup Bakrie kembali menjadi sorotan menjelang pemberlakuan aturan baru Bursa Efek Indonesia (BEI) yang menurunkan batas bawah harga saham menjadi Rp 1 per saham mulai 28 September 2026.", "line": 3, "source": "examples/news-1.md"}}, {"id": "c2", "text": "Menurut analis Bumiputera Sekuritas, aturan ini berlaku untuk seluruh emiten di BEI.", "line": 5, "kind": "PASS", "p_checkable": 0.96, "choice": "supports", "probabilities": {"supports": 1.0, "not_found": 0.0, "contradicts": 0.0}, "confidence": 1.0, "details_p": 0.82, "missing_numbers": [], "evidence": {"id": "s9", "text": "Ia menyebut aturan ini berlaku untuk seluruh saham di BEI, bukan khusus Grup Bakrie.", "line": 7, "source": "examples/news-1.md"}}, {"id": "c3", "text": "BNBR baru menuntaskan rights issue bernilai besar di harga Rp 33.", "line": 7, "kind": "FAIL", "p_checkable": 0.93, "choice": "contradicts", "probabilities": {"supports": 0.0, "not_found": 0.0, "contradicts": 1.0}, "confidence": 1.0, "details_p": 0.01, "missing_numbers": ["33"], "evidence": {"id": "s47", "text": "Saham ini juga baru menyelesaikan rights issue dalam jumlah besar di harga Rp 53.", "line": 25, "source": "examples/news-1.md"}}, {"id": "c4", "text": "Saham VIVA disuspensi di harga Rp 50.", "line": 9, "kind": "FAIL", "p_checkable": 0.92, "choice": "contradicts", "probabilities": {"supports": 0.0, "not_found": 0.0, "contradicts": 1.0}, "confidence": 1.0, "details_p": 0.02, "missing_numbers": [], "evidence": null}, {"id": "c5", "text": "BUMI hanya perlu turun sekitar 10% untuk menyentuh level Rp 50.", "line": 11, "kind": "FAIL", "p_checkable": 0.92, "choice": "contradicts", "probabilities": {"supports": 0.0, "not_found": 0.0, "contradicts": 1.0}, "confidence": 1.0, "details_p": 0.01, "missing_numbers": ["10"], "evidence": {"id": "s40", "text": "Adapun PT Bumi Resources Tbk (BUMI) di sekitar Rp192 harus turun sekitar 74%, PT Darma Henwa Tbk (DEWA) sekitar 86%, PT Bumi Resources Minerals Tbk (BRMS) sekitar 93%, dan PT Energi Mega Persada Tbk (ENRG) lebih dari 96% untuk turun ke level Rp50.", "line": 23, "source": "examples/news-1.md"}}, {"id": "c6", "text": "BEI juga menetapkan batas atas harga saham Rp 5.000 per saham di pasar reguler.", "line": 13, "kind": "REVIEW", "p_checkable": 0.57, "choice": "not_found", "probabilities": {"supports": 0.0, "not_found": 1.0, "contradicts": 0.0}, "confidence": 1.0, "details_p": 0.02, "missing_numbers": ["5.000"], "evidence": null}, {"id": "c7", "text": "Para investor ritel sangat senang dengan aturan baru ini.", "line": 15, "kind": "UNCHECKABLE", "p_checkable": 0.25, "choice": "not_found", "probabilities": {"supports": 0.0, "not_found": 1.0, "contradicts": 0.0}, "confidence": 1.0, "details_p": 0.02, "missing_numbers": [], "evidence": null}], "ts": "2026-09-25T09:59:57+00:00"}
@@ -123,3 +123,27 @@ def log_record(result: VerifyResult, threshold: float, sources: list[str], draft
123
123
  for v in result.verdicts
124
124
  ],
125
125
  }
126
+
127
+
128
+ def verdicts_from_record(record: dict) -> list[Verdict]:
129
+ """Inverse of log_record's `results` — replay a recorded run without the API."""
130
+ out = []
131
+ for r in record["results"]:
132
+ ev = r.get("evidence")
133
+ out.append(Verdict(
134
+ claim_id=r["id"],
135
+ claim_text=r["text"],
136
+ line=r["line"],
137
+ kind=r["kind"],
138
+ p_checkable=r.get("p_checkable"),
139
+ choice=r.get("choice"),
140
+ probabilities=r.get("probabilities") or {},
141
+ confidence=r.get("confidence"),
142
+ details_p=r.get("details_p"),
143
+ missing_numbers=tuple(r.get("missing_numbers") or ()),
144
+ evidence_id=ev.get("id") if ev else None,
145
+ evidence_text=ev.get("text") if ev else None,
146
+ evidence_line=ev.get("line") if ev else None,
147
+ evidence_source=ev.get("source") if ev else None,
148
+ ))
149
+ return out
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bash
2
+ # Arg-wiring checks for action.sh. No network — a fake `sooth` records its argv.
3
+ # Run: bash tests/test_action.sh
4
+ set -uo pipefail
5
+
6
+ here="$(cd "$(dirname "$0")" && pwd)"
7
+ tmp="$(mktemp -d)"
8
+ trap 'rm -rf "$tmp"' EXIT
9
+
10
+ write_fake() {
11
+ cat > "$tmp/sooth" <<'EOF'
12
+ #!/usr/bin/env bash
13
+ printf '%s\n' "$@" > "$ARGV_FILE"
14
+ prev=""
15
+ for a in "$@"; do
16
+ if [ "$prev" = "-o" ]; then echo "# report" > "$a"; fi
17
+ prev="$a"
18
+ done
19
+ exit "${FAKE_STATUS:-0}"
20
+ EOF
21
+ chmod +x "$tmp/sooth"
22
+ }
23
+
24
+ # argv as one line, plus the same with the dynamic "-o <report>" tail dropped
25
+ argv_full() { tr '\n' ' ' < "$ARGV_FILE"; }
26
+ args_only() { argv_full | sed 's/ -o [^ ]*//'; }
27
+
28
+ export PATH="$tmp:$PATH"
29
+ export ARGV_FILE="$tmp/argv"
30
+ export RUNNER_TEMP="$tmp"
31
+ export SOOTH_TEXT="draft.md"
32
+ export SOOTH_CONFIDENCE="0.75"
33
+ unset GITHUB_STEP_SUMMARY 2>/dev/null || true
34
+
35
+ fail() { echo "FAIL: $*" >&2; exit 1; }
36
+
37
+ write_fake
38
+
39
+ # single source
40
+ export SOOTH_SOURCE="policy.md"
41
+ bash "$here/../action.sh" >/dev/null || fail "single source: exit $?"
42
+ got="$(args_only)"
43
+ want="--format md --source policy.md --text draft.md --confidence 0.75 "
44
+ [ "$got" = "$want" ] || fail "single source argv: got [$got] want [$want]"
45
+
46
+ # multiple sources, newline-separated, blanks ignored
47
+ export SOOTH_SOURCE="a.md
48
+ b.md
49
+
50
+ c.md"
51
+ bash "$here/../action.sh" >/dev/null || fail "multi source: exit $?"
52
+ got="$(args_only)"
53
+ want="--format md --source a.md --source b.md --source c.md --text draft.md --confidence 0.75 "
54
+ [ "$got" = "$want" ] || fail "multi source argv: got [$got] want [$want]"
55
+
56
+ # confidence is optional
57
+ unset SOOTH_CONFIDENCE
58
+ export SOOTH_SOURCE="policy.md"
59
+ bash "$here/../action.sh" >/dev/null || fail "no confidence: exit $?"
60
+ got="$(args_only)"
61
+ want="--format md --source policy.md --text draft.md "
62
+ [ "$got" = "$want" ] || fail "no confidence argv: got [$got] want [$want]"
63
+
64
+ # a FAIL from sooth must fail the step (CI gate), and the report still prints
65
+ export SOOTH_CONFIDENCE="0.7"
66
+ export SOOTH_SOURCE="policy.md"
67
+ export FAKE_STATUS=1
68
+ out="$(bash "$here/../action.sh")"
69
+ status=$?
70
+ [ "$status" -eq 1 ] || fail "exit code not propagated: got $status"
71
+ [ -n "$out" ] || fail "report not printed on failure"
72
+
73
+ # hostile input must arrive as a literal argument and never execute
74
+ export FAKE_STATUS=0
75
+ export SOOTH_TEXT='draft.md; echo PWNED'
76
+ out="$(bash "$here/../action.sh")" || fail "injection: exit $?"
77
+ case "$out" in
78
+ *PWNED*) fail "input was executed as shell" ;;
79
+ esac
80
+ full="$(argv_full)"
81
+ case "$full" in
82
+ *"--text draft.md; echo PWNED "*) : ;;
83
+ *) fail "injection argv: got [$full]" ;;
84
+ esac
85
+
86
+ echo "ok — action.sh arg wiring (5 checks)"
@@ -12,7 +12,7 @@ from pathlib import Path
12
12
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) # direct-run without install
13
13
 
14
14
  from sooth.claims import split_claims, split_segments
15
- from sooth.report import bar, exit_code, log_record, render_markdown
15
+ from sooth.report import bar, exit_code, log_record, render_markdown, verdicts_from_record
16
16
  from sooth.verify import (
17
17
  FAIL,
18
18
  PASS,
@@ -229,6 +229,25 @@ def test_log_record_roundtrip_no_secret():
229
229
  assert "api_key" not in json.dumps(parsed).lower()
230
230
 
231
231
 
232
+ def test_verdicts_from_record_roundtrip():
233
+ """log_record → JSON → verdicts_from_record is lossless (demo replay path)."""
234
+ result = VerifyResult(
235
+ verdicts=[
236
+ v(PASS, confidence=0.9, probabilities={"supports": 0.9}, details_p=0.9,
237
+ evidence_id="s1", evidence_text="Source line.", evidence_line=3,
238
+ evidence_source="a.md"),
239
+ v(FAIL, claim_id="c2", confidence=1.0, probabilities={"contradicts": 1.0},
240
+ missing_numbers=("33",)),
241
+ ],
242
+ model="jev-1.13.0",
243
+ usage={"input_tokens": 10, "output_tokens": 1},
244
+ )
245
+ back = verdicts_from_record(json.loads(json.dumps(log_record(result, 0.7, ["a.md"], "d.md"))))
246
+ assert [(x.claim_id, x.kind, x.confidence) for x in back] == [("c1", PASS, 0.9), ("c2", FAIL, 1.0)]
247
+ assert back[0].evidence_text == "Source line." and back[0].evidence_line == 3
248
+ assert back[1].missing_numbers == ("33",) and back[1].evidence_text is None
249
+
250
+
232
251
  if __name__ == "__main__":
233
252
  fns = [f for name, f in sorted(globals().items()) if name.startswith("test_") and callable(f)]
234
253
  for fn in fns:
sooth-0.2.1/PKG-INFO DELETED
@@ -1,110 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: sooth
3
- Version: 0.2.1
4
- Summary: Verify AI-generated text against source material. Claim-by-claim trust reports powered by Jev (TypeSafe System One).
5
- Project-URL: Homepage, https://github.com/naufalhilmiaji/sooth
6
- Project-URL: Documentation, https://github.com/naufalhilmiaji/sooth/tree/main/docs
7
- Author-email: Naufal Hilmiaji <nhilmiaji@gmail.com>
8
- License: MIT
9
- License-File: LICENSE
10
- Keywords: ai,jev,llm,trust,typesafe,verification
11
- Classifier: Development Status :: 3 - Alpha
12
- Classifier: Environment :: Console
13
- Classifier: Intended Audience :: Developers
14
- Classifier: License :: OSI Approved :: MIT License
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Topic :: Software Development :: Quality Assurance
17
- Requires-Python: >=3.11
18
- Requires-Dist: typesafe-sdk
19
- Provides-Extra: dev
20
- Requires-Dist: pytest; extra == 'dev'
21
- Requires-Dist: ruff; extra == 'dev'
22
- Description-Content-Type: text/markdown
23
-
24
- # Sooth
25
-
26
- Verify AI-generated text against source material. Claim-by-claim **PASS / FAIL / REVIEW**, with calibrated probabilities you can act on in CI.
27
-
28
- Built on [Jev](https://docs.typesafe.ai) (TypeSafe System One) — typed judgments and probabilities instead of generated prose. Every verdict shows its probability distribution, and the combination rules are plain code you can read. No black box.
29
-
30
- ```
31
- sooth --source policy.md --text draft-reply.md
32
- ```
33
-
34
- Real output (planted errors in the draft vs a news source):
35
-
36
- ```
37
- # Sooth
38
-
39
- **PASS 2 · FAIL 3 · REVIEW 1 · UNCHECKABLE 1** — threshold 0.70
40
-
41
- | # | Claim | Verdict | P | Why (P distribution) | Evidence |
42
- |---|-------|---------|---|----------------------|----------|
43
- | 3 | BNBR baru menuntaskan rights issue bernilai besar di harga Rp 33. | ❌ FAIL | ████████ 1.00 | contradicts 1.00 / not_found 0.00 / supports 0.00 · details 0.01 · missing #s: 33 | `examples/news-1.md:25` "Saham ini juga baru menyelesaikan rights issue dalam juml…" |
44
- | 5 | BUMI hanya perlu turun sekitar 10% untuk menyentuh level Rp 50. | ❌ FAIL | ████████ 1.00 | contradicts 1.00 / not_found 0.00 / supports 0.00 · details 0.01 · missing #s: 10 | `examples/news-1.md:23` "Adapun PT Bumi Resources Tbk (BUMI) di sekitar Rp192 haru…" |
45
- | 6 | BEI juga menetapkan batas atas harga saham Rp 5.000 per saham. | ⚠️ REVIEW | ████████ 1.00 | contradicts 0.00 / not_found 1.00 / supports 0.00 · details 0.02 · missing #s: 5.000 | |
46
- | 7 | Para investor ritel sangat senang dengan aturan baru ini. | ➖ UNCHECKABLE | ██░░░░░░ 0.24 | P(checkable)=0.24 | |
47
- ```
48
-
49
- ## Why
50
-
51
- AI writes fast, nobody checks. Claims ship wrong. Sooth checks each claim against the evidence you give it — and when it is unsure, it says `REVIEW` instead of guessing.
52
-
53
- ## Install
54
-
55
- ```bash
56
- pip install sooth # or: pip install -e ".[dev]" from source
57
- export TYPESAFE_API_KEY=... # get one at console.typesafe.ai
58
- ```
59
-
60
- ## Usage
61
-
62
- ```bash
63
- sooth --source docs/policy.md --source tickets/t123.md --text draft-reply.md
64
-
65
- # CI-friendly exit codes
66
- sooth --source policy.md --text draft.md --format plain
67
- # 0 = clean · 1 = any FAIL · 2 = any REVIEW · 3 = usage/config error
68
-
69
- # Options
70
- # --confidence T REVIEW below this confidence (default 0.7)
71
- # --format md|plain
72
- # -o FILE write report to file
73
- # --log FILE append full judgment trace (one JSONL line per run)
74
- ```
75
-
76
- ## How it works
77
-
78
- 1. Draft is split into claims (one sentence each).
79
- 2. Each claim gets three questions to Jev, all fanned out in parallel batches: *is this checkable?*, *does the source support it?* (`supports` / `contradicts` / `not_found`), and *do all details match exactly?*
80
- 3. Verdicts are mapped in code: uncheckable → `UNCHECKABLE`; low confidence → `REVIEW`; then `PASS` / `FAIL`. Safeguards demote `PASS` to `REVIEW` when details drift or claim numbers are absent from the source (checked in plain code).
81
- 4. The report shows the full probability distribution per claim — not just a label.
82
-
83
- The decision logic lives in [`src/sooth/verify.py`](src/sooth/verify.py) in a dozen readable lines. Change thresholds and rules there, not in prompts.
84
-
85
- ## Known limits (alpha)
86
-
87
- - Safeguards catch most drift and smuggling: claim numbers absent from the source demote `PASS` to `REVIEW`, and a detail-match gate flags altered hedges ("about 74%" → "over 74%"). Not perfect — read the `Why` column before trusting a verdict.
88
- - Derived numbers (totals, values computed outside the source) look "missing" and land in `REVIEW`.
89
- - Source + questions must fit ~64k tokens — split long documents yourself.
90
-
91
- ## Development
92
-
93
- ```bash
94
- pip install -e ".[dev]"
95
- python3 tests/test_core.py # pure checks, no network
96
- pytest # same suite
97
- bash tests/smoke.sh # live smoke (needs TYPESAFE_API_KEY)
98
- PYTHONPATH=src python3 tests/calibrate.py # live calibration, 30 labeled claims
99
- ```
100
-
101
- Docs: [PRD](docs/PRD.md) · [Design](docs/DESIGN.md) · [Testing](docs/TESTING.md)
102
-
103
- ## Roadmap
104
-
105
- - v0.2 shipped: source-span evidence (the exact source line behind each verdict, shown in the report)
106
- - later: hosted web app — paste UI, history, team review queues
107
-
108
- ## License
109
-
110
- MIT © Naufal Hilmiaji. Powered by [TypeSafe](https://typesafe.ai) / Jev.
sooth-0.2.1/README.md DELETED
@@ -1,87 +0,0 @@
1
- # Sooth
2
-
3
- Verify AI-generated text against source material. Claim-by-claim **PASS / FAIL / REVIEW**, with calibrated probabilities you can act on in CI.
4
-
5
- Built on [Jev](https://docs.typesafe.ai) (TypeSafe System One) — typed judgments and probabilities instead of generated prose. Every verdict shows its probability distribution, and the combination rules are plain code you can read. No black box.
6
-
7
- ```
8
- sooth --source policy.md --text draft-reply.md
9
- ```
10
-
11
- Real output (planted errors in the draft vs a news source):
12
-
13
- ```
14
- # Sooth
15
-
16
- **PASS 2 · FAIL 3 · REVIEW 1 · UNCHECKABLE 1** — threshold 0.70
17
-
18
- | # | Claim | Verdict | P | Why (P distribution) | Evidence |
19
- |---|-------|---------|---|----------------------|----------|
20
- | 3 | BNBR baru menuntaskan rights issue bernilai besar di harga Rp 33. | ❌ FAIL | ████████ 1.00 | contradicts 1.00 / not_found 0.00 / supports 0.00 · details 0.01 · missing #s: 33 | `examples/news-1.md:25` "Saham ini juga baru menyelesaikan rights issue dalam juml…" |
21
- | 5 | BUMI hanya perlu turun sekitar 10% untuk menyentuh level Rp 50. | ❌ FAIL | ████████ 1.00 | contradicts 1.00 / not_found 0.00 / supports 0.00 · details 0.01 · missing #s: 10 | `examples/news-1.md:23` "Adapun PT Bumi Resources Tbk (BUMI) di sekitar Rp192 haru…" |
22
- | 6 | BEI juga menetapkan batas atas harga saham Rp 5.000 per saham. | ⚠️ REVIEW | ████████ 1.00 | contradicts 0.00 / not_found 1.00 / supports 0.00 · details 0.02 · missing #s: 5.000 | |
23
- | 7 | Para investor ritel sangat senang dengan aturan baru ini. | ➖ UNCHECKABLE | ██░░░░░░ 0.24 | P(checkable)=0.24 | |
24
- ```
25
-
26
- ## Why
27
-
28
- AI writes fast, nobody checks. Claims ship wrong. Sooth checks each claim against the evidence you give it — and when it is unsure, it says `REVIEW` instead of guessing.
29
-
30
- ## Install
31
-
32
- ```bash
33
- pip install sooth # or: pip install -e ".[dev]" from source
34
- export TYPESAFE_API_KEY=... # get one at console.typesafe.ai
35
- ```
36
-
37
- ## Usage
38
-
39
- ```bash
40
- sooth --source docs/policy.md --source tickets/t123.md --text draft-reply.md
41
-
42
- # CI-friendly exit codes
43
- sooth --source policy.md --text draft.md --format plain
44
- # 0 = clean · 1 = any FAIL · 2 = any REVIEW · 3 = usage/config error
45
-
46
- # Options
47
- # --confidence T REVIEW below this confidence (default 0.7)
48
- # --format md|plain
49
- # -o FILE write report to file
50
- # --log FILE append full judgment trace (one JSONL line per run)
51
- ```
52
-
53
- ## How it works
54
-
55
- 1. Draft is split into claims (one sentence each).
56
- 2. Each claim gets three questions to Jev, all fanned out in parallel batches: *is this checkable?*, *does the source support it?* (`supports` / `contradicts` / `not_found`), and *do all details match exactly?*
57
- 3. Verdicts are mapped in code: uncheckable → `UNCHECKABLE`; low confidence → `REVIEW`; then `PASS` / `FAIL`. Safeguards demote `PASS` to `REVIEW` when details drift or claim numbers are absent from the source (checked in plain code).
58
- 4. The report shows the full probability distribution per claim — not just a label.
59
-
60
- The decision logic lives in [`src/sooth/verify.py`](src/sooth/verify.py) in a dozen readable lines. Change thresholds and rules there, not in prompts.
61
-
62
- ## Known limits (alpha)
63
-
64
- - Safeguards catch most drift and smuggling: claim numbers absent from the source demote `PASS` to `REVIEW`, and a detail-match gate flags altered hedges ("about 74%" → "over 74%"). Not perfect — read the `Why` column before trusting a verdict.
65
- - Derived numbers (totals, values computed outside the source) look "missing" and land in `REVIEW`.
66
- - Source + questions must fit ~64k tokens — split long documents yourself.
67
-
68
- ## Development
69
-
70
- ```bash
71
- pip install -e ".[dev]"
72
- python3 tests/test_core.py # pure checks, no network
73
- pytest # same suite
74
- bash tests/smoke.sh # live smoke (needs TYPESAFE_API_KEY)
75
- PYTHONPATH=src python3 tests/calibrate.py # live calibration, 30 labeled claims
76
- ```
77
-
78
- Docs: [PRD](docs/PRD.md) · [Design](docs/DESIGN.md) · [Testing](docs/TESTING.md)
79
-
80
- ## Roadmap
81
-
82
- - v0.2 shipped: source-span evidence (the exact source line behind each verdict, shown in the report)
83
- - later: hosted web app — paste UI, history, team review queues
84
-
85
- ## License
86
-
87
- MIT © Naufal Hilmiaji. Powered by [TypeSafe](https://typesafe.ai) / Jev.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes