stilltrue 0.7.0 → 0.8.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 — 2026-10-28
4
+
5
+ **A list is not a denial.**
6
+
7
+ Asked whether `apple.com/apple-card` supports "Apple Card works with Windows
8
+ PCs", the judge ruled `not_supported`, reasoning:
9
+
10
+ > by listing only Apple devices as having the technology, it implies
11
+ > exclusion of other platforms like Windows.
12
+
13
+ The page never mentions Windows, so the right answer is `cant_tell`. The
14
+ contradiction guard could not catch it, because the quoted sentence was real.
15
+ It was about Apple Pay, though, not Apple Card. Two lines were added to the
16
+ shipped judge prompt:
17
+
18
+ - A source that names the cases something applies to has not ruled out the
19
+ ones it leaves unnamed, unless it says so in words ("only", "not available
20
+ on").
21
+ - A contradicting sentence must be about the same subject as the claim.
22
+
23
+ Measured on qwen3.6-35b-a3b, the weakest engine behind the demo:
24
+
25
+ | Set | 0.7.1 | 0.8.0 |
26
+ |---|---|---|
27
+ | Apple: Windows PCs, 3 runs | 0/3 | 3/3 |
28
+ | Apple: 10% Daily Cash, 3 runs | 3/3 | 3/3 |
29
+ | Controls | 4/4 | 4/4 |
30
+ | 21-case comparison set | 21/21 | 21/21 |
31
+ | False supported, all sets | 0 | 0 |
32
+
33
+ The box is not deterministic at temperature 0. On a second sample 0.8.0
34
+ scored 20/21 on the comparison set, missing "Visitors must create an
35
+ account". Rerun five times on each prompt, that case scored 4/5 on 0.7.1
36
+ and 5/5 on 0.8.0, so the miss is run-to-run variance rather than a
37
+ regression.
38
+
39
+ Known gap: two synthetic list cases still come back `not_supported` on that
40
+ model, which reads "issued at Duluth, Norcross and Snellville" as "issued
41
+ only at" them. Generating reasoning before the verdict fixed those two but
42
+ broke the real Apple case 3/3, so that change did not ship.
43
+
44
+ ## 0.7.1 — 2026-09-13
45
+
46
+ Docs restructure, no behavior changes except one message:
47
+
48
+ - README cut from 408 lines to ~190. A fresh-eyes pass (install → first
49
+ green check in three minutes) showed setup was never the problem — the
50
+ document performed difficulty the tool doesn't have. The drift
51
+ quickstart, which tested perfect, is untouched.
52
+ - New `docs/CONFIG.md`: the reference shelf (config fields, compare modes,
53
+ helpers, CLI flags), moved verbatim out of the tutorial path.
54
+ - `docs/VERIFY.md` gained the `supportStage` section from the README, so
55
+ the ready-made stage lives beside the judge walkthrough it builds on.
56
+ - `stilltrue init` now prints the full MARKERS.md URL — npm users have no
57
+ repo checkout for a relative path to resolve against.
58
+
3
59
  ## 0.7.0
4
60
 
5
61
  **A different value now counts as a contradiction.**
package/README.md CHANGED
@@ -71,7 +71,7 @@ Office", and "12 locations" all still appear on it.* Swap in your own URL
71
71
  and your own facts — that's a working check, no programming required.
72
72
 
73
73
  Every other field, compare mode, and helper is in
74
- [Configuration](#configuration) below. Choosing *what* to assert is the
74
+ [the configuration reference](docs/CONFIG.md). Choosing *what* to assert is the
75
75
  judgment call — surnames beat full names, `"143 schools"` beats a bare
76
76
  `"143"`. The short guide: [docs/MARKERS.md](docs/MARKERS.md).
77
77
 
@@ -115,172 +115,6 @@ You'll see one line per check:
115
115
  This only *warns*. An unreachable site doesn't mean your facts are wrong,
116
116
  and an alarm that cries wolf gets ignored — so outages never fail the build.
117
117
 
118
- ## Configuration
119
-
120
- stilltrue loads a config file from the current directory. The first of
121
- these that exists wins:
122
-
123
- `stilltrue.config.ts` · `.mts` · `.js` · `.mjs` · `.cjs`
124
-
125
- (`npx stilltrue init` writes `.mjs`.) Point at a file anywhere with
126
- `stilltrue drift --config path/to/file`.
127
-
128
- Wrap the export with `defineStilltrue` so TypeScript can check it:
129
-
130
- ```js
131
- import { defineStilltrue, corpus, json, surname } from 'stilltrue';
132
-
133
- export default defineStilltrue({
134
- drift: [ /* one entry per fact-set */ ],
135
- verify: { /* optional named pipelines — library use, see below */ },
136
- });
137
- ```
138
-
139
- ### Drift checks
140
-
141
- Each `drift` entry is one curated fact-set vs its live source.
142
-
143
- | Field | Required | Default | What it is |
144
- |---|---|---|---|
145
- | `name` | yes | — | Short id. Shows up in the terminal, `--only`, and the report. |
146
- | `source` | yes | — | `async (ctx) => actual`. Fetch/parse the live authority. **Throw** if the source is unreachable — that is outcome `error`, never `rot`. |
147
- | `expect` | yes | — | The curated facts. A value, or `async (ctx) => expected` (e.g. `json(...)`). A throw here is also `error` (broken config, not rot). |
148
- | `compare` | no | `'deep-equal'` | How to compare `expect` against what `source` returned. |
149
-
150
- `source` and function-form `expect` receive `ctx`:
151
-
152
- | | |
153
- |---|---|
154
- | `ctx.warn(message)` | Record a non-fatal note (one of several pages down, odd markup). Shows as a warning; does not fail the run. |
155
- | `ctx.configDir` | Absolute directory of the config file. Data paths resolve from here. |
156
-
157
- ### `compare`
158
-
159
- **`'contains-all'`** — `expect` is a list of marker strings; `source` must
160
- return a text corpus (what `corpus()` returns). Every marker must appear
161
- somewhere in that text, case-insensitively. Extra text on the page is
162
- ignored. This is the webpage-watch check.
163
-
164
- **`'deep-equal'`** — structural equality. Extra keys, missing keys, and
165
- value mismatches all rot, with pathed messages
166
- (`$.members[2].name: expected "Smith", source has "Jones"`). Use this when
167
- `source()` already returns structured data that should match `expect`
168
- exactly.
169
-
170
- **A function** — `(expected, actual) => string[]`. Return `[]` to pass;
171
- each string is one mismatch (outcome `rot`). Throw if the shapes are
172
- unusable (outcome `error`). Use this for numeric tolerance, order-insensitive
173
- lists, or any comparison that isn't "markers in a page" or "these two
174
- objects are identical."
175
-
176
- ```js
177
- compare: (expected, actual) =>
178
- expected.rate === actual.rate
179
- ? []
180
- : [`tax rate ${expected.rate} → ${actual.rate}`],
181
- ```
182
-
183
- Surnames are a *marker choice*, not a compare mode: derive them from your
184
- data with `surname()` and use `'contains-all'`.
185
-
186
- ### Helpers
187
-
188
- These are the built-in `source` / `expect` builders. You can always write
189
- your own functions instead.
190
-
191
- **`corpus(urls, options?)`** — fetch pages, strip HTML to text,
192
- concatenate. Individual unreachable pages call `ctx.warn`; if *every* page
193
- fails, it throws (`error`). Pair with `'contains-all'`.
194
-
195
- | Option | Default | What it is |
196
- |---|---|---|
197
- | `timeoutMs` | `20000` | Per-page fetch timeout, milliseconds. |
198
- | `userAgent` | `stilltrue drift check (github.com/sg-bfoster/stilltrue)` | Sent on each request so operators can identify the check. |
199
-
200
- ```js
201
- source: corpus(
202
- ['https://example.org/about', 'https://example.org/board'],
203
- { timeoutMs: 15_000, userAgent: 'my-app drift check' },
204
- ),
205
- ```
206
-
207
- **`json(path, derive?)`** — read a JSON file relative to the config file.
208
- With no `derive`, the parsed value *is* the expectation (typical with
209
- `'deep-equal'`). With `derive`, you map that value into whatever `compare`
210
- needs — usually a marker list:
211
-
212
- ```js
213
- expect: json('./data/board.json', (data) =>
214
- data.members.map((m) => surname(m.name)),
215
- ),
216
- ```
217
-
218
- **`surname(fullName)`** — last token of a name, with leading honorifics
219
- (`Dr.`, `Mr.`, `Mrs.`, `Ms.`) and trailing generational suffixes (`Jr.`,
220
- `Sr.`, `II`–`V`) stripped. `"Dr. Jane Smith III"` → `"Smith"`.
221
-
222
- **`htmlToText(html)`** — the same tag-stripping `corpus()` uses. For a
223
- custom `source` that already has HTML in hand.
224
-
225
- ### Custom `source`
226
-
227
- A throw is an outage; a return value is data to compare. Warn for partial
228
- trouble:
229
-
230
- ```js
231
- source: async (ctx) => {
232
- const res = await fetch('https://example.org/api/rates');
233
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
234
- const data = await res.json();
235
- if (!data.updatedAt) ctx.warn('response missing updatedAt');
236
- return data;
237
- },
238
- expect: json('./data/rates.json'),
239
- // compare defaults to 'deep-equal'
240
- ```
241
-
242
- ### `verify` pipelines
243
-
244
- `verify` is an optional map of named stage lists, for use from your app
245
- (`runVerify(config.verify.answers, draft)`). There is no `stilltrue verify`
246
- CLI command — verification runs in the request path, not in CI.
247
-
248
- Each stage:
249
-
250
- | Field | Required | Default | What it is |
251
- |---|---|---|---|
252
- | `name` | yes | — | Id of this checkpoint. The first rejection reports this name. |
253
- | `check` | yes | — | `(input, ctx) => { ok: true, value? } \| { ok: false, messages, revision? }`. |
254
- | `failPolicy` | no | `'open'` | What a **thrown** stage means. `'open'`: warn and continue (a judge outage must not block the app). `'closed'`: treat the throw as a rejection. |
255
- | `timeoutMs` | no | none | Abort the stage after this many ms. The timeout follows `failPolicy`. |
256
- | `tier` | no | `'inline'` | `'inline'` always runs; `'ci'` runs only when you call `runVerify(..., { tier: 'ci' })`. |
257
-
258
- Stages run in order and stop at the first rejection. `zodStage(name, schema)`
259
- builds a parse stage from a Zod schema.
260
- `generateVerified({ generate, stages, attempts, tier })` is the generate →
261
- verify → retry loop (`attempts` defaults to `2`). See
262
- [For developers: `verify`](#for-developers-verify) below.
263
-
264
- ### CLI flags
265
-
266
- `stilltrue drift`:
267
-
268
- | Flag | What it does |
269
- |---|---|
270
- | `--config <path>` | Config file. Default: first `stilltrue.config.*` in the current directory. |
271
- | `--only <name,name>` | Run only these checks. |
272
- | `--json <path>` | Write the result array as JSON. |
273
- | `--record` | Append this run to history. |
274
- | `--history <path>` | History file for `--record`. Default: `.stilltrue/history.jsonl`. |
275
-
276
- `stilltrue report`:
277
-
278
- | Flag | What it does |
279
- |---|---|
280
- | `--history <path>` | History file to read. Default: `.stilltrue/history.jsonl`. |
281
- | `--out <path>` | HTML output. Default: `stilltrue-report.html`. |
282
- | `--title <text>` | Page `<title>` and heading. Default: `stilltrue drift report`. |
283
-
284
118
  ## Running it automatically every week
285
119
 
286
120
  If your project is on GitHub, add this file as
@@ -329,79 +163,29 @@ comparison stays mechanical.)
329
163
  ## For developers: `verify`
330
164
 
331
165
  `verify` is a library for teams shipping AI-generated answers: an ordered
332
- series of checkpoints (a schema parse, a rules check, an AI judge — your
333
- choice) that each answer must pass before publishing. Rejections say
334
- exactly which checkpoint failed and why, in a format you can feed back to
335
- the model for a retry. A broken checker never blocks your app — it fails
336
- open. See the [project docs](docs/BRIEF.md) and the typed API in
337
- `stilltrue`'s exports (`runVerify`, `zodStage`, `generateVerified`).
338
-
339
- **Bring your own judge — any provider, or none.** stilltrue ships no AI
340
- and never touches a model key: a checkpoint is just a function you supply,
341
- so the judge stage can call Gemini, OpenAI, Anthropic, a local model — or
342
- be a plain schema/rules check with no AI at all. Swapping providers means
343
- editing your function, never this package.
344
-
345
- ### The common case, in six lines
346
-
347
- Most people arrive wanting one thing: *does this source actually support
348
- this claim?* `supportStage` is that check, ready-made. You bring a function
349
- that calls your model; it brings the prompt, the response schema, and the
350
- guard.
351
-
352
- ```js
353
- import { runVerify, supportStage } from 'stilltrue';
354
-
355
- const stage = supportStage({
356
- // The only required option. Return the model's reply — parsed or raw JSON.
357
- judge: ({ system, user, schema }) => callMyModel({ system, user, schema }),
358
- });
359
-
360
- const result = await runVerify([stage], { claim, source });
361
- // result.ok → true when the source supports the claim
362
- // result.value → { verdict, reasoning, evidence, contradiction, guard }
363
- // result.messages → why it was rejected, ready for a retry
364
- ```
365
-
366
- `verdict` is one of `supported`, `not_supported` or `cant_tell`. Silence is
367
- not support: a source that never addresses the claim returns `cant_tell`,
368
- even when the claim happens to be true.
369
-
370
- **"Not supported" has to earn itself.** That verdict tells a reader the
371
- source *says otherwise*, which is the strongest thing a judge can assert
372
- and the one most worth being wrong about. So the stage requires the judge
373
- to quote the contradicting sentence, and checks that quote against the
374
- source in code. If it cannot point at real text, the verdict is downgraded
375
- to `cant_tell` and a warning explains why. The check only ever downgrades —
376
- a missing quote is evidence of nothing except a missing quote.
377
-
378
- That guard is not theoretical. Judged by a small local model, adding two
379
- lines to a source that never mentioned the claim flipped a verdict from
380
- `supported` to `not_supported` — three runs each way, so not noise — where
381
- a frontier model stayed stable. A package that promises engine-agnostic
382
- verification has to survive the weaker engine.
383
-
384
- Useful options, all defaulted: `minRun` (how much of the contradiction
385
- quote must match the source — 24 characters, measured on civic notices and
386
- fee tables, not universal), `requireContradiction: false` to switch the
387
- guard off, `treatCantTellAs: 'warn'` to pass unanswerable claims instead of
388
- rejecting them, and `system` to replace the prompt entirely. `SUPPORT_SYSTEM`
389
- and `SUPPORT_SCHEMA` are exported too, so a provider with constrained
390
- decoding can take the schema directly.
391
-
392
- ### Writing your own stages
393
-
394
- `supportStage` is one stage; a pipeline is a list of them, and any function
395
- of the right shape will do. **[docs/VERIFY.md](docs/VERIFY.md)** is the full
396
- walkthrough — the judging prompt explained, response mapping, retries, and
397
- the generate → verify → retry loop — for when the ready-made stage is not
398
- the shape you need.
399
-
400
- ## What about testing prompts?
401
-
402
- That's a different job with a great existing tool:
403
- [promptfoo](https://promptfoo.dev) tests your prompts; stilltrue tests your
404
- facts. How they fit together: [docs/GOLDEN.md](docs/GOLDEN.md).
166
+ series of checkpoints — a schema parse, a rules check, an AI judge, your
167
+ choice — that each answer must pass before publishing. Rejections say which
168
+ checkpoint failed and why, in a format you can feed back to the model for a
169
+ retry; a broken checker fails open rather than blocking your app. stilltrue
170
+ ships no AI and never touches a model key — the judge is a function you
171
+ supply, so swapping providers means editing your function, never this
172
+ package.
173
+
174
+ **[docs/VERIFY.md](docs/VERIFY.md) is the whole story**, including a
175
+ complete copy-paste judge (Gemini shown, OpenAI-compatible variant inline —
176
+ which also covers LM Studio and every hosted provider) and `supportStage`,
177
+ the ready-made "does this source support this claim?" check.
178
+
179
+ ## Reference
180
+
181
+ - **[Configuration](docs/CONFIG.md)** — every config field, compare mode,
182
+ helper, and CLI flag.
183
+ - **[Picking good markers](docs/MARKERS.md)** — the judgment calls
184
+ (`surnames beat full names`, `"143 schools"` beats `"143"`).
185
+ - **[Verify](docs/VERIFY.md)** — building a judge, writing stages,
186
+ `generateVerified`.
187
+ - **[Prompts vs facts](docs/GOLDEN.md)** — [promptfoo](https://promptfoo.dev)
188
+ tests your prompts; stilltrue tests your facts. How they fit together.
405
189
 
406
190
  ## License
407
191
 
package/dist/cli.js CHANGED
@@ -106,7 +106,7 @@ try {
106
106
  ' 1. Edit stilltrue.config.mjs — point source at the real page(s) your facts came from,\n' +
107
107
  ' and replace the example markers with your own facts (plain strings)\n' +
108
108
  ' 2. Run: npx stilltrue drift\n' +
109
- 'Picking good markers: docs/MARKERS.md in the stilltrue repo');
109
+ 'Picking good markers: https://github.com/sg-bfoster/stilltrue/blob/main/docs/MARKERS.md');
110
110
  }
111
111
  process.exit(0);
112
112
  break;
package/dist/support.d.ts CHANGED
@@ -101,6 +101,19 @@ export interface SupportStageOptions {
101
101
  * the obvious over-correction is ruling not_supported on a page that simply
102
102
  * lists several values for different cases.
103
103
  *
104
+ * The list and same-subject lines were added 2026-10-28, the mirror image of
105
+ * the 10% failure on the same page. Asked whether apple.com/apple-card supports
106
+ * "Apple Card works with Windows PCs", the judge ruled not_supported, quoting
107
+ * "Apple Pay ... built into iPhone, Apple Watch, iPad, and Mac" and reasoning
108
+ * that listing only Apple devices "implies exclusion". That is an argument from
109
+ * silence, and the quote was about Apple Pay rather than Apple Card. The guard
110
+ * could not catch it, because the quote was real. On qwen3.6-35b-a3b the real
111
+ * case went from 0/3 to 3/3 correct, with no change on the 21-case comparison
112
+ * set, the controls, or the 10% case. Two synthetic list cases ("issued at the
113
+ * Duluth, Norcross and Snellville branches") still fail on that model, which
114
+ * reads a plain list as "only" these. Putting reasoning ahead of the verdict
115
+ * fixed those and broke the real Apple case 3/3, so it did not ship.
116
+ *
104
117
  * The line about qualification is load-bearing rather than decorative.
105
118
  * Measured 2026-09-04 without it: adding two lines to a source that never
106
119
  * mention the claim flipped a verdict from supported to not_supported — three
@@ -108,7 +121,7 @@ export interface SupportStageOptions {
108
121
  * model stayed stable. A package promising engine-agnostic verification has to
109
122
  * survive the weaker engine, so the instruction ships by default.
110
123
  */
111
- export declare const SUPPORT_SYSTEM = "You are a strict fact-check judge. Decide whether the SOURCE supports the CLAIM using ONLY the source text \u2014 no outside knowledge, no charitable guessing.\nVerdicts:\n- \"supported\": the source affirmatively supports the claim.\n- \"not_supported\": the source contradicts the claim or asserts something incompatible with it. This INCLUDES the source stating a DIFFERENT VALUE for the same thing the claim quantifies \u2014 a rate, price, date, count, duration or name. If the claim says 10% and the source says the rate is 3%, the source has contradicted it; it does not have to mention 10% to do so.\n- \"cant_tell\": the source never addresses the subject of the claim at all, or addresses it only partially. A source that states its own value for the very thing the claim asserts HAS addressed it \u2014 that is not_supported, not cant_tell. Silence is NOT support.\nA qualification is not a contradiction. If the source substantially supports the claim but adds an exception, a carve-out or a condition, it has NOT said otherwise \u2014 do not rule not_supported. Reserve not_supported for a source that asserts something the claim cannot be true alongside.\nTo rule \"not_supported\" you MUST fill \"contradiction\" with the exact sentence from the source that says otherwise, copied verbatim. Where the conflict is a differing value, that sentence is the one stating the SOURCE's own value. If no such sentence exists, you may not use that verdict.\nCompare like with like: a different value only contradicts when it describes the same thing under the same conditions. A source listing several values for different cases has not contradicted a claim that matches one of them.\nReturn STRICT JSON only:\n{\"verdict\":\"supported\"|\"not_supported\"|\"cant_tell\",\"reasoning\":\"<one or two plain sentences>\",\"evidence\":\"<short verbatim quote from the source that decided it, or null>\",\"contradiction\":\"<verbatim sentence that contradicts the claim, or null>\"}";
124
+ export declare const SUPPORT_SYSTEM = "You are a strict fact-check judge. Decide whether the SOURCE supports the CLAIM using ONLY the source text \u2014 no outside knowledge, no charitable guessing.\nVerdicts:\n- \"supported\": the source affirmatively supports the claim.\n- \"not_supported\": the source contradicts the claim or asserts something incompatible with it. This INCLUDES the source stating a DIFFERENT VALUE for the same thing the claim quantifies \u2014 a rate, price, date, count, duration or name. If the claim says 10% and the source says the rate is 3%, the source has contradicted it; it does not have to mention 10% to do so.\n- \"cant_tell\": the source never addresses the subject of the claim at all, or addresses it only partially. A source that states its own value for the very thing the claim asserts HAS addressed it \u2014 that is not_supported, not cant_tell. Silence is NOT support.\nA qualification is not a contradiction. If the source substantially supports the claim but adds an exception, a carve-out or a condition, it has NOT said otherwise \u2014 do not rule not_supported. Reserve not_supported for a source that asserts something the claim cannot be true alongside.\nTo rule \"not_supported\" you MUST fill \"contradiction\" with the exact sentence from the source that says otherwise, copied verbatim. Where the conflict is a differing value, that sentence is the one stating the SOURCE's own value. If no such sentence exists, you may not use that verdict.\nCompare like with like: a different value only contradicts when it describes the same thing under the same conditions. A source listing several values for different cases has not contradicted a claim that matches one of them.\nA list is not a denial. A source that names the devices, places, people or cases something applies to has NOT ruled out the ones it leaves unnamed, unless it says so in words \u2014 \"only\", \"exclusively\", \"not available on\", \"does not work with\". Absence from a list is silence, and silence is cant_tell.\nThe contradicting sentence must be about the same subject as the claim. A sentence about a different product, service or program does not contradict it.\nReturn STRICT JSON only:\n{\"verdict\":\"supported\"|\"not_supported\"|\"cant_tell\",\"reasoning\":\"<one or two plain sentences>\",\"evidence\":\"<short verbatim quote from the source that decided it, or null>\",\"contradiction\":\"<verbatim sentence that contradicts the claim, or null>\"}";
112
125
  /** JSON Schema for the judgement, for providers that constrain decoding. */
113
126
  export declare const SUPPORT_SCHEMA: {
114
127
  readonly type: "object";
package/dist/support.js CHANGED
@@ -32,6 +32,19 @@
32
32
  * the obvious over-correction is ruling not_supported on a page that simply
33
33
  * lists several values for different cases.
34
34
  *
35
+ * The list and same-subject lines were added 2026-10-28, the mirror image of
36
+ * the 10% failure on the same page. Asked whether apple.com/apple-card supports
37
+ * "Apple Card works with Windows PCs", the judge ruled not_supported, quoting
38
+ * "Apple Pay ... built into iPhone, Apple Watch, iPad, and Mac" and reasoning
39
+ * that listing only Apple devices "implies exclusion". That is an argument from
40
+ * silence, and the quote was about Apple Pay rather than Apple Card. The guard
41
+ * could not catch it, because the quote was real. On qwen3.6-35b-a3b the real
42
+ * case went from 0/3 to 3/3 correct, with no change on the 21-case comparison
43
+ * set, the controls, or the 10% case. Two synthetic list cases ("issued at the
44
+ * Duluth, Norcross and Snellville branches") still fail on that model, which
45
+ * reads a plain list as "only" these. Putting reasoning ahead of the verdict
46
+ * fixed those and broke the real Apple case 3/3, so it did not ship.
47
+ *
35
48
  * The line about qualification is load-bearing rather than decorative.
36
49
  * Measured 2026-09-04 without it: adding two lines to a source that never
37
50
  * mention the claim flipped a verdict from supported to not_supported — three
@@ -47,6 +60,8 @@ Verdicts:
47
60
  A qualification is not a contradiction. If the source substantially supports the claim but adds an exception, a carve-out or a condition, it has NOT said otherwise — do not rule not_supported. Reserve not_supported for a source that asserts something the claim cannot be true alongside.
48
61
  To rule "not_supported" you MUST fill "contradiction" with the exact sentence from the source that says otherwise, copied verbatim. Where the conflict is a differing value, that sentence is the one stating the SOURCE's own value. If no such sentence exists, you may not use that verdict.
49
62
  Compare like with like: a different value only contradicts when it describes the same thing under the same conditions. A source listing several values for different cases has not contradicted a claim that matches one of them.
63
+ A list is not a denial. A source that names the devices, places, people or cases something applies to has NOT ruled out the ones it leaves unnamed, unless it says so in words — "only", "exclusively", "not available on", "does not work with". Absence from a list is silence, and silence is cant_tell.
64
+ The contradicting sentence must be about the same subject as the claim. A sentence about a different product, service or program does not contradict it.
50
65
  Return STRICT JSON only:
51
66
  {"verdict":"supported"|"not_supported"|"cant_tell","reasoning":"<one or two plain sentences>","evidence":"<short verbatim quote from the source that decided it, or null>","contradiction":"<verbatim sentence that contradicts the claim, or null>"}`;
52
67
  /** JSON Schema for the judgement, for providers that constrain decoding. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stilltrue",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Your tests check your code. stilltrue checks your facts. CI checks for AI-in-the-loop apps: drift (curated facts vs live sources), golden (regression evals), verify (judge answers against sources).",
5
5
  "keywords": [
6
6
  "drift",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "jiti": "^2.7.0",
34
- "zod": "^3.23.0"
34
+ "zod": "^3.23.0 || ^4.0.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^22.0.0",