stilltrue 0.6.0 → 0.7.1
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 +72 -0
- package/README.md +24 -240
- package/dist/cli.js +1 -1
- package/dist/support.d.ts +15 -1
- package/dist/support.js +18 -3
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.7.1 — 2026-09-13
|
|
4
|
+
|
|
5
|
+
Docs restructure, no behavior changes except one message:
|
|
6
|
+
|
|
7
|
+
- README cut from 408 lines to ~190. A fresh-eyes pass (install → first
|
|
8
|
+
green check in three minutes) showed setup was never the problem — the
|
|
9
|
+
document performed difficulty the tool doesn't have. The drift
|
|
10
|
+
quickstart, which tested perfect, is untouched.
|
|
11
|
+
- New `docs/CONFIG.md`: the reference shelf (config fields, compare modes,
|
|
12
|
+
helpers, CLI flags), moved verbatim out of the tutorial path.
|
|
13
|
+
- `docs/VERIFY.md` gained the `supportStage` section from the README, so
|
|
14
|
+
the ready-made stage lives beside the judge walkthrough it builds on.
|
|
15
|
+
- `stilltrue init` now prints the full MARKERS.md URL — npm users have no
|
|
16
|
+
repo checkout for a relative path to resolve against.
|
|
17
|
+
|
|
18
|
+
## 0.7.0
|
|
19
|
+
|
|
20
|
+
**A different value now counts as a contradiction.**
|
|
21
|
+
|
|
22
|
+
The judge used to answer `cant_tell` when a source stated its own figure for
|
|
23
|
+
the thing a claim quantified. Asked whether `apple.com/apple-card` supports
|
|
24
|
+
"Apple Card gives 10% Daily Cash back on every purchase", it reasoned:
|
|
25
|
+
|
|
26
|
+
> The source states that Apple Card offers 2% Daily Cash for most purchases,
|
|
27
|
+
> 3% at Apple and select merchants, and 1% on the titanium card, but it does
|
|
28
|
+
> not mention a 10% Daily Cash rate.
|
|
29
|
+
|
|
30
|
+
It had read every rate on the page and still treated the absence of the
|
|
31
|
+
claimed number as the claim being unaddressed. Nothing on that page denies
|
|
32
|
+
10% in words, so the old wording — `cant_tell: the source does not address
|
|
33
|
+
the claim` — left it nowhere else to land.
|
|
34
|
+
|
|
35
|
+
Explicit denial is the rare shape of a false claim. The common one is a wrong
|
|
36
|
+
number, and a checker that goes quiet on wrong numbers is quiet exactly when
|
|
37
|
+
it is most useful. It also collapsed the distinction the verdict set exists
|
|
38
|
+
for: a claim the source contradicts and a claim the source never mentions
|
|
39
|
+
came back identical.
|
|
40
|
+
|
|
41
|
+
- `not_supported` now explicitly covers a source stating a DIFFERENT VALUE for
|
|
42
|
+
what the claim quantifies — a rate, price, date, count, duration or name.
|
|
43
|
+
- `cant_tell` explicitly excludes that case.
|
|
44
|
+
- The contradiction-quote rule gains the matching line: where the conflict is
|
|
45
|
+
a differing value, the sentence to cite is the one stating the source's own
|
|
46
|
+
value.
|
|
47
|
+
- "Compare like with like" ships in the same change, because the obvious
|
|
48
|
+
over-correction is ruling `not_supported` against a page that simply lists
|
|
49
|
+
several values for different cases.
|
|
50
|
+
|
|
51
|
+
**Minor, not patch.** The public API is unchanged, but verdicts change for the
|
|
52
|
+
same inputs — and for a library whose product IS the verdict, that should not
|
|
53
|
+
reach a consumer pinned to `^0.6.0` silently.
|
|
54
|
+
|
|
55
|
+
Verified against a local gpt-oss-20b (the weaker of two available engines),
|
|
56
|
+
five claims against one live page:
|
|
57
|
+
|
|
58
|
+
| claim | before | after |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| 3% Daily Cash at Apple | supported | supported |
|
|
61
|
+
| **10% on every purchase** | **cant_tell** | **not_supported** |
|
|
62
|
+
| works with Windows PCs | cant_tell | cant_tell |
|
|
63
|
+
| 2% with Apple Pay | — | supported |
|
|
64
|
+
| no annual fee | — | supported |
|
|
65
|
+
|
|
66
|
+
The last two are over-correction checks. The contradiction guard did not
|
|
67
|
+
downgrade the 10% verdict, so it arrived with a verbatim quote that passed the
|
|
68
|
+
contiguous-run check — refused with evidence, not merely refused.
|
|
69
|
+
|
|
70
|
+
## 0.6.0 and earlier
|
|
71
|
+
|
|
72
|
+
Not recorded here; see the git history.
|
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
|
-
[
|
|
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
|
|
333
|
-
choice
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
|
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
|
@@ -87,6 +87,20 @@ export interface SupportStageOptions {
|
|
|
87
87
|
/**
|
|
88
88
|
* The shipped judge prompt.
|
|
89
89
|
*
|
|
90
|
+
* The "different value" clause was added 2026-09-11 after a measured failure:
|
|
91
|
+
* asked whether apple.com/apple-card supports "Apple Card gives 10% Daily Cash",
|
|
92
|
+
* the judge answered cant_tell, reasoning "the source states 2%... 3%... and 1%,
|
|
93
|
+
* but it does not mention a 10% Daily Cash rate". It had read every rate on the
|
|
94
|
+
* page and still treated the absence of the claimed number as the claim being
|
|
95
|
+
* unaddressed. Nothing on that page denies 10% in words, so the original
|
|
96
|
+
* wording gave it nowhere else to land.
|
|
97
|
+
*
|
|
98
|
+
* That is the common shape of a false claim in the wild — a wrong number, not
|
|
99
|
+
* an explicit denial — so a checker that goes quiet on it is quiet exactly when
|
|
100
|
+
* it is most needed. The compare-like-with-like line ships alongside because
|
|
101
|
+
* the obvious over-correction is ruling not_supported on a page that simply
|
|
102
|
+
* lists several values for different cases.
|
|
103
|
+
*
|
|
90
104
|
* The line about qualification is load-bearing rather than decorative.
|
|
91
105
|
* Measured 2026-09-04 without it: adding two lines to a source that never
|
|
92
106
|
* mention the claim flipped a verdict from supported to not_supported — three
|
|
@@ -94,7 +108,7 @@ export interface SupportStageOptions {
|
|
|
94
108
|
* model stayed stable. A package promising engine-agnostic verification has to
|
|
95
109
|
* survive the weaker engine, so the instruction ships by default.
|
|
96
110
|
*/
|
|
97
|
-
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.\n- \"cant_tell\": the source
|
|
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>\"}";
|
|
98
112
|
/** JSON Schema for the judgement, for providers that constrain decoding. */
|
|
99
113
|
export declare const SUPPORT_SCHEMA: {
|
|
100
114
|
readonly type: "object";
|
package/dist/support.js
CHANGED
|
@@ -18,6 +18,20 @@
|
|
|
18
18
|
/**
|
|
19
19
|
* The shipped judge prompt.
|
|
20
20
|
*
|
|
21
|
+
* The "different value" clause was added 2026-09-11 after a measured failure:
|
|
22
|
+
* asked whether apple.com/apple-card supports "Apple Card gives 10% Daily Cash",
|
|
23
|
+
* the judge answered cant_tell, reasoning "the source states 2%... 3%... and 1%,
|
|
24
|
+
* but it does not mention a 10% Daily Cash rate". It had read every rate on the
|
|
25
|
+
* page and still treated the absence of the claimed number as the claim being
|
|
26
|
+
* unaddressed. Nothing on that page denies 10% in words, so the original
|
|
27
|
+
* wording gave it nowhere else to land.
|
|
28
|
+
*
|
|
29
|
+
* That is the common shape of a false claim in the wild — a wrong number, not
|
|
30
|
+
* an explicit denial — so a checker that goes quiet on it is quiet exactly when
|
|
31
|
+
* it is most needed. The compare-like-with-like line ships alongside because
|
|
32
|
+
* the obvious over-correction is ruling not_supported on a page that simply
|
|
33
|
+
* lists several values for different cases.
|
|
34
|
+
*
|
|
21
35
|
* The line about qualification is load-bearing rather than decorative.
|
|
22
36
|
* Measured 2026-09-04 without it: adding two lines to a source that never
|
|
23
37
|
* mention the claim flipped a verdict from supported to not_supported — three
|
|
@@ -28,10 +42,11 @@
|
|
|
28
42
|
export const SUPPORT_SYSTEM = `You are a strict fact-check judge. Decide whether the SOURCE supports the CLAIM using ONLY the source text — no outside knowledge, no charitable guessing.
|
|
29
43
|
Verdicts:
|
|
30
44
|
- "supported": the source affirmatively supports the claim.
|
|
31
|
-
- "not_supported": the source contradicts the claim or asserts something incompatible with it.
|
|
32
|
-
- "cant_tell": the source
|
|
45
|
+
- "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 — 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.
|
|
46
|
+
- "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 — that is not_supported, not cant_tell. Silence is NOT support.
|
|
33
47
|
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.
|
|
34
|
-
To rule "not_supported" you MUST fill "contradiction" with the exact sentence from the source that says otherwise, copied verbatim. If no such sentence exists, you may not use that verdict.
|
|
48
|
+
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
|
+
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.
|
|
35
50
|
Return STRICT JSON only:
|
|
36
51
|
{"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>"}`;
|
|
37
52
|
/** 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.
|
|
3
|
+
"version": "0.7.1",
|
|
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",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"stilltrue": "dist/cli.js"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
-
"dist"
|
|
23
|
+
"dist",
|
|
24
|
+
"CHANGELOG.md"
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
27
|
"build": "tsc",
|