@attest-ai/core 1.0.1 → 1.1.0-rc.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/dist/cjs/index.cjs +1033 -273
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +324 -101
- package/dist/cjs/proto/index.cjs +8 -1
- package/dist/cjs/proto/index.cjs.map +1 -1
- package/dist/cjs/proto/index.d.cts +17 -1
- package/dist/esm/cli.d.ts +3 -1
- package/dist/esm/cli.js +219 -80
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/index.d.ts +324 -101
- package/dist/esm/index.js +1026 -278
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/proto/index.d.ts +17 -1
- package/dist/esm/proto/index.js +6 -1
- package/dist/esm/proto/index.js.map +1 -1
- package/engine-checksums.json +10 -0
- package/package.json +4 -2
package/dist/esm/cli.js
CHANGED
|
@@ -3,30 +3,34 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5
5
|
import * as fs4 from "fs";
|
|
6
|
-
import * as
|
|
6
|
+
import * as os3 from "os";
|
|
7
7
|
import * as path4 from "path";
|
|
8
|
-
import { fileURLToPath as
|
|
8
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9
9
|
|
|
10
10
|
// src/version.ts
|
|
11
|
-
var VERSION = "1.0.1";
|
|
12
|
-
var ENGINE_VERSION = "1.0.1";
|
|
11
|
+
var VERSION = "1.1.0-rc.1";
|
|
12
|
+
var ENGINE_VERSION = "1.1.0-rc.1";
|
|
13
13
|
|
|
14
14
|
// src/engine-manager.ts
|
|
15
15
|
import { spawn, spawnSync } from "child_process";
|
|
16
16
|
import { createInterface } from "readline";
|
|
17
|
-
import { existsSync, accessSync, statSync as statSync2, constants as fsConstants } from "fs";
|
|
17
|
+
import { existsSync as existsSync2, accessSync, statSync as statSync2, constants as fsConstants } from "fs";
|
|
18
18
|
import { delimiter, join as join2 } from "path";
|
|
19
19
|
|
|
20
20
|
// src/engine-downloader.ts
|
|
21
21
|
import * as https from "https";
|
|
22
|
-
import * as http from "http";
|
|
23
22
|
import * as fs from "fs";
|
|
24
23
|
import * as path from "path";
|
|
25
24
|
import * as os from "os";
|
|
26
25
|
import * as crypto from "crypto";
|
|
26
|
+
import { fileURLToPath } from "url";
|
|
27
27
|
var RELEASE_BASE = `https://github.com/attest-framework/attest/releases/download/v${ENGINE_VERSION}`;
|
|
28
|
-
var
|
|
28
|
+
var TABLE_NAME = "engine-checksums.json";
|
|
29
29
|
var MAX_REDIRECTS = 5;
|
|
30
|
+
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
31
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
32
|
+
var DEFAULT_TOTAL_TIMEOUT_MS = 12e4;
|
|
33
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
30
34
|
function versionedBinDir() {
|
|
31
35
|
return path.join(os.homedir(), ".attest", "bin", ENGINE_VERSION);
|
|
32
36
|
}
|
|
@@ -59,73 +63,131 @@ function cachedEnginePath() {
|
|
|
59
63
|
const binPath = path.join(versionedBinDir(), binaryFilename());
|
|
60
64
|
return fs.statSync(binPath, { throwIfNoEntry: false })?.isFile() ? binPath : null;
|
|
61
65
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
if (match) {
|
|
69
|
-
checksums.set(match[2], match[1]);
|
|
70
|
-
}
|
|
66
|
+
function packageRoot() {
|
|
67
|
+
let dir = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
|
68
|
+
while (!fs.existsSync(path.join(dir, "package.json"))) {
|
|
69
|
+
const parent = path.dirname(dir);
|
|
70
|
+
if (parent === dir) return dir;
|
|
71
|
+
dir = parent;
|
|
71
72
|
}
|
|
72
|
-
return
|
|
73
|
+
return dir;
|
|
73
74
|
}
|
|
74
|
-
function
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
function embeddedHash(key, tablePath) {
|
|
76
|
+
if (!fs.existsSync(tablePath)) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`This @attest-ai/core install has no embedded engine hash table (${TABLE_NAME}), which is how a source or workspace install looks, so it never downloads an engine. Point ATTEST_ENGINE_PATH at an engine binary, or build one with \`make engine\` and set ATTEST_ENGINE_PATH=./bin/attest-engine.`
|
|
78
79
|
);
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
let hash;
|
|
82
|
+
try {
|
|
83
|
+
const table = JSON.parse(fs.readFileSync(tablePath, "utf-8"));
|
|
84
|
+
hash = table[ENGINE_VERSION]?.[key];
|
|
85
|
+
} catch (err) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`@attest-ai/core ${VERSION} ships an unreadable engine hash table at ${tablePath}: ${String(err)}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (typeof hash !== "string" || !SHA256_HEX.test(hash)) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`@attest-ai/core ${VERSION} ships no valid engine hash for v${ENGINE_VERSION} on ${key} in ${tablePath}.`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return hash;
|
|
96
|
+
}
|
|
97
|
+
function httpsGet(url, o) {
|
|
98
|
+
return new Promise((resolve3, reject) => {
|
|
99
|
+
let current;
|
|
100
|
+
let done = false;
|
|
101
|
+
const finish = (err, data) => {
|
|
102
|
+
if (done) return;
|
|
103
|
+
done = true;
|
|
104
|
+
clearTimeout(deadline);
|
|
105
|
+
if (err) {
|
|
106
|
+
current?.destroy();
|
|
107
|
+
reject(err);
|
|
108
|
+
} else {
|
|
109
|
+
resolve3(data);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const deadline = setTimeout(
|
|
113
|
+
() => finish(new Error(`Timed out after ${o.totalTimeoutMs}ms fetching ${url}`)),
|
|
114
|
+
o.totalTimeoutMs
|
|
115
|
+
);
|
|
116
|
+
const hop = (target, redirects) => {
|
|
117
|
+
if (redirects > MAX_REDIRECTS) {
|
|
118
|
+
finish(new Error(`Too many redirects (>${MAX_REDIRECTS}) following ${url}`));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
let parsed;
|
|
122
|
+
try {
|
|
123
|
+
parsed = new URL(target);
|
|
124
|
+
} catch {
|
|
125
|
+
finish(new Error(`Invalid URL: ${target}`));
|
|
92
126
|
return;
|
|
93
127
|
}
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
reject(new Error(`HTTP ${status} fetching ${url}`));
|
|
128
|
+
if (parsed.protocol !== "https:") {
|
|
129
|
+
finish(new Error(`Refusing to download over a non-https URL: ${target}`));
|
|
97
130
|
return;
|
|
98
131
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
132
|
+
current = https.get(parsed, { ca: o.ca, timeout: o.connectTimeoutMs }, (res) => {
|
|
133
|
+
const status = res.statusCode ?? 0;
|
|
134
|
+
if (REDIRECT_STATUSES.has(status)) {
|
|
135
|
+
res.resume();
|
|
136
|
+
const location = res.headers.location;
|
|
137
|
+
if (!location) {
|
|
138
|
+
finish(new Error(`Redirect ${status} without Location header from ${target}`));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let next;
|
|
142
|
+
try {
|
|
143
|
+
next = new URL(location, parsed).toString();
|
|
144
|
+
} catch {
|
|
145
|
+
finish(new Error(`Invalid redirect Location ${JSON.stringify(location)} from ${target}`));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
hop(next, redirects + 1);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (status !== 200) {
|
|
152
|
+
res.resume();
|
|
153
|
+
finish(new Error(`HTTP ${status} fetching ${target}`));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const chunks = [];
|
|
157
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
158
|
+
res.on("end", () => finish(null, Buffer.concat(chunks)));
|
|
159
|
+
res.on("error", (err) => finish(err));
|
|
160
|
+
});
|
|
161
|
+
current.on(
|
|
162
|
+
"timeout",
|
|
163
|
+
() => finish(new Error(`No response within ${o.connectTimeoutMs}ms from ${target}`))
|
|
164
|
+
);
|
|
165
|
+
current.on("error", (err) => finish(err));
|
|
166
|
+
};
|
|
167
|
+
hop(url, 0);
|
|
104
168
|
});
|
|
105
169
|
}
|
|
106
|
-
async function downloadEngine() {
|
|
170
|
+
async function downloadEngine(options2 = {}) {
|
|
107
171
|
const key = platformKey();
|
|
108
172
|
const filename = `attest-engine-${key}${os.platform() === "win32" ? ".exe" : ""}`;
|
|
109
|
-
const
|
|
173
|
+
const expectedHash = embeddedHash(key, options2.tablePath ?? path.join(packageRoot(), TABLE_NAME));
|
|
174
|
+
const binaryUrl = `${options2.releaseBase ?? RELEASE_BASE}/${filename}`;
|
|
110
175
|
process.stderr.write(`[attest] Downloading engine v${ENGINE_VERSION} for ${key}...
|
|
111
176
|
`);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
);
|
|
177
|
+
let data;
|
|
178
|
+
try {
|
|
179
|
+
data = await httpsGet(binaryUrl, {
|
|
180
|
+
ca: options2.ca,
|
|
181
|
+
connectTimeoutMs: options2.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,
|
|
182
|
+
totalTimeoutMs: options2.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT_MS
|
|
183
|
+
});
|
|
184
|
+
} catch (err) {
|
|
185
|
+
throw new Error(`Failed to download engine from ${binaryUrl}: ${err.message}`);
|
|
121
186
|
}
|
|
122
|
-
process.stderr.write(`[attest] Downloading ${binaryUrl}...
|
|
123
|
-
`);
|
|
124
|
-
const data = await httpsGet(binaryUrl);
|
|
125
187
|
const actualHash = crypto.createHash("sha256").update(data).digest("hex");
|
|
126
188
|
if (actualHash !== expectedHash) {
|
|
127
189
|
throw new Error(
|
|
128
|
-
|
|
190
|
+
`${filename} does not match the hash shipped in @attest-ai/core ${VERSION}: expected ${expectedHash}, got ${actualHash}. Refusing to install it. Retry, or set ATTEST_ENGINE_PATH to a binary you trust.`
|
|
129
191
|
);
|
|
130
192
|
}
|
|
131
193
|
process.stderr.write(`[attest] Checksum verified.
|
|
@@ -194,7 +256,7 @@ function onPath() {
|
|
|
194
256
|
return null;
|
|
195
257
|
}
|
|
196
258
|
function requireFile(enginePath, label) {
|
|
197
|
-
if (!
|
|
259
|
+
if (!existsSync2(enginePath) || !statSync2(enginePath).isFile()) {
|
|
198
260
|
throw new Error(`${label}=${enginePath} does not exist or is not a file.`);
|
|
199
261
|
}
|
|
200
262
|
}
|
|
@@ -402,8 +464,9 @@ Next: run \`npx vitest run\`. Then change the line marked ${DEMO_MARKER} in test
|
|
|
402
464
|
// src/capture.ts
|
|
403
465
|
import { spawn as spawn2 } from "child_process";
|
|
404
466
|
import * as fs3 from "fs";
|
|
467
|
+
import * as os2 from "os";
|
|
405
468
|
import * as path3 from "path";
|
|
406
|
-
import { fileURLToPath } from "url";
|
|
469
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
407
470
|
var FIXTURE_VERSION = 1;
|
|
408
471
|
var DEFAULT_FIXTURE_DIR = path3.join("tests", "attest", "fixtures");
|
|
409
472
|
var CaptureError = class extends Error {
|
|
@@ -416,7 +479,7 @@ var CaptureError = class extends Error {
|
|
|
416
479
|
}
|
|
417
480
|
};
|
|
418
481
|
function toPath(p) {
|
|
419
|
-
return p instanceof URL ?
|
|
482
|
+
return p instanceof URL ? fileURLToPath2(p) : p;
|
|
420
483
|
}
|
|
421
484
|
function loadFixture(file) {
|
|
422
485
|
const p = toPath(file);
|
|
@@ -540,7 +603,75 @@ function testPathFor(fixturePath) {
|
|
|
540
603
|
const name = path3.basename(fixturePath).replace(/\.attest\.json$/, "");
|
|
541
604
|
return path3.join(path3.dirname(path3.dirname(fixturePath)), `${name}.test.ts`);
|
|
542
605
|
}
|
|
543
|
-
function
|
|
606
|
+
function relUrl(target, testPath) {
|
|
607
|
+
const rel = path3.relative(path3.dirname(testPath), target).split(path3.sep).join("/");
|
|
608
|
+
const url = rel === "" ? "." : rel;
|
|
609
|
+
return `new URL(${ts(url.startsWith(".") ? url : `./${url}`)}, import.meta.url)`;
|
|
610
|
+
}
|
|
611
|
+
function chainLines(calls, subject, param) {
|
|
612
|
+
const lines = [`function contracts(${param}): ExpectChain {`, ` return attestExpect(${subject})`];
|
|
613
|
+
for (const [c, dsl] of calls) {
|
|
614
|
+
lines.push(` // ${comment(`${c.id}: ${c.description}`)}`, ` ${dsl}`);
|
|
615
|
+
}
|
|
616
|
+
lines[lines.length - 1] += ";";
|
|
617
|
+
lines.push("}");
|
|
618
|
+
return lines;
|
|
619
|
+
}
|
|
620
|
+
function skippedLines(skipped) {
|
|
621
|
+
if (skipped.length === 0) return [];
|
|
622
|
+
return ["", "// Drafted contracts without a TypeScript DSL call (left out):", ...skipped.map((s) => `// - ${comment(s)}`)];
|
|
623
|
+
}
|
|
624
|
+
function renderReplay(fixture, calls, skipped, fixturePath, testPath, rerun) {
|
|
625
|
+
const meaning = fixture.verification.expect_fail ? [
|
|
626
|
+
"// The recording is an incident: these contracts failed on it. The test passes once",
|
|
627
|
+
"// the new run satisfies them, so it fails again if the agent regresses."
|
|
628
|
+
] : ["// The test fails when the agent's behavior stops satisfying them."];
|
|
629
|
+
const world = rerun.world === void 0 ? "undefined" : relUrl(rerun.world, testPath);
|
|
630
|
+
const lines = [
|
|
631
|
+
`// Replay test drafted by \`attest capture\` from fixture ${comment(ts(fixture.name))}.`,
|
|
632
|
+
"//",
|
|
633
|
+
"// Re-runs COMMAND, the source command of the fixture, under strict replay (the engine's",
|
|
634
|
+
"// `replay --mode strict --no-live`): OpenAI- and Anthropic-compatible calls are served",
|
|
635
|
+
"// from the recording, and one that matches no recording fails the test. Other providers",
|
|
636
|
+
"// are not blocked: run this test without their credentials. The drafted contracts are",
|
|
637
|
+
"// evaluated on the NEW run's trace, not on the recording.",
|
|
638
|
+
"//",
|
|
639
|
+
"// COMMAND is what this test runs and is reviewed with the test: if the fixture's",
|
|
640
|
+
"// source.command stops matching it, the test fails instead of running the fixture's.",
|
|
641
|
+
...meaning,
|
|
642
|
+
"//",
|
|
643
|
+
"// A pass means the new run is consistent with the recording and satisfies the drafted",
|
|
644
|
+
"// contracts; it does not prove the agent correct. Identities are compared by role",
|
|
645
|
+
"// (relative identity): a run that swaps who plays which role misses, while the absolute",
|
|
646
|
+
"// identity is not checked. Review and edit before committing. Re-capturing does not",
|
|
647
|
+
"// overwrite this file.",
|
|
648
|
+
`import { attestExpect, replayRun, type ExpectChain } from "@attest-ai/core";`,
|
|
649
|
+
`import type { Trace } from "@attest-ai/core/proto";`,
|
|
650
|
+
`import { evaluate } from "@attest-ai/vitest";`,
|
|
651
|
+
`import { describe, it } from "vitest";`,
|
|
652
|
+
"",
|
|
653
|
+
`const FIXTURE = ${relUrl(fixturePath, testPath)};`,
|
|
654
|
+
"// The directory `attest capture` ran in: the source command's relative paths start here.",
|
|
655
|
+
`const RUN_DIR = ${relUrl(rerun.cwd, testPath)};`,
|
|
656
|
+
`const WORLD = ${world};`,
|
|
657
|
+
"// Reviewed argv: the only command this test runs.",
|
|
658
|
+
`const COMMAND = ${ts(fixture.source.command ?? [])};`,
|
|
659
|
+
...skippedLines(skipped),
|
|
660
|
+
"",
|
|
661
|
+
...chainLines(calls, "trace", "trace: Trace"),
|
|
662
|
+
"",
|
|
663
|
+
`describe(${ts(fixture.name)}, () => {`,
|
|
664
|
+
` it("new run is consistent with the recording", async () => {`,
|
|
665
|
+
" const trace = await replayRun(FIXTURE, { command: COMMAND, cwd: RUN_DIR, world: WORLD });",
|
|
666
|
+
"",
|
|
667
|
+
" await evaluate(contracts(trace));",
|
|
668
|
+
" });",
|
|
669
|
+
"});"
|
|
670
|
+
];
|
|
671
|
+
return `${lines.join("\n")}
|
|
672
|
+
`;
|
|
673
|
+
}
|
|
674
|
+
function renderVitest(fixture, fixturePath, testPath, rerun) {
|
|
544
675
|
const calls = [];
|
|
545
676
|
const skipped = [];
|
|
546
677
|
for (const c of fixture.contracts) {
|
|
@@ -550,7 +681,9 @@ function renderVitest(fixture, fixturePath, testPath) {
|
|
|
550
681
|
skipped.push(err.message);
|
|
551
682
|
}
|
|
552
683
|
}
|
|
553
|
-
|
|
684
|
+
if (rerun !== void 0 && fixture.source.kind === "command" && fixture.source.replay_capable) {
|
|
685
|
+
return renderReplay(fixture, calls, skipped, fixturePath, testPath, rerun);
|
|
686
|
+
}
|
|
554
687
|
const status = new Map(fixture.verification.results.map((r) => [r.contract, r.status]));
|
|
555
688
|
const expectFail = fixture.verification.expect_fail;
|
|
556
689
|
const lines = [
|
|
@@ -562,18 +695,11 @@ function renderVitest(fixture, fixturePath, testPath) {
|
|
|
562
695
|
`import { ${expectFail ? "inspect" : "evaluate"} } from "@attest-ai/vitest";`,
|
|
563
696
|
`import { describe, ${expectFail ? "expect, " : ""}it } from "vitest";`,
|
|
564
697
|
"",
|
|
565
|
-
`const FIXTURE = loadFixture(
|
|
698
|
+
`const FIXTURE = loadFixture(${relUrl(fixturePath, testPath)});`,
|
|
699
|
+
...skippedLines(skipped),
|
|
700
|
+
"",
|
|
701
|
+
...chainLines(calls, "FIXTURE.trace", "")
|
|
566
702
|
];
|
|
567
|
-
if (skipped.length > 0) {
|
|
568
|
-
lines.push("", "// Drafted contracts without a TypeScript DSL call (left out):");
|
|
569
|
-
for (const s of skipped) lines.push(`// - ${comment(s)}`);
|
|
570
|
-
}
|
|
571
|
-
lines.push("", "function contracts(): ExpectChain {", " return attestExpect(FIXTURE.trace)");
|
|
572
|
-
for (const [c, dsl] of calls) {
|
|
573
|
-
lines.push(` // ${comment(`${c.id}: ${c.description}`)}`, ` ${dsl}`);
|
|
574
|
-
}
|
|
575
|
-
lines[lines.length - 1] += ";";
|
|
576
|
-
lines.push("}");
|
|
577
703
|
if (expectFail) {
|
|
578
704
|
lines.push("", "// Status of each contract on the captured incident, in contracts() order.");
|
|
579
705
|
lines.push("const EXPECTED: Array<[string, string]> = [");
|
|
@@ -602,14 +728,14 @@ function renderVitest(fixture, fixturePath, testPath) {
|
|
|
602
728
|
return `${lines.join("\n")}
|
|
603
729
|
`;
|
|
604
730
|
}
|
|
605
|
-
function emitVitest(fixturePath) {
|
|
731
|
+
function emitVitest(fixturePath, rerun) {
|
|
606
732
|
const testPath = testPathFor(fixturePath);
|
|
607
733
|
const existing = fs3.lstatSync(testPath, { throwIfNoEntry: false });
|
|
608
734
|
if (existing?.isSymbolicLink()) {
|
|
609
735
|
throw new CaptureError(`${testPath} is a symbolic link; refusing to draft a test through it`);
|
|
610
736
|
}
|
|
611
737
|
if (existing !== void 0) return null;
|
|
612
|
-
const source = renderVitest(loadFixture(fixturePath), fixturePath, testPath);
|
|
738
|
+
const source = renderVitest(loadFixture(fixturePath), fixturePath, testPath, rerun);
|
|
613
739
|
fs3.mkdirSync(path3.dirname(testPath), { recursive: true });
|
|
614
740
|
try {
|
|
615
741
|
fs3.writeFileSync(testPath, source, { encoding: "utf8", flag: "wx" });
|
|
@@ -624,7 +750,7 @@ function emitVitest(fixturePath) {
|
|
|
624
750
|
|
|
625
751
|
// src/cli.ts
|
|
626
752
|
var HELP_FLAGS = /* @__PURE__ */ new Set(["-h", "--help"]);
|
|
627
|
-
var SDK_USAGE = "usage: attest <command> [args]\n\nSDK commands:\n init [--ci github] [--skills] [--mcp] [--json]\n scaffold a sample test and merge policy here\n (--ci github also writes .github/workflows/attest.yml;\n --skills installs the agent skills with attest-engine;\n --mcp writes the MCP client configs (.mcp.json, .cursor/mcp.json,\n .codex/config.toml) with attest-engine;\n --json prints {created, updated, skipped} paths as JSON)\n\n`attest capture` (engine) also drafts tests/attest/<name>.test.ts from a
|
|
753
|
+
var SDK_USAGE = "usage: attest <command> [args]\n\nSDK commands:\n init [--ci github] [--skills] [--mcp] [--json]\n scaffold a sample test and merge policy here\n (--ci github also writes .github/workflows/attest.yml;\n --skills installs the agent skills with attest-engine;\n --mcp writes the MCP client configs (.mcp.json, .cursor/mcp.json,\n .codex/config.toml) with attest-engine;\n --json prints {created, updated, skipped} paths as JSON)\n\n`attest capture` (engine) also drafts tests/attest/<name>.test.ts from a fixture that\nis consistent with the recording, unless that file exists. For a command capture the\ntest re-runs the command written in it under strict replay (OpenAI- and Anthropic-\ncompatible calls are served from the recording; other providers are not blocked) and\nevaluates the contracts on the new run.\n\noptions:\n --version print the SDK version and the engine version it requires\n --help print this help\n";
|
|
628
754
|
var HELP_PROBE_TIMEOUT_MS = 1e4;
|
|
629
755
|
function errorMessage(err) {
|
|
630
756
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -679,7 +805,7 @@ async function printHelp() {
|
|
|
679
805
|
}
|
|
680
806
|
function exitStatus(status, signal) {
|
|
681
807
|
if (status !== null) return status;
|
|
682
|
-
if (signal !== null) return 128 + (
|
|
808
|
+
if (signal !== null) return 128 + (os3.constants.signals[signal] ?? 0);
|
|
683
809
|
return 1;
|
|
684
810
|
}
|
|
685
811
|
async function engineProviding(command) {
|
|
@@ -765,6 +891,18 @@ function captureWritesFixture(args) {
|
|
|
765
891
|
}
|
|
766
892
|
return true;
|
|
767
893
|
}
|
|
894
|
+
function captureWorldPath(args) {
|
|
895
|
+
for (let i = 0; i < args.length; i++) {
|
|
896
|
+
const a = args[i];
|
|
897
|
+
if (a === "--") return void 0;
|
|
898
|
+
const [name, ...rest] = a.split("=");
|
|
899
|
+
if (name === "--world" || name === "-world") {
|
|
900
|
+
const value = rest.length > 0 ? rest.join("=") : args[i + 1];
|
|
901
|
+
return value === void 0 ? void 0 : path4.resolve(value);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return void 0;
|
|
905
|
+
}
|
|
768
906
|
function userResultFile(args) {
|
|
769
907
|
for (const a of args) {
|
|
770
908
|
if (a === "--") break;
|
|
@@ -779,7 +917,7 @@ function captureAndEmit(engine, args) {
|
|
|
779
917
|
);
|
|
780
918
|
return 2;
|
|
781
919
|
}
|
|
782
|
-
const tmp = fs4.mkdtempSync(path4.join(
|
|
920
|
+
const tmp = fs4.mkdtempSync(path4.join(os3.tmpdir(), "attest-capture-"));
|
|
783
921
|
const resultFile = path4.join(tmp, "result.json");
|
|
784
922
|
let result;
|
|
785
923
|
let code;
|
|
@@ -799,10 +937,10 @@ function captureAndEmit(engine, args) {
|
|
|
799
937
|
`);
|
|
800
938
|
return code;
|
|
801
939
|
}
|
|
802
|
-
const written = emitVitest(fixture);
|
|
940
|
+
const written = emitVitest(fixture, { cwd: process.cwd(), world: captureWorldPath(args) });
|
|
803
941
|
process.stderr.write(
|
|
804
942
|
written === null ? ` test: ${testPathFor(fixture)} exists; left unchanged
|
|
805
|
-
` : ` test: drafted ${written}
|
|
943
|
+
` : ` test: drafted ${written}; for a command capture it re-runs the command under strict replay (other providers are not blocked: run it without their credentials) and evaluates the contracts on the new run
|
|
806
944
|
`
|
|
807
945
|
);
|
|
808
946
|
return code;
|
|
@@ -992,7 +1130,7 @@ ${SDK_USAGE}`);
|
|
|
992
1130
|
function isDirectExecution() {
|
|
993
1131
|
const script = process.argv[1];
|
|
994
1132
|
if (script === void 0 || !fs4.existsSync(script)) return false;
|
|
995
|
-
return fs4.realpathSync(script) === fs4.realpathSync(
|
|
1133
|
+
return fs4.realpathSync(script) === fs4.realpathSync(fileURLToPath3(import.meta.url));
|
|
996
1134
|
}
|
|
997
1135
|
if (isDirectExecution()) {
|
|
998
1136
|
main().then(
|
|
@@ -1005,6 +1143,7 @@ if (isDirectExecution()) {
|
|
|
1005
1143
|
);
|
|
1006
1144
|
}
|
|
1007
1145
|
export {
|
|
1146
|
+
captureWorldPath,
|
|
1008
1147
|
captureWritesFixture,
|
|
1009
1148
|
engineCommands,
|
|
1010
1149
|
exitStatus,
|