stilltrue 0.4.1 → 0.5.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 +42 -15
- package/dist/cli.js +19 -1
- package/dist/init.d.ts +6 -0
- package/dist/init.js +72 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,33 +36,60 @@ project folder with your facts saved in a JSON file.
|
|
|
36
36
|
npm install --save-dev stilltrue
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
**2.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
**2. Scaffold a check** — this creates a ready-to-edit config file (it
|
|
40
|
+
never overwrites anything you already have):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx stilltrue init
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Open the generated `stilltrue.config.mjs`. Each check answers three
|
|
47
|
+
questions: *where do I fetch the truth from* (`source`), *what do I
|
|
48
|
+
believe* (`expect`), and *how do I compare the two* (`compare`) — and in
|
|
49
|
+
its simplest form, **the only editing needed is replacing strings**:
|
|
43
50
|
|
|
44
51
|
```js
|
|
45
|
-
import { defineStilltrue, corpus
|
|
52
|
+
import { defineStilltrue, corpus } from 'stilltrue';
|
|
46
53
|
|
|
47
54
|
export default defineStilltrue({
|
|
48
55
|
drift: [
|
|
49
56
|
{
|
|
50
|
-
name: '
|
|
51
|
-
// Where the truth lives: the
|
|
52
|
-
source: corpus(['https://
|
|
53
|
-
// What you believe:
|
|
54
|
-
expect:
|
|
55
|
-
|
|
56
|
-
),
|
|
57
|
-
// How to compare: every name must still appear on those pages.
|
|
57
|
+
name: 'my-first-check',
|
|
58
|
+
// Where the truth lives: the official page(s) your facts came from.
|
|
59
|
+
source: corpus(['https://example.org/about']),
|
|
60
|
+
// What you believe: your facts, as short strings ("markers").
|
|
61
|
+
expect: ['Jane Smith', 'Main Street Office', '12 locations'],
|
|
62
|
+
// How to compare: every marker must still appear on those pages.
|
|
58
63
|
compare: 'contains-all',
|
|
59
64
|
},
|
|
60
65
|
],
|
|
61
66
|
});
|
|
62
67
|
```
|
|
63
68
|
|
|
64
|
-
This
|
|
65
|
-
|
|
69
|
+
This says: *fetch that page, and make sure "Jane Smith", "Main Street
|
|
70
|
+
Office", and "12 locations" all still appear on it.* Swap in your own URL
|
|
71
|
+
and your own facts — that's a working check, no programming required.
|
|
72
|
+
|
|
73
|
+
Choosing what to assert is the judgment call — surnames beat full names,
|
|
74
|
+
"143 schools" beats a bare "143". The short guide:
|
|
75
|
+
[docs/MARKERS.md](docs/MARKERS.md).
|
|
76
|
+
|
|
77
|
+
**Leveling up (optional):** you don't have to keep the facts in the config.
|
|
78
|
+
If `./data/markers.json` is just a list of strings, point at it with
|
|
79
|
+
`expect: json('./data/markers.json')` — still no code. And if your app
|
|
80
|
+
already keeps structured data, derive the markers from it so your data file
|
|
81
|
+
stays the single source of truth:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
// ./data/school-board.json: { "members": [{ "name": "Dr. Jane Smith" }, ...] }
|
|
85
|
+
expect: json('./data/school-board.json', (data) =>
|
|
86
|
+
data.members.map((m) => surname(m.name)) // keeps just "Smith" — titles
|
|
87
|
+
), // and first names vary on pages
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The function receives *your* file's contents, whatever shape they are, and
|
|
91
|
+
returns the list of markers — stilltrue never needs to understand your data
|
|
92
|
+
structure. Update the data file and the markers update themselves.
|
|
66
93
|
|
|
67
94
|
**3. Run it:**
|
|
68
95
|
|
package/dist/cli.js
CHANGED
|
@@ -94,13 +94,31 @@ try {
|
|
|
94
94
|
case 'report':
|
|
95
95
|
process.exit(await report(rest));
|
|
96
96
|
break;
|
|
97
|
+
case 'init': {
|
|
98
|
+
const { init } = await import("./init.js");
|
|
99
|
+
const { created, skipped } = await init(process.cwd());
|
|
100
|
+
for (const f of created)
|
|
101
|
+
console.log(`created ${f}`);
|
|
102
|
+
for (const f of skipped)
|
|
103
|
+
console.log(`kept existing ${f} (never overwritten)`);
|
|
104
|
+
if (created.length) {
|
|
105
|
+
console.log('\nNext steps:\n' +
|
|
106
|
+
' 1. Edit stilltrue.config.mjs — point source at the real page(s) your facts came from,\n' +
|
|
107
|
+
' and replace the example markers with your own facts (plain strings)\n' +
|
|
108
|
+
' 2. Run: npx stilltrue drift\n' +
|
|
109
|
+
'Picking good markers: docs/MARKERS.md in the stilltrue repo');
|
|
110
|
+
}
|
|
111
|
+
process.exit(0);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
97
114
|
case 'golden':
|
|
98
115
|
console.error('stilltrue deliberately has no golden runner — promptfoo tests your prompts; stilltrue tests your facts.\n' +
|
|
99
116
|
'Use https://promptfoo.dev for prompt/answer regression evals. Doctrine + pairing guide: docs/GOLDEN.md');
|
|
100
117
|
process.exit(1);
|
|
101
118
|
break;
|
|
102
119
|
default:
|
|
103
|
-
console.log('usage: stilltrue
|
|
120
|
+
console.log('usage: stilltrue init\n' +
|
|
121
|
+
' stilltrue drift [--config <path>] [--only <names>] [--json <path>] [--record] [--history <path>]\n' +
|
|
104
122
|
' stilltrue report [--history <path>] [--out <path>] [--title <text>]');
|
|
105
123
|
process.exit(command ? 1 : 0);
|
|
106
124
|
}
|
package/dist/init.d.ts
ADDED
package/dist/init.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const CONFIG_TEMPLATE = `// stilltrue configuration — one entry per fact-set you want watched.
|
|
5
|
+
// Docs: https://github.com/sg-bfoster/stilltrue#readme
|
|
6
|
+
// Picking good markers: https://github.com/sg-bfoster/stilltrue/blob/main/docs/MARKERS.md
|
|
7
|
+
import { defineStilltrue, corpus, json, surname } from 'stilltrue';
|
|
8
|
+
|
|
9
|
+
export default defineStilltrue({
|
|
10
|
+
drift: [
|
|
11
|
+
{
|
|
12
|
+
// A short id for this check — shows up in output and reports.
|
|
13
|
+
name: 'my-first-check',
|
|
14
|
+
|
|
15
|
+
// WHERE THE TRUTH LIVES: the official page(s) your facts came from.
|
|
16
|
+
// corpus() fetches each page and strips it down to readable text.
|
|
17
|
+
source: corpus([
|
|
18
|
+
'https://example.org/about',
|
|
19
|
+
// 'https://example.org/board', // add more pages if facts span several
|
|
20
|
+
]),
|
|
21
|
+
|
|
22
|
+
// WHAT YOU BELIEVE: list the facts as short strings ("markers").
|
|
23
|
+
// Every one of these must still appear somewhere on the page(s)
|
|
24
|
+
// above, or the check fails and names the missing one.
|
|
25
|
+
expect: [
|
|
26
|
+
'Jane Smith',
|
|
27
|
+
'Main Street Office',
|
|
28
|
+
'12 locations',
|
|
29
|
+
],
|
|
30
|
+
|
|
31
|
+
// HOW TO COMPARE: 'contains-all' = every marker must appear in the
|
|
32
|
+
// fetched page text (case-insensitive).
|
|
33
|
+
compare: 'contains-all',
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// ── Leveling up (optional) ──────────────────────────────────────────
|
|
37
|
+
//
|
|
38
|
+
// Instead of listing markers inline, you can keep them in a file.
|
|
39
|
+
// If ./data/markers.json contains just a list of strings —
|
|
40
|
+
// ["Jane Smith", "Main Street Office", "12 locations"]
|
|
41
|
+
// — then this works with no code at all:
|
|
42
|
+
//
|
|
43
|
+
// expect: json('./data/markers.json'),
|
|
44
|
+
//
|
|
45
|
+
// And if your app already keeps structured data — say
|
|
46
|
+
// ./data/board.json looks like
|
|
47
|
+
// { "members": [ { "name": "Dr. Jane Smith" }, { "name": "Bob Jones Jr." } ] }
|
|
48
|
+
// — you can derive the markers from it, so your data file stays the
|
|
49
|
+
// single source of truth (surname() keeps just "Smith" / "Jones",
|
|
50
|
+
// because pages change titles and first names more than surnames):
|
|
51
|
+
//
|
|
52
|
+
// expect: json('./data/board.json', (data) =>
|
|
53
|
+
// data.members.map((m) => surname(m.name))
|
|
54
|
+
// ),
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
`;
|
|
58
|
+
/** Write the starter config. Never overwrites an existing one. */
|
|
59
|
+
export async function init(dir) {
|
|
60
|
+
const existingConfig = [
|
|
61
|
+
'stilltrue.config.mjs',
|
|
62
|
+
'stilltrue.config.ts',
|
|
63
|
+
'stilltrue.config.mts',
|
|
64
|
+
'stilltrue.config.js',
|
|
65
|
+
'stilltrue.config.cjs',
|
|
66
|
+
].find((name) => existsSync(join(dir, name)));
|
|
67
|
+
if (existingConfig) {
|
|
68
|
+
return { created: [], skipped: [existingConfig] };
|
|
69
|
+
}
|
|
70
|
+
await writeFile(join(dir, 'stilltrue.config.mjs'), CONFIG_TEMPLATE);
|
|
71
|
+
return { created: ['stilltrue.config.mjs'], skipped: [] };
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stilltrue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|