@attest-ai/core 0.5.0 → 0.9.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.
Files changed (123) hide show
  1. package/dist/cjs/index.cjs +741 -25
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.d.cts +145 -8
  4. package/dist/cjs/proto/index.cjs +81 -0
  5. package/dist/cjs/proto/index.cjs.map +1 -1
  6. package/dist/cjs/proto/index.d.cts +145 -3
  7. package/dist/esm/cli.js +321 -2
  8. package/dist/esm/cli.js.map +1 -1
  9. package/dist/esm/index.d.ts +145 -8
  10. package/dist/esm/index.js +724 -25
  11. package/dist/esm/index.js.map +1 -1
  12. package/dist/esm/proto/index.d.ts +145 -3
  13. package/dist/esm/proto/index.js +77 -0
  14. package/dist/esm/proto/index.js.map +1 -1
  15. package/package.json +4 -4
  16. package/dist/adapters/anthropic.d.ts +0 -31
  17. package/dist/adapters/anthropic.d.ts.map +0 -1
  18. package/dist/adapters/anthropic.js +0 -42
  19. package/dist/adapters/anthropic.js.map +0 -1
  20. package/dist/adapters/gemini.d.ts +0 -30
  21. package/dist/adapters/gemini.d.ts.map +0 -1
  22. package/dist/adapters/gemini.js +0 -41
  23. package/dist/adapters/gemini.js.map +0 -1
  24. package/dist/adapters/index.d.ts +0 -11
  25. package/dist/adapters/index.d.ts.map +0 -1
  26. package/dist/adapters/index.js +0 -7
  27. package/dist/adapters/index.js.map +0 -1
  28. package/dist/adapters/manual.d.ts +0 -9
  29. package/dist/adapters/manual.d.ts.map +0 -1
  30. package/dist/adapters/manual.js +0 -16
  31. package/dist/adapters/manual.js.map +0 -1
  32. package/dist/adapters/ollama.d.ts +0 -19
  33. package/dist/adapters/ollama.d.ts.map +0 -1
  34. package/dist/adapters/ollama.js +0 -30
  35. package/dist/adapters/ollama.js.map +0 -1
  36. package/dist/adapters/openai.d.ts +0 -34
  37. package/dist/adapters/openai.d.ts.map +0 -1
  38. package/dist/adapters/openai.js +0 -42
  39. package/dist/adapters/openai.js.map +0 -1
  40. package/dist/adapters/otel.d.ts +0 -25
  41. package/dist/adapters/otel.d.ts.map +0 -1
  42. package/dist/adapters/otel.js +0 -133
  43. package/dist/adapters/otel.js.map +0 -1
  44. package/dist/agent.d.ts +0 -14
  45. package/dist/agent.d.ts.map +0 -1
  46. package/dist/agent.js +0 -54
  47. package/dist/agent.js.map +0 -1
  48. package/dist/client.d.ts +0 -23
  49. package/dist/client.d.ts.map +0 -1
  50. package/dist/client.js +0 -171
  51. package/dist/client.js.map +0 -1
  52. package/dist/config.d.ts +0 -21
  53. package/dist/config.d.ts.map +0 -1
  54. package/dist/config.js +0 -31
  55. package/dist/config.js.map +0 -1
  56. package/dist/context.d.ts +0 -4
  57. package/dist/context.d.ts.map +0 -1
  58. package/dist/context.js +0 -3
  59. package/dist/context.js.map +0 -1
  60. package/dist/delegate.d.ts +0 -3
  61. package/dist/delegate.d.ts.map +0 -1
  62. package/dist/delegate.js +0 -24
  63. package/dist/delegate.js.map +0 -1
  64. package/dist/engine-downloader.d.ts +0 -7
  65. package/dist/engine-downloader.d.ts.map +0 -1
  66. package/dist/engine-downloader.js +0 -136
  67. package/dist/engine-downloader.js.map +0 -1
  68. package/dist/engine-manager.d.ts +0 -26
  69. package/dist/engine-manager.d.ts.map +0 -1
  70. package/dist/engine-manager.js +0 -172
  71. package/dist/engine-manager.js.map +0 -1
  72. package/dist/expect.d.ts +0 -133
  73. package/dist/expect.d.ts.map +0 -1
  74. package/dist/expect.js +0 -330
  75. package/dist/expect.js.map +0 -1
  76. package/dist/index.d.ts +0 -16
  77. package/dist/index.d.ts.map +0 -1
  78. package/dist/index.js +0 -15
  79. package/dist/index.js.map +0 -1
  80. package/dist/proto/codec.d.ts +0 -20
  81. package/dist/proto/codec.d.ts.map +0 -1
  82. package/dist/proto/codec.js +0 -71
  83. package/dist/proto/codec.js.map +0 -1
  84. package/dist/proto/constants.d.ts +0 -21
  85. package/dist/proto/constants.d.ts.map +0 -1
  86. package/dist/proto/constants.js +0 -25
  87. package/dist/proto/constants.js.map +0 -1
  88. package/dist/proto/errors.d.ts +0 -13
  89. package/dist/proto/errors.d.ts.map +0 -1
  90. package/dist/proto/errors.js +0 -24
  91. package/dist/proto/errors.js.map +0 -1
  92. package/dist/proto/index.d.ts +0 -5
  93. package/dist/proto/index.d.ts.map +0 -1
  94. package/dist/proto/index.js +0 -5
  95. package/dist/proto/index.js.map +0 -1
  96. package/dist/proto/types.d.ts +0 -104
  97. package/dist/proto/types.d.ts.map +0 -1
  98. package/dist/proto/types.js +0 -2
  99. package/dist/proto/types.js.map +0 -1
  100. package/dist/result.d.ts +0 -15
  101. package/dist/result.d.ts.map +0 -1
  102. package/dist/result.js +0 -32
  103. package/dist/result.js.map +0 -1
  104. package/dist/simulation.d.ts +0 -8
  105. package/dist/simulation.d.ts.map +0 -1
  106. package/dist/simulation.js +0 -21
  107. package/dist/simulation.js.map +0 -1
  108. package/dist/tier.d.ts +0 -5
  109. package/dist/tier.d.ts.map +0 -1
  110. package/dist/tier.js +0 -10
  111. package/dist/tier.js.map +0 -1
  112. package/dist/trace-tree.d.ts +0 -20
  113. package/dist/trace-tree.d.ts.map +0 -1
  114. package/dist/trace-tree.js +0 -92
  115. package/dist/trace-tree.js.map +0 -1
  116. package/dist/trace.d.ts +0 -41
  117. package/dist/trace.d.ts.map +0 -1
  118. package/dist/trace.js +0 -88
  119. package/dist/trace.js.map +0 -1
  120. package/dist/version.d.ts +0 -3
  121. package/dist/version.d.ts.map +0 -1
  122. package/dist/version.js +0 -3
  123. package/dist/version.js.map +0 -1
package/dist/esm/cli.js CHANGED
@@ -6,7 +6,220 @@ import * as path from "path";
6
6
  import * as os from "os";
7
7
 
8
8
  // src/version.ts
9
- var VERSION = "0.5.0";
9
+ var VERSION = "0.9.0";
10
+
11
+ // src/calibration.ts
12
+ import { createHash } from "crypto";
13
+ function computeAgreement(pairs, threshold) {
14
+ if (pairs.length === 0) {
15
+ throw new Error("no labeled pairs available");
16
+ }
17
+ if (threshold <= 0 || threshold >= 1) {
18
+ throw new Error("threshold must be in (0, 1)");
19
+ }
20
+ const n = pairs.length;
21
+ let humanPos = 0;
22
+ let judgePos = 0;
23
+ let bothPos = 0;
24
+ let bothNeg = 0;
25
+ for (const p of pairs) {
26
+ const hPos = p.human >= threshold;
27
+ const jPos = p.judge >= threshold;
28
+ if (hPos) humanPos++;
29
+ if (jPos) judgePos++;
30
+ if (hPos && jPos) bothPos++;
31
+ if (!hPos && !jPos) bothNeg++;
32
+ }
33
+ const humanNeg = n - humanPos;
34
+ const judgeNeg = n - judgePos;
35
+ const agreement = (bothPos + bothNeg) / n;
36
+ const expected = (humanPos * judgePos + humanNeg * judgeNeg) / (n * n);
37
+ const cohenKappa = expected < 1 ? (agreement - expected) / (1 - expected) : 0;
38
+ const rocAuc = rocAUC(pairs, threshold);
39
+ return { threshold, n, agreement, cohenKappa, rocAuc };
40
+ }
41
+ function rocAUC(pairs, threshold) {
42
+ const rows = pairs.map((p) => ({
43
+ score: p.judge,
44
+ pos: p.human >= threshold
45
+ }));
46
+ const pos = rows.filter((r) => r.pos).length;
47
+ const neg = rows.length - pos;
48
+ if (pos === 0 || neg === 0) return 0;
49
+ rows.sort((a, b) => a.score - b.score);
50
+ let rankSumPos = 0;
51
+ let i = 0;
52
+ while (i < rows.length) {
53
+ let j = i;
54
+ while (j < rows.length && rows[j].score === rows[i].score) {
55
+ j++;
56
+ }
57
+ const avgRank = (i + j + 1) / 2;
58
+ for (let k = i; k < j; k++) {
59
+ if (rows[k].pos) rankSumPos += avgRank;
60
+ }
61
+ i = j;
62
+ }
63
+ const auc = (rankSumPos - pos * (pos + 1) / 2) / (pos * neg);
64
+ if (Number.isNaN(auc) || !Number.isFinite(auc)) return 0;
65
+ return auc;
66
+ }
67
+ function loadLabelsCSV(text) {
68
+ const rows = parseCSV(text);
69
+ if (rows.length === 0) {
70
+ throw new Error("empty CSV");
71
+ }
72
+ let start = 0;
73
+ if (rows[0].length >= 2) {
74
+ const firstHuman = parseFloatStrict(rows[0][1]);
75
+ if (firstHuman === null) start = 1;
76
+ }
77
+ const out = [];
78
+ for (let idx = start; idx < rows.length; idx++) {
79
+ const row = rows[idx];
80
+ const lineNum = idx + 1;
81
+ if (row.length === 0) continue;
82
+ if (row[0].trimStart().startsWith("#")) continue;
83
+ if (row.length < 2) {
84
+ throw new Error(
85
+ `CSV line ${lineNum}: want at least 2 columns (input, human_label)`
86
+ );
87
+ }
88
+ const human = parseFloatStrict(row[1]);
89
+ if (human === null) {
90
+ throw new Error(`CSV line ${lineNum}: human_label not a float: ${row[1]}`);
91
+ }
92
+ const rec = {
93
+ input: row[0],
94
+ humanLabel: human,
95
+ judgeScore: 0,
96
+ judgeKnown: false
97
+ };
98
+ if (row.length >= 3 && row[2].trim() !== "") {
99
+ const judge = parseFloatStrict(row[2]);
100
+ if (judge === null) {
101
+ throw new Error(
102
+ `CSV line ${lineNum}: judge_score not a float: ${row[2]}`
103
+ );
104
+ }
105
+ rec.judgeScore = judge;
106
+ rec.judgeKnown = true;
107
+ }
108
+ out.push(rec);
109
+ }
110
+ if (out.length === 0) {
111
+ throw new Error("CSV contained no labeled rows");
112
+ }
113
+ return out;
114
+ }
115
+ function loadLabelsJSONL(text) {
116
+ const out = [];
117
+ const lines = text.split(/\r?\n/);
118
+ for (let i = 0; i < lines.length; i++) {
119
+ const stripped = lines[i].trim();
120
+ if (!stripped) continue;
121
+ let obj;
122
+ try {
123
+ obj = JSON.parse(stripped);
124
+ } catch (err) {
125
+ throw new Error(`JSONL line ${i + 1}: ${err.message}`);
126
+ }
127
+ if (Object.keys(obj).length === 0) continue;
128
+ if (obj["human_label"] === void 0) {
129
+ throw new Error(`JSONL line ${i + 1}: missing human_label`);
130
+ }
131
+ const human = parseJSONNumber(obj["human_label"]);
132
+ if (human === null) {
133
+ throw new Error(`JSONL line ${i + 1}: human_label is not a number`);
134
+ }
135
+ const rec = {
136
+ input: typeof obj["input"] === "string" ? obj["input"] : "",
137
+ humanLabel: human,
138
+ judgeScore: 0,
139
+ judgeKnown: false
140
+ };
141
+ if (obj["judge_score"] !== void 0 && obj["judge_score"] !== null) {
142
+ const judge = parseJSONNumber(obj["judge_score"]);
143
+ if (judge === null) {
144
+ throw new Error(`JSONL line ${i + 1}: judge_score is not a number`);
145
+ }
146
+ rec.judgeScore = judge;
147
+ rec.judgeKnown = true;
148
+ }
149
+ out.push(rec);
150
+ }
151
+ if (out.length === 0) {
152
+ throw new Error("JSONL contained no labeled rows");
153
+ }
154
+ return out;
155
+ }
156
+ function parseFloatStrict(raw) {
157
+ const trimmed = raw.trim();
158
+ if (trimmed === "") return null;
159
+ const n = Number(trimmed);
160
+ if (Number.isNaN(n)) return null;
161
+ return n;
162
+ }
163
+ function parseJSONNumber(value) {
164
+ if (typeof value !== "number") return null;
165
+ if (Number.isNaN(value)) return null;
166
+ return value;
167
+ }
168
+ function parseCSV(text) {
169
+ const rows = [];
170
+ let current = [];
171
+ let field = "";
172
+ let inQuotes = false;
173
+ let i = 0;
174
+ while (i < text.length) {
175
+ const ch = text[i];
176
+ if (inQuotes) {
177
+ if (ch === '"') {
178
+ if (text[i + 1] === '"') {
179
+ field += '"';
180
+ i += 2;
181
+ continue;
182
+ }
183
+ inQuotes = false;
184
+ i++;
185
+ continue;
186
+ }
187
+ field += ch;
188
+ i++;
189
+ continue;
190
+ }
191
+ if (ch === '"') {
192
+ inQuotes = true;
193
+ i++;
194
+ continue;
195
+ }
196
+ if (ch === ",") {
197
+ current.push(field);
198
+ field = "";
199
+ i++;
200
+ continue;
201
+ }
202
+ if (ch === "\n") {
203
+ current.push(field);
204
+ rows.push(current);
205
+ current = [];
206
+ field = "";
207
+ i++;
208
+ continue;
209
+ }
210
+ if (ch === "\r") {
211
+ i++;
212
+ continue;
213
+ }
214
+ field += ch;
215
+ i++;
216
+ }
217
+ if (field.length > 0 || current.length > 0) {
218
+ current.push(field);
219
+ rows.push(current);
220
+ }
221
+ return rows;
222
+ }
10
223
 
11
224
  // src/cli.ts
12
225
  function cacheDir() {
@@ -139,11 +352,117 @@ function main(argv = process.argv.slice(2)) {
139
352
  cmdValidate(process.cwd());
140
353
  process.exit(0);
141
354
  }
355
+ if (args[0] === "calibrate") {
356
+ cmdCalibrate(args.slice(1));
357
+ process.exit(0);
358
+ }
142
359
  process.stderr.write(
143
- "Usage: attest <command>\n\nCommands:\n --version Print version\n init Scaffold a test project\n validate Validate test suite\n cache stats Show cache statistics\n cache clear Clear cache database\n"
360
+ "Usage: attest <command>\n\nCommands:\n --version Print version\n init Scaffold a test project\n validate Validate test suite\n cache stats Show cache statistics\n cache clear Clear cache database\n calibrate Compute judge agreement metrics over labeled data\n"
144
361
  );
145
362
  process.exit(args.length === 0 ? 0 : 1);
146
363
  }
364
+ function parseCalibrateArgs(args) {
365
+ const opts = {
366
+ labels: "",
367
+ rubric: "default",
368
+ rubricVersion: "",
369
+ threshold: 0.5
370
+ };
371
+ for (let i = 0; i < args.length; i++) {
372
+ const arg = args[i];
373
+ const next = args[i + 1];
374
+ if (arg === "--labels") {
375
+ if (next === void 0) throw new Error("--labels requires a path");
376
+ opts.labels = next;
377
+ i++;
378
+ } else if (arg === "--rubric") {
379
+ if (next === void 0) throw new Error("--rubric requires a name");
380
+ opts.rubric = next;
381
+ i++;
382
+ } else if (arg === "--rubric-version") {
383
+ if (next === void 0) throw new Error("--rubric-version requires a value");
384
+ opts.rubricVersion = next;
385
+ i++;
386
+ } else if (arg === "--threshold") {
387
+ if (next === void 0) throw new Error("--threshold requires a number");
388
+ const parsed = Number(next);
389
+ if (next.trim() === "" || Number.isNaN(parsed)) {
390
+ throw new Error(`--threshold value ${JSON.stringify(next)} is not a number`);
391
+ }
392
+ opts.threshold = parsed;
393
+ i++;
394
+ } else {
395
+ throw new Error(`unknown calibrate flag: ${arg}`);
396
+ }
397
+ }
398
+ if (!opts.labels) {
399
+ throw new Error("--labels is required");
400
+ }
401
+ return opts;
402
+ }
403
+ function cmdCalibrate(args) {
404
+ let opts;
405
+ try {
406
+ opts = parseCalibrateArgs(args);
407
+ } catch (err) {
408
+ process.stderr.write(`calibrate: ${err.message}
409
+ `);
410
+ process.exit(2);
411
+ return;
412
+ }
413
+ const text = fs.readFileSync(opts.labels, "utf-8");
414
+ const ext = path.extname(opts.labels).toLowerCase();
415
+ let records;
416
+ try {
417
+ if (ext === ".jsonl" || ext === ".ndjson") {
418
+ records = loadLabelsJSONL(text);
419
+ } else if (ext === ".csv") {
420
+ records = loadLabelsCSV(text);
421
+ } else if (text.trimStart().startsWith("{")) {
422
+ records = loadLabelsJSONL(text);
423
+ } else {
424
+ records = loadLabelsCSV(text);
425
+ }
426
+ } catch (err) {
427
+ process.stderr.write(`calibrate: ${err.message}
428
+ `);
429
+ process.exit(1);
430
+ return;
431
+ }
432
+ const pairs = records.filter((r) => r.judgeKnown).map((r) => ({ human: r.humanLabel, judge: r.judgeScore }));
433
+ const missing = records.filter((r) => !r.judgeKnown).length;
434
+ if (pairs.length === 0) {
435
+ process.stderr.write(
436
+ `calibrate: no rows had judge_score (${missing} rows missing); pre-score the labels file first
437
+ `
438
+ );
439
+ process.exit(1);
440
+ return;
441
+ }
442
+ let result;
443
+ try {
444
+ result = computeAgreement(pairs, opts.threshold);
445
+ } catch (err) {
446
+ process.stderr.write(`calibrate: ${err.message}
447
+ `);
448
+ process.exit(1);
449
+ return;
450
+ }
451
+ const output = {
452
+ rubric_name: opts.rubric,
453
+ rubric_version: opts.rubricVersion,
454
+ threshold: result.threshold,
455
+ label_count: result.n,
456
+ agreement: round3(result.agreement),
457
+ cohen_kappa: round3(result.cohenKappa),
458
+ roc_auc: round3(result.rocAuc),
459
+ missing_judge: missing
460
+ };
461
+ process.stdout.write(JSON.stringify(output, null, 2) + "\n");
462
+ }
463
+ function round3(x) {
464
+ return Math.round(x * 1e3) / 1e3;
465
+ }
147
466
  var isDirectExecution = typeof import.meta.url === "string" && import.meta.url.startsWith("file:") && process.argv[1] !== void 0 && import.meta.url.endsWith(path.basename(process.argv[1]));
148
467
  if (isDirectExecution) {
149
468
  main();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli.ts","../../src/version.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI entry point for Attest: npx attest or attest (after global install).\n *\n * Ported from Python sdks/python/src/attest/__main__.py\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { VERSION } from \"./version.js\";\n\nfunction cacheDir(): string {\n const envOverride = process.env[\"ATTEST_CACHE_DIR\"];\n if (envOverride) return envOverride;\n return path.join(os.homedir(), \".attest\", \"cache\");\n}\n\nfunction cacheDbPath(): string {\n return path.join(cacheDir(), \"attest.db\");\n}\n\nfunction cmdCacheStats(): void {\n const dbPath = cacheDbPath();\n const exists = fs.existsSync(dbPath);\n let fileSize = 0;\n if (exists) {\n fileSize = fs.statSync(dbPath).size;\n }\n const stats = { exists, file_size: fileSize, path: dbPath };\n process.stdout.write(JSON.stringify(stats) + \"\\n\");\n}\n\nfunction cmdCacheClear(): void {\n const dbPath = cacheDbPath();\n if (fs.existsSync(dbPath)) {\n fs.unlinkSync(dbPath);\n process.stdout.write(`Cleared cache: ${dbPath}\\n`);\n } else {\n process.stdout.write(`No cache to clear: ${dbPath}\\n`);\n }\n}\n\nconst CONFTEST_TEMPLATE = `import { describe, it, expect } from \"vitest\";\nimport { attestExpect, TraceBuilder } from \"@attest-ai/core\";\n\ndescribe(\"my agent\", () => {\n it(\"returns expected output\", async () => {\n const trace = new TraceBuilder(\"my-agent\")\n .setInput({ user_message: \"hello\" })\n .setOutput({ message: \"world\" })\n .build();\n\n attestExpect(trace).outputContains(\"world\");\n });\n});\n`;\n\nfunction cmdInit(targetDir: string): void {\n const testsDir = path.join(targetDir, \"tests\");\n fs.mkdirSync(testsDir, { recursive: true });\n process.stdout.write(`Directory: ${testsDir}\\n`);\n\n const sampleTestPath = path.join(testsDir, \"test_my_agent.test.ts\");\n if (fs.existsSync(sampleTestPath)) {\n process.stderr.write(`Skipped (exists): ${sampleTestPath}\\n`);\n } else {\n fs.writeFileSync(sampleTestPath, CONFTEST_TEMPLATE);\n process.stdout.write(`Created: ${sampleTestPath}\\n`);\n }\n}\n\nfunction cmdValidate(targetDir: string): void {\n const testsDir = path.join(targetDir, \"tests\");\n\n let testFiles: string[] = [];\n if (fs.existsSync(testsDir)) {\n testFiles = fs\n .readdirSync(testsDir)\n .filter((f) => f.endsWith(\".test.ts\") || f.endsWith(\".test.js\"));\n }\n\n if (testFiles.length === 0) {\n process.stderr.write(\n `Warning: no test files (*.test.ts / *.test.js) found in ${testsDir}.\\n`,\n );\n } else {\n process.stdout.write(`Found ${testFiles.length} test file(s) in ${testsDir}\\n`);\n }\n\n // Check for stale golden traces\n const STALE_DAYS = 30;\n const now = Date.now();\n const goldenFiles = findGoldenFiles(targetDir);\n for (const golden of goldenFiles) {\n const mtime = fs.statSync(golden).mtimeMs;\n const ageDays = Math.floor((now - mtime) / (1000 * 60 * 60 * 24));\n if (ageDays > STALE_DAYS) {\n process.stderr.write(\n `Warning: golden trace ${golden} is ${ageDays} days old (>${STALE_DAYS} days). Consider regenerating.\\n`,\n );\n }\n }\n}\n\nfunction findGoldenFiles(dir: string): string[] {\n const results: string[] = [];\n if (!fs.existsSync(dir)) return results;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory() && entry.name !== \"node_modules\") {\n results.push(...findGoldenFiles(fullPath));\n } else if (entry.isFile() && entry.name.endsWith(\".golden\")) {\n results.push(fullPath);\n }\n }\n return results;\n}\n\nexport function main(argv: string[] = process.argv.slice(2)): void {\n const args = argv;\n\n if (args[0] === \"--version\") {\n process.stdout.write(`attest ${VERSION}\\n`);\n process.exit(0);\n }\n\n if (args[0] === \"cache\" && args.length >= 2) {\n if (args[1] === \"stats\") {\n cmdCacheStats();\n process.exit(0);\n } else if (args[1] === \"clear\") {\n cmdCacheClear();\n process.exit(0);\n } else {\n process.stderr.write(`Unknown cache subcommand: ${args[1]}\\n`);\n process.stderr.write(\"Available: cache stats, cache clear\\n\");\n process.exit(1);\n }\n }\n\n if (args[0] === \"init\") {\n cmdInit(process.cwd());\n process.exit(0);\n }\n\n if (args[0] === \"validate\") {\n cmdValidate(process.cwd());\n process.exit(0);\n }\n\n process.stderr.write(\n \"Usage: attest <command>\\n\\n\" +\n \"Commands:\\n\" +\n \" --version Print version\\n\" +\n \" init Scaffold a test project\\n\" +\n \" validate Validate test suite\\n\" +\n \" cache stats Show cache statistics\\n\" +\n \" cache clear Clear cache database\\n\",\n );\n process.exit(args.length === 0 ? 0 : 1);\n}\n\n// Run when executed directly\nconst isDirectExecution =\n typeof import.meta.url === \"string\" &&\n import.meta.url.startsWith(\"file:\") &&\n process.argv[1] !== undefined &&\n import.meta.url.endsWith(path.basename(process.argv[1]));\n\nif (isDirectExecution) {\n main();\n}\n","export const VERSION = \"0.5.0\";\nexport const ENGINE_VERSION = \"0.5.0\";\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli.ts","../../src/version.ts","../../src/calibration.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI entry point for Attest: npx attest or attest (after global install).\n *\n * Ported from Python sdks/python/src/attest/__main__.py\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { VERSION } from \"./version.js\";\nimport {\n computeAgreement,\n loadLabelsCSV,\n loadLabelsJSONL,\n type LabelPair,\n} from \"./calibration.js\";\n\nfunction cacheDir(): string {\n const envOverride = process.env[\"ATTEST_CACHE_DIR\"];\n if (envOverride) return envOverride;\n return path.join(os.homedir(), \".attest\", \"cache\");\n}\n\nfunction cacheDbPath(): string {\n return path.join(cacheDir(), \"attest.db\");\n}\n\nfunction cmdCacheStats(): void {\n const dbPath = cacheDbPath();\n const exists = fs.existsSync(dbPath);\n let fileSize = 0;\n if (exists) {\n fileSize = fs.statSync(dbPath).size;\n }\n const stats = { exists, file_size: fileSize, path: dbPath };\n process.stdout.write(JSON.stringify(stats) + \"\\n\");\n}\n\nfunction cmdCacheClear(): void {\n const dbPath = cacheDbPath();\n if (fs.existsSync(dbPath)) {\n fs.unlinkSync(dbPath);\n process.stdout.write(`Cleared cache: ${dbPath}\\n`);\n } else {\n process.stdout.write(`No cache to clear: ${dbPath}\\n`);\n }\n}\n\nconst CONFTEST_TEMPLATE = `import { describe, it, expect } from \"vitest\";\nimport { attestExpect, TraceBuilder } from \"@attest-ai/core\";\n\ndescribe(\"my agent\", () => {\n it(\"returns expected output\", async () => {\n const trace = new TraceBuilder(\"my-agent\")\n .setInput({ user_message: \"hello\" })\n .setOutput({ message: \"world\" })\n .build();\n\n attestExpect(trace).outputContains(\"world\");\n });\n});\n`;\n\nfunction cmdInit(targetDir: string): void {\n const testsDir = path.join(targetDir, \"tests\");\n fs.mkdirSync(testsDir, { recursive: true });\n process.stdout.write(`Directory: ${testsDir}\\n`);\n\n const sampleTestPath = path.join(testsDir, \"test_my_agent.test.ts\");\n if (fs.existsSync(sampleTestPath)) {\n process.stderr.write(`Skipped (exists): ${sampleTestPath}\\n`);\n } else {\n fs.writeFileSync(sampleTestPath, CONFTEST_TEMPLATE);\n process.stdout.write(`Created: ${sampleTestPath}\\n`);\n }\n}\n\nfunction cmdValidate(targetDir: string): void {\n const testsDir = path.join(targetDir, \"tests\");\n\n let testFiles: string[] = [];\n if (fs.existsSync(testsDir)) {\n testFiles = fs\n .readdirSync(testsDir)\n .filter((f) => f.endsWith(\".test.ts\") || f.endsWith(\".test.js\"));\n }\n\n if (testFiles.length === 0) {\n process.stderr.write(\n `Warning: no test files (*.test.ts / *.test.js) found in ${testsDir}.\\n`,\n );\n } else {\n process.stdout.write(`Found ${testFiles.length} test file(s) in ${testsDir}\\n`);\n }\n\n // Check for stale golden traces\n const STALE_DAYS = 30;\n const now = Date.now();\n const goldenFiles = findGoldenFiles(targetDir);\n for (const golden of goldenFiles) {\n const mtime = fs.statSync(golden).mtimeMs;\n const ageDays = Math.floor((now - mtime) / (1000 * 60 * 60 * 24));\n if (ageDays > STALE_DAYS) {\n process.stderr.write(\n `Warning: golden trace ${golden} is ${ageDays} days old (>${STALE_DAYS} days). Consider regenerating.\\n`,\n );\n }\n }\n}\n\nfunction findGoldenFiles(dir: string): string[] {\n const results: string[] = [];\n if (!fs.existsSync(dir)) return results;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory() && entry.name !== \"node_modules\") {\n results.push(...findGoldenFiles(fullPath));\n } else if (entry.isFile() && entry.name.endsWith(\".golden\")) {\n results.push(fullPath);\n }\n }\n return results;\n}\n\nexport function main(argv: string[] = process.argv.slice(2)): void {\n const args = argv;\n\n if (args[0] === \"--version\") {\n process.stdout.write(`attest ${VERSION}\\n`);\n process.exit(0);\n }\n\n if (args[0] === \"cache\" && args.length >= 2) {\n if (args[1] === \"stats\") {\n cmdCacheStats();\n process.exit(0);\n } else if (args[1] === \"clear\") {\n cmdCacheClear();\n process.exit(0);\n } else {\n process.stderr.write(`Unknown cache subcommand: ${args[1]}\\n`);\n process.stderr.write(\"Available: cache stats, cache clear\\n\");\n process.exit(1);\n }\n }\n\n if (args[0] === \"init\") {\n cmdInit(process.cwd());\n process.exit(0);\n }\n\n if (args[0] === \"validate\") {\n cmdValidate(process.cwd());\n process.exit(0);\n }\n\n if (args[0] === \"calibrate\") {\n cmdCalibrate(args.slice(1));\n process.exit(0);\n }\n\n process.stderr.write(\n \"Usage: attest <command>\\n\\n\" +\n \"Commands:\\n\" +\n \" --version Print version\\n\" +\n \" init Scaffold a test project\\n\" +\n \" validate Validate test suite\\n\" +\n \" cache stats Show cache statistics\\n\" +\n \" cache clear Clear cache database\\n\" +\n \" calibrate Compute judge agreement metrics over labeled data\\n\",\n );\n process.exit(args.length === 0 ? 0 : 1);\n}\n\ninterface CalibrateOptions {\n labels: string;\n rubric: string;\n rubricVersion: string;\n threshold: number;\n}\n\nfunction parseCalibrateArgs(args: string[]): CalibrateOptions {\n const opts: CalibrateOptions = {\n labels: \"\",\n rubric: \"default\",\n rubricVersion: \"\",\n threshold: 0.5,\n };\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n const next = args[i + 1];\n if (arg === \"--labels\") {\n if (next === undefined) throw new Error(\"--labels requires a path\");\n opts.labels = next;\n i++;\n } else if (arg === \"--rubric\") {\n if (next === undefined) throw new Error(\"--rubric requires a name\");\n opts.rubric = next;\n i++;\n } else if (arg === \"--rubric-version\") {\n if (next === undefined) throw new Error(\"--rubric-version requires a value\");\n opts.rubricVersion = next;\n i++;\n } else if (arg === \"--threshold\") {\n if (next === undefined) throw new Error(\"--threshold requires a number\");\n const parsed = Number(next);\n if (next.trim() === \"\" || Number.isNaN(parsed)) {\n throw new Error(`--threshold value ${JSON.stringify(next)} is not a number`);\n }\n opts.threshold = parsed;\n i++;\n } else {\n throw new Error(`unknown calibrate flag: ${arg}`);\n }\n }\n if (!opts.labels) {\n throw new Error(\"--labels is required\");\n }\n return opts;\n}\n\nfunction cmdCalibrate(args: string[]): void {\n let opts: CalibrateOptions;\n try {\n opts = parseCalibrateArgs(args);\n } catch (err) {\n process.stderr.write(`calibrate: ${(err as Error).message}\\n`);\n process.exit(2);\n return;\n }\n\n const text = fs.readFileSync(opts.labels, \"utf-8\");\n const ext = path.extname(opts.labels).toLowerCase();\n let records;\n try {\n if (ext === \".jsonl\" || ext === \".ndjson\") {\n records = loadLabelsJSONL(text);\n } else if (ext === \".csv\") {\n records = loadLabelsCSV(text);\n } else if (text.trimStart().startsWith(\"{\")) {\n records = loadLabelsJSONL(text);\n } else {\n records = loadLabelsCSV(text);\n }\n } catch (err) {\n process.stderr.write(`calibrate: ${(err as Error).message}\\n`);\n process.exit(1);\n return;\n }\n\n const pairs: LabelPair[] = records\n .filter((r) => r.judgeKnown)\n .map((r) => ({ human: r.humanLabel, judge: r.judgeScore }));\n const missing = records.filter((r) => !r.judgeKnown).length;\n if (pairs.length === 0) {\n process.stderr.write(\n `calibrate: no rows had judge_score (${missing} rows missing); pre-score the labels file first\\n`,\n );\n process.exit(1);\n return;\n }\n\n let result;\n try {\n result = computeAgreement(pairs, opts.threshold);\n } catch (err) {\n process.stderr.write(`calibrate: ${(err as Error).message}\\n`);\n process.exit(1);\n return;\n }\n\n const output = {\n rubric_name: opts.rubric,\n rubric_version: opts.rubricVersion,\n threshold: result.threshold,\n label_count: result.n,\n agreement: round3(result.agreement),\n cohen_kappa: round3(result.cohenKappa),\n roc_auc: round3(result.rocAuc),\n missing_judge: missing,\n };\n process.stdout.write(JSON.stringify(output, null, 2) + \"\\n\");\n}\n\nfunction round3(x: number): number {\n return Math.round(x * 1000) / 1000;\n}\n\n// Run when executed directly\nconst isDirectExecution =\n typeof import.meta.url === \"string\" &&\n import.meta.url.startsWith(\"file:\") &&\n process.argv[1] !== undefined &&\n import.meta.url.endsWith(path.basename(process.argv[1]));\n\nif (isDirectExecution) {\n main();\n}\n","export const VERSION = \"0.9.0\";\nexport const ENGINE_VERSION = \"0.9.0\";\n","/**\n * Calibration helpers — TS parity with engine/internal/assertion/judge/calibration.go\n * and sdks/python/src/attest/calibration.py.\n *\n * Computes Cohen's κ, agreement %, and ROC-AUC over (human, judge) score\n * pairs and parses CSV/JSONL labels files. Metrics match the engine\n * byte-for-byte on the same input.\n */\n\nimport { createHash } from \"node:crypto\";\n\nexport interface LabelPair {\n readonly human: number;\n readonly judge: number;\n}\n\nexport interface AgreementResult {\n readonly threshold: number;\n readonly n: number;\n readonly agreement: number;\n readonly cohenKappa: number;\n readonly rocAuc: number;\n}\n\nexport interface LabeledRecord {\n input: string;\n humanLabel: number;\n judgeScore: number;\n judgeKnown: boolean;\n}\n\n/**\n * Compute Cohen's κ, agreement %, and ROC-AUC over labeled pairs.\n *\n * Throws when pairs is empty or threshold is outside (0, 1). Cohen's κ\n * degenerates to 0 when all labels land in a single class for both\n * raters; ROC-AUC is reported as 0 when only one human-class is\n * present.\n */\nexport function computeAgreement(\n pairs: readonly LabelPair[],\n threshold: number,\n): AgreementResult {\n if (pairs.length === 0) {\n throw new Error(\"no labeled pairs available\");\n }\n if (threshold <= 0 || threshold >= 1) {\n throw new Error(\"threshold must be in (0, 1)\");\n }\n\n const n = pairs.length;\n let humanPos = 0;\n let judgePos = 0;\n let bothPos = 0;\n let bothNeg = 0;\n for (const p of pairs) {\n const hPos = p.human >= threshold;\n const jPos = p.judge >= threshold;\n if (hPos) humanPos++;\n if (jPos) judgePos++;\n if (hPos && jPos) bothPos++;\n if (!hPos && !jPos) bothNeg++;\n }\n const humanNeg = n - humanPos;\n const judgeNeg = n - judgePos;\n\n const agreement = (bothPos + bothNeg) / n;\n const expected =\n (humanPos * judgePos + humanNeg * judgeNeg) / (n * n);\n const cohenKappa =\n expected < 1 ? (agreement - expected) / (1 - expected) : 0;\n\n const rocAuc = rocAUC(pairs, threshold);\n\n return { threshold, n, agreement, cohenKappa, rocAuc };\n}\n\nfunction rocAUC(pairs: readonly LabelPair[], threshold: number): number {\n const rows: { score: number; pos: boolean }[] = pairs.map((p) => ({\n score: p.judge,\n pos: p.human >= threshold,\n }));\n const pos = rows.filter((r) => r.pos).length;\n const neg = rows.length - pos;\n if (pos === 0 || neg === 0) return 0;\n\n rows.sort((a, b) => a.score - b.score);\n\n let rankSumPos = 0;\n let i = 0;\n while (i < rows.length) {\n let j = i;\n while (j < rows.length && rows[j]!.score === rows[i]!.score) {\n j++;\n }\n const avgRank = (i + j + 1) / 2;\n for (let k = i; k < j; k++) {\n if (rows[k]!.pos) rankSumPos += avgRank;\n }\n i = j;\n }\n\n const auc = (rankSumPos - (pos * (pos + 1)) / 2) / (pos * neg);\n if (Number.isNaN(auc) || !Number.isFinite(auc)) return 0;\n return auc;\n}\n\n/**\n * Parse a 2- or 3-column CSV (input, human_label[, judge_score]).\n * Header detected when row[0][1] does not parse as a float. Lines\n * beginning with `#` are skipped.\n */\nexport function loadLabelsCSV(text: string): LabeledRecord[] {\n const rows = parseCSV(text);\n if (rows.length === 0) {\n throw new Error(\"empty CSV\");\n }\n let start = 0;\n if (rows[0]!.length >= 2) {\n const firstHuman = parseFloatStrict(rows[0]![1]!);\n if (firstHuman === null) start = 1;\n }\n const out: LabeledRecord[] = [];\n for (let idx = start; idx < rows.length; idx++) {\n const row = rows[idx]!;\n const lineNum = idx + 1;\n if (row.length === 0) continue;\n if (row[0]!.trimStart().startsWith(\"#\")) continue;\n if (row.length < 2) {\n throw new Error(\n `CSV line ${lineNum}: want at least 2 columns (input, human_label)`,\n );\n }\n const human = parseFloatStrict(row[1]!);\n if (human === null) {\n throw new Error(`CSV line ${lineNum}: human_label not a float: ${row[1]}`);\n }\n const rec: LabeledRecord = {\n input: row[0]!,\n humanLabel: human,\n judgeScore: 0,\n judgeKnown: false,\n };\n if (row.length >= 3 && row[2]!.trim() !== \"\") {\n const judge = parseFloatStrict(row[2]!);\n if (judge === null) {\n throw new Error(\n `CSV line ${lineNum}: judge_score not a float: ${row[2]}`,\n );\n }\n rec.judgeScore = judge;\n rec.judgeKnown = true;\n }\n out.push(rec);\n }\n if (out.length === 0) {\n throw new Error(\"CSV contained no labeled rows\");\n }\n return out;\n}\n\n/**\n * Parse a newline-delimited JSON file. Each line is an object with the\n * shape {\"input\": \"...\", \"human_label\": 0.9, \"judge_score\": 0.8}.\n * `judge_score` is optional.\n */\nexport function loadLabelsJSONL(text: string): LabeledRecord[] {\n const out: LabeledRecord[] = [];\n const lines = text.split(/\\r?\\n/);\n for (let i = 0; i < lines.length; i++) {\n const stripped = lines[i]!.trim();\n if (!stripped) continue;\n let obj: Record<string, unknown>;\n try {\n obj = JSON.parse(stripped) as Record<string, unknown>;\n } catch (err) {\n throw new Error(`JSONL line ${i + 1}: ${(err as Error).message}`);\n }\n if (Object.keys(obj).length === 0) continue;\n if (obj[\"human_label\"] === undefined) {\n throw new Error(`JSONL line ${i + 1}: missing human_label`);\n }\n const human = parseJSONNumber(obj[\"human_label\"]);\n if (human === null) {\n throw new Error(`JSONL line ${i + 1}: human_label is not a number`);\n }\n const rec: LabeledRecord = {\n input: typeof obj[\"input\"] === \"string\" ? obj[\"input\"] : \"\",\n humanLabel: human,\n judgeScore: 0,\n judgeKnown: false,\n };\n if (obj[\"judge_score\"] !== undefined && obj[\"judge_score\"] !== null) {\n const judge = parseJSONNumber(obj[\"judge_score\"]);\n if (judge === null) {\n throw new Error(`JSONL line ${i + 1}: judge_score is not a number`);\n }\n rec.judgeScore = judge;\n rec.judgeKnown = true;\n }\n out.push(rec);\n }\n if (out.length === 0) {\n throw new Error(\"JSONL contained no labeled rows\");\n }\n return out;\n}\n\n/**\n * Return the 16-character SHA-256 prefix used by the engine for prompt\n * hashes. Mirrors promptHash in engine/internal/assertion/judge_eval.go\n * so SDK-recorded calibration rows align with JudgeMetadata.prompt_hash.\n */\nexport function promptHash(text: string): string {\n return createHash(\"sha256\").update(text).digest(\"hex\").slice(0, 16);\n}\n\n/**\n * Parse a string as a number, rejecting empty input and values that\n * JavaScript's `Number()` would silently coerce to 0. Returns null on\n * rejection so callers can throw a useful error. Matches Go's\n * strconv.ParseFloat and Python's float() — both error on the empty\n * string, while bare `Number(\"\")` returns 0.\n *\n * Permits \"Infinity\" so the loader stays consistent with strconv.ParseFloat;\n * downstream consumers (CalibrationStore.Record, computeAgreement) handle\n * the [0, 1] range check separately.\n */\nfunction parseFloatStrict(raw: string): number | null {\n const trimmed = raw.trim();\n if (trimmed === \"\") return null;\n const n = Number(trimmed);\n if (Number.isNaN(n)) return null;\n return n;\n}\n\n/**\n * Coerce a JSON value to a finite number. Accepts only `number` (rejects\n * stringly-typed numbers and booleans) so calibration files cannot\n * accidentally smuggle non-numeric labels past type-loose JSON parsing.\n */\nfunction parseJSONNumber(value: unknown): number | null {\n if (typeof value !== \"number\") return null;\n if (Number.isNaN(value)) return null;\n return value;\n}\n\n/**\n * Minimal RFC-4180-ish CSV parser. Handles quoted fields with embedded\n * commas and CRLF line endings. Sufficient for calibration label files;\n * callers needing full CSV support should bring their own parser.\n */\nfunction parseCSV(text: string): string[][] {\n const rows: string[][] = [];\n let current: string[] = [];\n let field = \"\";\n let inQuotes = false;\n let i = 0;\n while (i < text.length) {\n const ch = text[i]!;\n if (inQuotes) {\n if (ch === '\"') {\n if (text[i + 1] === '\"') {\n field += '\"';\n i += 2;\n continue;\n }\n inQuotes = false;\n i++;\n continue;\n }\n field += ch;\n i++;\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n i++;\n continue;\n }\n if (ch === \",\") {\n current.push(field);\n field = \"\";\n i++;\n continue;\n }\n if (ch === \"\\n\") {\n current.push(field);\n rows.push(current);\n current = [];\n field = \"\";\n i++;\n continue;\n }\n if (ch === \"\\r\") {\n i++;\n continue;\n }\n field += ch;\n i++;\n }\n if (field.length > 0 || current.length > 0) {\n current.push(field);\n rows.push(current);\n }\n return rows;\n}\n"],"mappings":"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","names":[]}
@@ -1,12 +1,12 @@
1
- import { InitializeResult, Trace, Assertion, EvaluateBatchResult, Step, AssertionResult } from './proto/index.js';
2
- export { AgentId, AssertionId, AssertionSpec, ConstraintSpec, ContentSpec, ERR_ASSERTION_ERROR, ERR_ENGINE_ERROR, ERR_INVALID_TRACE, ERR_PROVIDER_ERROR, ERR_SESSION_ERROR, ERR_TIMEOUT, EmbeddingSpec, EngineTimeoutError, ErrorData, EvaluateBatchParams, InitializeParams, LlmJudgeSpec, PluginSpec, ProtocolError, RPCError, RPCRequest, RPCResponse, STATUS_HARD_FAIL, STATUS_PASS, STATUS_SOFT_FAIL, STEP_AGENT_CALL, STEP_LLM_CALL, STEP_RETRIEVAL, STEP_TOOL_CALL, SchemaSpec, ShutdownResult, SubmitPluginResultParams, TYPE_CONSTRAINT, TYPE_CONTENT, TYPE_EMBEDDING, TYPE_LLM_JUDGE, TYPE_PLUGIN, TYPE_SCHEMA, TYPE_TRACE, TYPE_TRACE_TREE, TraceId, TraceMetadata, TraceSpec, TraceTreeSpec, agentId, assertionId, decodeResponse, encodeRequest, extractId, extractResult, traceId } from './proto/index.js';
1
+ import { InitializeResult, ProtocolLogger, ProtocolDiagnostic, Trace, Assertion, EvaluateBatchResult, DriftReport, SimulatePersona, ConversationMessage, SimulateFaultConfig, Step, AssertionResult } from './proto/index.js';
2
+ export { AgentId, AssertionId, AssertionSpec, BiasProbe, ConstraintSpec, ContentSpec, ERR_ASSERTION_ERROR, ERR_ENGINE_ERROR, ERR_INVALID_TRACE, ERR_PROVIDER_ERROR, ERR_SESSION_ERROR, ERR_TIMEOUT, EmbeddingSpec, EngineTimeoutError, ErrorData, EvaluateBatchParams, GenerateUserMessageParams, GenerateUserMessageResult, InitializeParams, JudgeAgreement, JudgeMetadata, LlmJudgeSpec, PluginSpec, ProtocolDesyncError, ProtocolDiagnosticBuffer, ProtocolDiagnosticKind, ProtocolError, QueryDriftParams, QueryDriftResult, RPCError, RPCRequest, RPCResponse, STATUS_HARD_FAIL, STATUS_PASS, STATUS_SOFT_FAIL, STEP_AGENT_CALL, STEP_LLM_CALL, STEP_RETRIEVAL, STEP_TOOL_CALL, SchemaSpec, ShutdownResult, SubmitPluginResultParams, TYPE_CONSTRAINT, TYPE_CONTENT, TYPE_EMBEDDING, TYPE_LLM_JUDGE, TYPE_PLUGIN, TYPE_SCHEMA, TYPE_TRACE, TYPE_TRACE_TREE, TraceId, TraceMetadata, TraceSpec, TraceTreeSpec, agentId, assertionId, decodeResponse, encodeRequest, extractId, extractResult, layerForType, previewLine, traceId } from './proto/index.js';
3
3
  import { ChildProcess } from 'node:child_process';
4
4
  import { Interface } from 'node:readline';
5
5
  import { AsyncLocalStorage } from 'node:async_hooks';
6
6
  import { EventEmitter } from 'node:events';
7
7
 
8
- declare const VERSION = "0.5.0";
9
- declare const ENGINE_VERSION = "0.5.0";
8
+ declare const VERSION = "0.9.0";
9
+ declare const ENGINE_VERSION = "0.9.0";
10
10
 
11
11
  declare class EngineManager {
12
12
  private enginePath;
@@ -31,6 +31,28 @@ declare class EngineManager {
31
31
  get readlineInterface(): Interface | null;
32
32
  }
33
33
 
34
+ interface AttestClientOptions {
35
+ /** Logger override. When omitted, warnings only emit if ATTEST_DEBUG_PROTOCOL=1. */
36
+ logger?: ProtocolLogger;
37
+ /** Listener invoked for every protocol diagnostic. */
38
+ onDiagnostic?: (diagnostic: ProtocolDiagnostic) => void;
39
+ /** Diagnostic ring-buffer capacity. Default 32. */
40
+ diagnosticBufferSize?: number;
41
+ /** Number of diagnostics in `desyncWindowMs` that triggers desync rejection. Default 3. */
42
+ desyncThreshold?: number;
43
+ /** Window length for desync detection in milliseconds. Default 1000. */
44
+ desyncWindowMs?: number;
45
+ }
46
+ /**
47
+ * AttestClient owns request/response correlation over the engine's
48
+ * NDJSON protocol.
49
+ *
50
+ * Desync detection is a one-way latch: once the diagnostic rate breaches
51
+ * `desyncThreshold` within `desyncWindowMs` the client refuses further
52
+ * sends for the lifetime of the instance. There is no reset hook by
53
+ * design — recovery requires constructing a new client over a fresh
54
+ * engine subprocess.
55
+ */
34
56
  declare class AttestClient {
35
57
  private readonly engine;
36
58
  private requestId;
@@ -38,10 +60,23 @@ declare class AttestClient {
38
60
  private writeChain;
39
61
  private readerActive;
40
62
  private lineHandler;
41
- constructor(engine: EngineManager);
63
+ private readonly logger;
64
+ private readonly diagnosticBuffer;
65
+ private readonly diagnosticListeners;
66
+ private readonly desyncThreshold;
67
+ private readonly desyncWindowMs;
68
+ private desynced;
69
+ constructor(engine: EngineManager, options?: AttestClientOptions);
70
+ /** Subscribe to protocol diagnostics. Returns an unsubscribe function. */
71
+ onProtocolDiagnostic(listener: (diagnostic: ProtocolDiagnostic) => void): () => void;
72
+ /** Snapshot of the diagnostic ring buffer (most recent last). */
73
+ protocolDiagnostics(): readonly ProtocolDiagnostic[];
42
74
  startReader(): void;
43
75
  stopReader(): void;
76
+ private recordDiagnostic;
77
+ private maybeTriggerDesync;
44
78
  private handleLine;
79
+ private handleDecodeFailure;
45
80
  private failAll;
46
81
  sendRequest(method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
47
82
  evaluateBatch(trace: Trace, assertions: readonly Assertion[], options?: {
@@ -50,6 +85,12 @@ declare class AttestClient {
50
85
  submitPluginResult(traceId: string, pluginName: string, assertionId: string, status: string, score: number, explanation: string, options?: {
51
86
  timeout?: number;
52
87
  }): Promise<boolean>;
88
+ queryDrift(assertionId: string, windowSize?: number, options?: {
89
+ timeout?: number;
90
+ }): Promise<DriftReport>;
91
+ generateUserMessage(persona: SimulatePersona, conversationHistory: readonly ConversationMessage[], faultConfig?: SimulateFaultConfig, options?: {
92
+ timeout?: number;
93
+ }): Promise<string>;
53
94
  }
54
95
 
55
96
  declare class TraceBuilder {
@@ -106,6 +147,29 @@ declare class AgentResult {
106
147
  get failCount(): number;
107
148
  }
108
149
 
150
+ /**
151
+ * Terminal-grade rendering of Attest assertion failures.
152
+ *
153
+ * Mirrors the Python attest.cli.diagnostics module so PR comments,
154
+ * pytest output, and vitest output show the same fields. Use as the
155
+ * message argument to a vitest `expect(result.passed, ...)` so
156
+ * reviewers see trace path, expected vs actual, and judge metadata.
157
+ */
158
+
159
+ interface RenderOptions {
160
+ /** Emit ANSI colour escapes. Default false (plain text). */
161
+ color?: boolean;
162
+ /** Source location annotation appended after each block. */
163
+ testFile?: string;
164
+ /** Render passing assertions in addition to failures. Default false. */
165
+ includePassing?: boolean;
166
+ }
167
+ declare function renderAssertionFailure(result: AssertionResult, options?: RenderOptions): string;
168
+ declare function renderSummary(result: AgentResult, options?: {
169
+ color?: boolean;
170
+ }): string;
171
+ declare function renderDiagnostics(result: AgentResult, options?: RenderOptions): string;
172
+
109
173
  declare class TraceTree {
110
174
  readonly root: Trace;
111
175
  constructor(root: Trace);
@@ -517,7 +581,7 @@ declare class ExpectChain {
517
581
  * ```
518
582
  */
519
583
  outputSimilarTo(reference: string, opts?: {
520
- threshold?: number;
584
+ threshold?: number | "dynamic";
521
585
  model?: string;
522
586
  soft?: boolean;
523
587
  }): this;
@@ -538,7 +602,7 @@ declare class ExpectChain {
538
602
  */
539
603
  passesJudge(criteria: string, opts?: {
540
604
  rubric?: string;
541
- threshold?: number;
605
+ threshold?: number | "dynamic";
542
606
  model?: string;
543
607
  soft?: boolean;
544
608
  }): this;
@@ -805,21 +869,36 @@ declare function tier(level: number): <T extends (...args: never[]) => unknown>(
805
869
  * @param options.simulation - Enable simulation mode. All `evaluateBatch` calls
806
870
  * return deterministic pass results without spawning the engine or calling any
807
871
  * LLM. Equivalent to setting `ATTEST_SIMULATION=1` in the environment.
872
+ * @param options.sampleRate - Sampling rate for continuous evaluation (0.0–1.0).
873
+ * Equivalent to setting `ATTEST_SAMPLE_RATE` in the environment.
808
874
  */
809
875
  declare function config(options: {
810
876
  simulation?: boolean;
877
+ sampleRate?: number;
811
878
  }): void;
812
879
  /**
813
880
  * Returns true when simulation mode is active via either the programmatic API
814
881
  * or the `ATTEST_SIMULATION` environment variable.
815
882
  */
816
883
  declare function isSimulationMode(): boolean;
884
+ /**
885
+ * Returns the sampling rate for continuous evaluation. Priority:
886
+ * programmatic `config({ sampleRate })` > `ATTEST_SAMPLE_RATE` env var > 0.
887
+ */
888
+ declare function getSampleRate(): number;
817
889
  /**
818
890
  * Reset all programmatic config to defaults. Primarily for use in tests.
819
891
  * Environment variables are not affected.
820
892
  */
821
893
  declare function resetConfig(): void;
822
894
 
895
+ /**
896
+ * Return a deterministic all-pass result for every assertion without
897
+ * contacting the engine or any LLM provider. Used when simulation mode
898
+ * is active (`ATTEST_SIMULATION=1` or `config({ simulation: true })`).
899
+ */
900
+ declare function simulationEvaluateBatch(assertions: readonly Assertion[]): EvaluateBatchResult;
901
+
823
902
  interface PluginResult {
824
903
  readonly status: string;
825
904
  readonly score: number;
@@ -882,6 +961,64 @@ declare class ContinuousEvalRunner {
882
961
  private scheduleNext;
883
962
  }
884
963
 
964
+ /**
965
+ * Calibration helpers — TS parity with engine/internal/assertion/judge/calibration.go
966
+ * and sdks/python/src/attest/calibration.py.
967
+ *
968
+ * Computes Cohen's κ, agreement %, and ROC-AUC over (human, judge) score
969
+ * pairs and parses CSV/JSONL labels files. Metrics match the engine
970
+ * byte-for-byte on the same input.
971
+ */
972
+ interface LabelPair {
973
+ readonly human: number;
974
+ readonly judge: number;
975
+ }
976
+ interface AgreementResult {
977
+ readonly threshold: number;
978
+ readonly n: number;
979
+ readonly agreement: number;
980
+ readonly cohenKappa: number;
981
+ readonly rocAuc: number;
982
+ }
983
+ interface LabeledRecord {
984
+ input: string;
985
+ humanLabel: number;
986
+ judgeScore: number;
987
+ judgeKnown: boolean;
988
+ }
989
+ /**
990
+ * Compute Cohen's κ, agreement %, and ROC-AUC over labeled pairs.
991
+ *
992
+ * Throws when pairs is empty or threshold is outside (0, 1). Cohen's κ
993
+ * degenerates to 0 when all labels land in a single class for both
994
+ * raters; ROC-AUC is reported as 0 when only one human-class is
995
+ * present.
996
+ */
997
+ declare function computeAgreement(pairs: readonly LabelPair[], threshold: number): AgreementResult;
998
+ /**
999
+ * Parse a 2- or 3-column CSV (input, human_label[, judge_score]).
1000
+ * Header detected when row[0][1] does not parse as a float. Lines
1001
+ * beginning with `#` are skipped.
1002
+ */
1003
+ declare function loadLabelsCSV(text: string): LabeledRecord[];
1004
+ /**
1005
+ * Parse a newline-delimited JSON file. Each line is an object with the
1006
+ * shape {"input": "...", "human_label": 0.9, "judge_score": 0.8}.
1007
+ * `judge_score` is optional.
1008
+ */
1009
+ declare function loadLabelsJSONL(text: string): LabeledRecord[];
1010
+ /**
1011
+ * Return the 16-character SHA-256 prefix used by the engine for prompt
1012
+ * hashes. Mirrors promptHash in engine/internal/assertion/judge_eval.go
1013
+ * so SDK-recorded calibration rows align with JudgeMetadata.prompt_hash.
1014
+ */
1015
+ declare function promptHash(text: string): string;
1016
+
1017
+ declare const FRIENDLY_USER: SimulatePersona;
1018
+ declare const ADVERSARIAL_USER: SimulatePersona;
1019
+ declare const CONFUSED_USER: SimulatePersona;
1020
+ declare const COOPERATIVE_USER: SimulatePersona;
1021
+
885
1022
  declare class ManualAdapter {
886
1023
  private readonly agentId;
887
1024
  constructor(agentId?: string);
@@ -1082,4 +1219,4 @@ interface TraceAdapter {
1082
1219
  traceFromResponse(response: unknown, options?: Record<string, unknown>): Trace;
1083
1220
  }
1084
1221
 
1085
- export { Agent, AgentResult, AlertDispatcher, type AlertPayload, AnthropicAdapter, Assertion, AssertionResult, AttestClient, type AttestPlugin, ContinuousEvalRunner, ENGINE_VERSION, EngineManager, EvaluateBatchResult, ExpectChain, GeminiAdapter, InitializeResult, LangChainAdapter, ManualAdapter, OTelAdapter, OllamaAdapter, OpenAIAdapter, PluginRegistry, type PluginResult, Sampler, Step, TIER_1, TIER_2, TIER_3, Trace, type TraceAdapter, TraceBuilder, TraceTree, VERSION, activeBuilder, agent, attestExpect, config, delegate, executePluginAssertion, isSimulationMode, resetConfig, tier };
1222
+ export { ADVERSARIAL_USER, Agent, AgentResult, type AgreementResult, AlertDispatcher, type AlertPayload, AnthropicAdapter, Assertion, AssertionResult, AttestClient, type AttestClientOptions, type AttestPlugin, CONFUSED_USER, COOPERATIVE_USER, ContinuousEvalRunner, ConversationMessage, DriftReport, ENGINE_VERSION, EngineManager, EvaluateBatchResult, ExpectChain, FRIENDLY_USER, GeminiAdapter, InitializeResult, type LabelPair, type LabeledRecord, LangChainAdapter, ManualAdapter, OTelAdapter, OllamaAdapter, OpenAIAdapter, PluginRegistry, type PluginResult, ProtocolDiagnostic, ProtocolLogger, type RenderOptions, Sampler, SimulateFaultConfig, SimulatePersona, Step, TIER_1, TIER_2, TIER_3, Trace, type TraceAdapter, TraceBuilder, TraceTree, VERSION, activeBuilder, agent, attestExpect, computeAgreement, config, delegate, executePluginAssertion, getSampleRate, isSimulationMode, loadLabelsCSV, loadLabelsJSONL, promptHash, renderAssertionFailure, renderDiagnostics, renderSummary, resetConfig, simulationEvaluateBatch, tier };