stilltrue 0.3.0 → 0.4.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/README.md CHANGED
@@ -2,105 +2,152 @@
2
2
 
3
3
  > Your tests check your code. **stilltrue** checks your facts.
4
4
 
5
- AI-in-the-loop apps quietly start lying three ways: code changes break old
6
- answers, the world changes and curated facts rot, and the model embellishes
7
- beyond its sources. stilltrue is one watchdog per failure mode, run from CI —
8
- no server, no dashboard, no SaaS.
9
-
10
- | Command | Watchdog | Status |
11
- |---|---|---|
12
- | `npx stilltrue drift` | Curated facts vs their live authoritative sources — fails the build on rot | **v0.1 focus** |
13
- | `npx stilltrue golden` | Deterministic regression evals for answers | later (may wrap promptfoo) |
14
- | `verify` (library) | Staged acceptance pipeline for AI output — judge answers against sources in the request path | **shipped 0.3.0** |
15
-
16
- **Why drift first:** "drift detection" in the market means statistical ML
17
- feature drift; website-change monitors alert humans, not CI. Nobody ships
18
- *"assert my curated facts still match their live source, as a CI check."*
19
- This was built bespoke three times across three domains before becoming a
20
- package — see [docs/BRIEF.md](docs/BRIEF.md) for the full design brief,
21
- positioning, and build plan.
22
-
23
- ## Core principle
24
-
25
- **AI may extract, but must never decide.** The default drift path has zero AI:
26
- fetch → parse → compare → diff. A consumer's `source()` may use an LLM to pull
27
- structure out of a messy page, but the comparison and the verdict are always
28
- deterministic. Same inputs, same verdict — a nondeterministic alarm trains
29
- people to ignore it.
30
-
31
- Drift has three outcomes, never two: `pass`, `rot` (facts changed — fail the
32
- build), and `error` (source unreachable — warn, don't fail). Conflating the
33
- last two causes alarm fatigue.
34
-
35
- ## Quick start
36
-
37
- ```ts
38
- // stilltrue.config.ts (or .mjs / .js)
5
+ ## The problem
6
+
7
+ Say you've built an app or chatbot that answers questions using facts you
8
+ collected — a list of school board members, office hours, tax rates,
9
+ whatever. You checked those facts when you wrote them down. But the world
10
+ keeps moving: a board member resigns, a rate changes, a first day of school
11
+ rolls over. **Your app doesn't know.** It keeps confidently repeating
12
+ yesterday's truth, and nothing alerts you.
13
+
14
+ stilltrue is the alarm. Once a week (or whenever you like), it re-reads the
15
+ official web pages your facts came from and checks that every fact you
16
+ recorded still appears there. If something vanished, your build fails and
17
+ tells you exactly which fact went stale — so a human can go verify and
18
+ update it.
19
+
20
+ ## What you get
21
+
22
+ | Command | What it does |
23
+ |---|---|
24
+ | `npx stilltrue drift` | Checks your saved facts against their live sources. Fails loudly if a fact has gone stale. |
25
+ | `npx stilltrue report` | Turns past check runs into a simple web page — green/red grid, what broke and when. |
26
+ | `verify` | For developers: a checkpoint that inspects AI-generated answers before they're shown to users. |
27
+
28
+ ## Getting started
29
+
30
+ You'll need [Node.js](https://nodejs.org) (version 20 or newer) and a
31
+ project folder with your facts saved in a JSON file.
32
+
33
+ **1. Install it** — in your project folder, run:
34
+
35
+ ```bash
36
+ npm install --save-dev stilltrue
37
+ ```
38
+
39
+ **2. Describe a check** — create a file called `stilltrue.config.mjs` in
40
+ the same folder. Each check answers three questions: *where do I fetch the
41
+ truth from* (`source`), *what do I believe* (`expect`), and *how do I
42
+ compare the two* (`compare`):
43
+
44
+ ```js
39
45
  import { defineStilltrue, corpus, json, surname } from 'stilltrue';
40
46
 
41
47
  export default defineStilltrue({
42
48
  drift: [
43
49
  {
44
50
  name: 'school-board-roster',
45
- source: corpus(['https://district.example.org/board']),
46
- expect: json('./data/school-board.json', (d) => d.members.map((m) => surname(m.name))),
51
+ // Where the truth lives: the district's own pages.
52
+ source: corpus(['https://www.myschooldistrict.org/board']),
53
+ // What you believe: read your saved file, keep just the last names.
54
+ expect: json('./data/school-board.json', (data) =>
55
+ data.members.map((m) => surname(m.name))
56
+ ),
57
+ // How to compare: every name must still appear on those pages.
47
58
  compare: 'contains-all',
48
59
  },
49
60
  ],
50
61
  });
51
62
  ```
52
63
 
64
+ This example says: *fetch the board page, and make sure every last name in
65
+ my `school-board.json` file still appears somewhere on it.*
66
+
67
+ **3. Run it:**
68
+
69
+ ```bash
70
+ npx stilltrue drift
71
+ ```
72
+
73
+ You'll see one line per check:
74
+
75
+ ```
76
+ ✓ school-board-roster — pass (1204ms)
77
+
78
+ 1 pass, 0 rot, 0 error (errors warn, never fail)
79
+ ```
80
+
81
+ ## The three results (and why there are three, not two)
82
+
83
+ - **pass** — every saved fact still appears at its source. All good.
84
+ - **rot** — a fact is gone from the source. The world changed; your data is
85
+ stale. This *fails the run* so you notice and fix the data.
86
+ - **error** — the website couldn't be reached (down, timeout, blocked).
87
+ This only *warns*. An unreachable site doesn't mean your facts are wrong,
88
+ and an alarm that cries wolf gets ignored — so outages never fail the build.
89
+
90
+ ## Running it automatically every week
91
+
92
+ If your project is on GitHub, add this file as
93
+ `.github/workflows/drift.yml` and GitHub will run the check every Wednesday
94
+ and email you if anything rotted:
95
+
53
96
  ```yaml
54
- # .github/workflows/drift.yml
97
+ name: Fact drift check
55
98
  on:
56
- schedule: [{ cron: '17 14 * * 3' }] # off-beat minute: GitHub drops :00 crons
99
+ schedule:
100
+ - cron: '17 14 * * 3' # Wednesdays, ~10am US Eastern
101
+ workflow_dispatch: # adds a manual "Run workflow" button
57
102
  jobs:
58
103
  drift:
59
104
  runs-on: ubuntu-latest
60
105
  steps:
61
- - uses: actions/checkout@v4
62
- - uses: actions/setup-node@v4
106
+ - uses: actions/checkout@v5
107
+ - uses: actions/setup-node@v5
63
108
  with: { node-version: '22' }
64
109
  - run: npm ci
65
110
  - run: npx stilltrue drift
66
111
  ```
67
112
 
68
- `source` is your code (fetch+parse, or LLM extraction — the comparison stays
69
- deterministic either way). `expect` is your curated file. Rot fails the run
70
- with one message per vanished fact; unreachable sources only warn.
113
+ ## The report
71
114
 
72
- ## Verify — staged acceptance for AI output
115
+ Add `--record` to save each run, then generate the web page:
73
116
 
74
- An ordered, short-circuiting pipeline: each stage is a pluggable judge — a
75
- Zod parse, a deterministic validator, or an LLM. Rejection is structured
76
- (`{ stage, messages[] }`, one issue per entry) and designed to be fed back to
77
- the generator for a retry. Stages that *throw* are infrastructure failures,
78
- not verdicts: `failPolicy: 'open'` (default) warns and continues — a judge
79
- outage must never block your app.
117
+ ```bash
118
+ npx stilltrue drift --record
119
+ npx stilltrue report
120
+ ```
80
121
 
81
- ```ts
82
- import { runVerify, zodStage, generateVerified, formatRejection } from 'stilltrue';
122
+ That writes `stilltrue-report.html` — open it in any browser. It shows a
123
+ timeline grid (one row per check, one column per run, green ✓ / red ✗ /
124
+ amber ⚠) and a log of everything that ever rotted, with details.
83
125
 
84
- const stages = [
85
- zodStage('parse_schema', AnswerSchema),
86
- { name: 'grounding', timeoutMs: 10_000, check: llmJudge }, // may return a revision
87
- { name: 'chase-sim', tier: 'ci', check: expensiveSimulation }, // skipped inline
88
- ];
126
+ ## One rule underneath everything
89
127
 
90
- // Request path:
91
- const result = await runVerify(stages, draft);
92
- publish(result.value); // draft, parsed draft, or the judge's rewrite
128
+ **AI may extract, but never decides.** The drift check itself uses no AI at
129
+ all — it fetches, reads, and compares text, the same way every time, with
130
+ no API key and no cost. Deterministic alarms are trustworthy alarms. (If
131
+ *your* fact-gathering step wants to use AI to pull structure out of a messy
132
+ page, that's fine — it happens inside your `source` function, and the
133
+ comparison stays mechanical.)
93
134
 
94
- // Generator loop (one automatic retry, then fall back — don't loop forever):
95
- const gen = await generateVerified({ generate: (feedback) => ask(model, prompt, feedback && formatRejection(feedback)), stages });
96
- ```
135
+ ## For developers: `verify`
136
+
137
+ `verify` is a library for teams shipping AI-generated answers: an ordered
138
+ series of checkpoints (a schema parse, a rules check, an AI judge — your
139
+ choice) that each answer must pass before publishing. Rejections say
140
+ exactly which checkpoint failed and why, in a format you can feed back to
141
+ the model for a retry. A broken checker never blocks your app — it fails
142
+ open. See the [project docs](docs/BRIEF.md) and the typed API in
143
+ `stilltrue`'s exports (`runVerify`, `zodStage`, `generateVerified`).
97
144
 
98
- ## Status
145
+ ## What about testing prompts?
99
146
 
100
- v0.3 — `drift` and `report` running in production CI (AskGwinnett); `verify`
101
- shipped as a library. `golden` remains open: wrap promptfoo or defer to it
102
- (see the brief's competitive research).
147
+ That's a different job with a great existing tool:
148
+ [promptfoo](https://promptfoo.dev) tests your prompts; stilltrue tests your
149
+ facts. How they fit together: [docs/GOLDEN.md](docs/GOLDEN.md).
103
150
 
104
151
  ## License
105
152
 
106
- MIT
153
+ MIT — free to use, modify, and share.
package/dist/cli.js CHANGED
@@ -95,7 +95,8 @@ try {
95
95
  process.exit(await report(rest));
96
96
  break;
97
97
  case 'golden':
98
- console.error('stilltrue golden: not implemented yet — drift shipped first (docs/BRIEF.md)');
98
+ console.error('stilltrue deliberately has no golden runner — promptfoo tests your prompts; stilltrue tests your facts.\n' +
99
+ 'Use https://promptfoo.dev for prompt/answer regression evals. Doctrine + pairing guide: docs/GOLDEN.md');
99
100
  process.exit(1);
100
101
  break;
101
102
  default:
package/dist/config.d.ts CHANGED
@@ -58,8 +58,6 @@ export interface StilltrueConfig {
58
58
  drift?: DriftCheck<any, any>[];
59
59
  /** Named verify pipelines (see src/verify.ts) — usable from library code. */
60
60
  verify?: Record<string, import('./verify.ts').VerifyStage<any>[]>;
61
- /** golden is a later phase — see docs/BRIEF.md build plan. */
62
- golden?: unknown;
63
61
  }
64
62
  /** Identity function for typed configs: `export default defineStilltrue({...})`. */
65
63
  export declare function defineStilltrue(config: StilltrueConfig): StilltrueConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stilltrue",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",
@@ -37,5 +37,13 @@
37
37
  },
38
38
  "engines": {
39
39
  "node": ">=20"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/sg-bfoster/stilltrue.git"
44
+ },
45
+ "homepage": "https://github.com/sg-bfoster/stilltrue#readme",
46
+ "bugs": {
47
+ "url": "https://github.com/sg-bfoster/stilltrue/issues"
40
48
  }
41
49
  }