@skill-test/vitest 0.1.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/LICENSE +21 -0
- package/README.md +68 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +26 -0
- package/dist/vitest.d.ts +16 -0
- package/dist/vitest.js +54 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nick DeRobertis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @skill-test/vitest
|
|
2
|
+
|
|
3
|
+
A [vitest](https://vitest.dev) plugin for [skilltest](../../README.md): run
|
|
4
|
+
AI-skill tests and natural-language evals from vitest, and mix in your own
|
|
5
|
+
deterministic checks. Built on
|
|
6
|
+
[`@skill-test/sdk`](../../sdks/typescript/README.md) — the SDK's API is
|
|
7
|
+
re-exported here, so a vitest suite needs only this one dependency.
|
|
8
|
+
|
|
9
|
+
## As code
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { runSkill, assistantText } from "@skill-test/vitest";
|
|
13
|
+
|
|
14
|
+
test("greeter names the patient", async () => {
|
|
15
|
+
const report = await runSkill("cases/greet.yaml", {
|
|
16
|
+
platforms: ["claude-code"],
|
|
17
|
+
models: ["claude-opus-4-8"],
|
|
18
|
+
});
|
|
19
|
+
expect(report.passed, report.runs[0]?.case).toBe(true);
|
|
20
|
+
expect(assistantText(report.runs[0]!.transcript)).toContain("Dr. Smith");
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## One-liner
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { skillTest } from "@skill-test/vitest";
|
|
28
|
+
|
|
29
|
+
skillTest("greeter confirms the appointment", "cases/greet.yaml");
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Recommended: auto-discover a tree of cases
|
|
33
|
+
|
|
34
|
+
When vitest is your primary test runner, keep your cases as data and let one
|
|
35
|
+
test module collect them. Name each case `*.skilltest.yaml` (or `.yml`) and add
|
|
36
|
+
a single `skills.test.ts`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// skills.test.ts
|
|
40
|
+
import { discover } from "@skill-test/vitest";
|
|
41
|
+
|
|
42
|
+
discover("cases"); // registers one vitest test per *.skilltest.yaml under cases/
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
# cases/greet.skilltest.yaml
|
|
47
|
+
skill: ./skills/greeter
|
|
48
|
+
input: "Greet Dr. Smith."
|
|
49
|
+
evals:
|
|
50
|
+
- type: boolean
|
|
51
|
+
criterion: "the reply greets Dr. Smith by name"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This is the closest vitest equivalent to pytest's auto-collection: vitest only
|
|
55
|
+
collects its own test modules, so the one-line `discover()` call stands in for a
|
|
56
|
+
file collector. Adding a case is then just dropping in a YAML file — no code
|
|
57
|
+
change. Pass run options as the second argument (`discover("cases", { platforms:
|
|
58
|
+
["claude-code"] })`); for matrices or deterministic mix-in assertions, reach for
|
|
59
|
+
`runSkill` in an ordinary `test()` instead.
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
The plugin shells out to the `skilltest` binary. Point at one with the
|
|
64
|
+
`SKILLTEST_BIN` env var (or the `bin` option) and the provider with
|
|
65
|
+
`SKILLTEST_PROVIDER` (or the `provider` option). A failing eval is returned in
|
|
66
|
+
`report.passed`; bad input and provider failures throw `SkilltestUsageError` /
|
|
67
|
+
`SkilltestProviderError`. See the repository root for the provider protocol and
|
|
68
|
+
full schema.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@skill-test/vitest` — run AI-skill tests and natural-language evals in vitest.
|
|
3
|
+
*
|
|
4
|
+
* The vitest integration on top of `@skill-test/sdk`, whose API is re-exported
|
|
5
|
+
* here so a vitest suite needs only this one dependency:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { skillTest, discover, runSkill, assistantText } from "@skill-test/vitest";
|
|
9
|
+
*
|
|
10
|
+
* skillTest("greeter names the patient", "cases/greet.yaml");
|
|
11
|
+
* // or auto-discover a tree of *.skilltest.yaml cases:
|
|
12
|
+
* discover("cases");
|
|
13
|
+
*
|
|
14
|
+
* // For matrices or deterministic mix-in checks, use the SDK API directly:
|
|
15
|
+
* test("greeter", async () => {
|
|
16
|
+
* const report = await runSkill("cases/greet.yaml");
|
|
17
|
+
* expect(report.passed).toBe(true);
|
|
18
|
+
* expect(assistantText(report.runs[0]!.transcript)).toContain("Dr. Smith");
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* This module (via the helpers) imports `vitest`, so only load it inside a
|
|
23
|
+
* vitest run. `@skill-test/vitest/vitest` remains as an alias for the helpers.
|
|
24
|
+
*/
|
|
25
|
+
export { skillTest, discover, CASE_SUFFIXES } from "./vitest.js";
|
|
26
|
+
export * from "@skill-test/sdk";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@skill-test/vitest` — run AI-skill tests and natural-language evals in vitest.
|
|
3
|
+
*
|
|
4
|
+
* The vitest integration on top of `@skill-test/sdk`, whose API is re-exported
|
|
5
|
+
* here so a vitest suite needs only this one dependency:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { skillTest, discover, runSkill, assistantText } from "@skill-test/vitest";
|
|
9
|
+
*
|
|
10
|
+
* skillTest("greeter names the patient", "cases/greet.yaml");
|
|
11
|
+
* // or auto-discover a tree of *.skilltest.yaml cases:
|
|
12
|
+
* discover("cases");
|
|
13
|
+
*
|
|
14
|
+
* // For matrices or deterministic mix-in checks, use the SDK API directly:
|
|
15
|
+
* test("greeter", async () => {
|
|
16
|
+
* const report = await runSkill("cases/greet.yaml");
|
|
17
|
+
* expect(report.passed).toBe(true);
|
|
18
|
+
* expect(assistantText(report.runs[0]!.transcript)).toContain("Dr. Smith");
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* This module (via the helpers) imports `vitest`, so only load it inside a
|
|
23
|
+
* vitest run. `@skill-test/vitest/vitest` remains as an alias for the helpers.
|
|
24
|
+
*/
|
|
25
|
+
export { skillTest, discover, CASE_SUFFIXES } from "./vitest.js";
|
|
26
|
+
export * from "@skill-test/sdk";
|
package/dist/vitest.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RunOptions } from "@skill-test/sdk";
|
|
2
|
+
/** Filename suffixes a case file must carry to be auto-discovered. */
|
|
3
|
+
export declare const CASE_SUFFIXES: readonly [".skilltest.yaml", ".skilltest.yml"];
|
|
4
|
+
/** Register a vitest test that runs `casePath` and asserts every eval passed. */
|
|
5
|
+
export declare function skillTest(name: string, casePath: string, options?: RunOptions): void;
|
|
6
|
+
/**
|
|
7
|
+
* Recursively find every `*.skilltest.yaml` / `*.skilltest.yml` case under `dir`
|
|
8
|
+
* and register each as a vitest test (named by its path relative to `dir`).
|
|
9
|
+
*
|
|
10
|
+
* vitest only collects its own test modules, so it can't pick up bare YAML the
|
|
11
|
+
* way pytest's collector does. Calling `discover` from a single `*.test.ts` is
|
|
12
|
+
* the closest equivalent: one line gives you pytest-style auto-collection. Cases
|
|
13
|
+
* are sorted for a stable order, and discovery is synchronous so the tests are
|
|
14
|
+
* registered before vitest collects the file.
|
|
15
|
+
*/
|
|
16
|
+
export declare function discover(dir?: string, options?: RunOptions): void;
|
package/dist/vitest.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vitest helpers: register a skill case as a vitest test in one line with
|
|
3
|
+
* {@link skillTest}, or auto-discover a tree of cases (the recommended setup
|
|
4
|
+
* when vitest is your primary runner) with {@link discover}:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { skillTest, discover } from "@skill-test/vitest";
|
|
8
|
+
* skillTest("greeter names the patient", "cases/greet.yaml");
|
|
9
|
+
* discover("cases");
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* For matrices or deterministic mix-in checks, call the SDK's `runSkill` from
|
|
13
|
+
* an ordinary `test()` instead. This module imports `vitest`, so only load it
|
|
14
|
+
* inside a vitest run.
|
|
15
|
+
*/
|
|
16
|
+
import { readdirSync } from "node:fs";
|
|
17
|
+
import { join, relative } from "node:path";
|
|
18
|
+
import { describeFailures, runSkill } from "@skill-test/sdk";
|
|
19
|
+
import { expect, test } from "vitest";
|
|
20
|
+
/** Filename suffixes a case file must carry to be auto-discovered. */
|
|
21
|
+
export const CASE_SUFFIXES = [".skilltest.yaml", ".skilltest.yml"];
|
|
22
|
+
/** Register a vitest test that runs `casePath` and asserts every eval passed. */
|
|
23
|
+
export function skillTest(name, casePath, options = {}) {
|
|
24
|
+
test(name, async () => {
|
|
25
|
+
const report = await runSkill(casePath, options);
|
|
26
|
+
expect(report.passed, describeFailures(report)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Recursively find every `*.skilltest.yaml` / `*.skilltest.yml` case under `dir`
|
|
31
|
+
* and register each as a vitest test (named by its path relative to `dir`).
|
|
32
|
+
*
|
|
33
|
+
* vitest only collects its own test modules, so it can't pick up bare YAML the
|
|
34
|
+
* way pytest's collector does. Calling `discover` from a single `*.test.ts` is
|
|
35
|
+
* the closest equivalent: one line gives you pytest-style auto-collection. Cases
|
|
36
|
+
* are sorted for a stable order, and discovery is synchronous so the tests are
|
|
37
|
+
* registered before vitest collects the file.
|
|
38
|
+
*/
|
|
39
|
+
export function discover(dir = ".", options = {}) {
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = readdirSync(dir, { recursive: true, withFileTypes: true });
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
throw new Error(`skilltest discover: cannot read directory \`${dir}\`: ${err.message}`);
|
|
46
|
+
}
|
|
47
|
+
const cases = entries
|
|
48
|
+
.filter((entry) => entry.isFile() && CASE_SUFFIXES.some((suffix) => entry.name.endsWith(suffix)))
|
|
49
|
+
.map((entry) => join(entry.parentPath, entry.name))
|
|
50
|
+
.sort();
|
|
51
|
+
for (const casePath of cases) {
|
|
52
|
+
skillTest(relative(dir, casePath) || casePath, casePath, options);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skill-test/vitest",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "vitest integration for skilltest: register AI-skill test cases as vitest tests, built on @skill-test/sdk.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/nickderobertis/skilltest.git",
|
|
9
|
+
"directory": "plugins/vitest"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./vitest": {
|
|
23
|
+
"types": "./dist/vitest.d.ts",
|
|
24
|
+
"default": "./dist/vitest.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@skill-test/sdk": "0.1.1"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"vitest": ">=1.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.7.5",
|
|
38
|
+
"typescript": "^5.6.3",
|
|
39
|
+
"vitest": "^2.1.3"
|
|
40
|
+
},
|
|
41
|
+
"nx": {
|
|
42
|
+
"includedScripts": []
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -p tsconfig.build.json",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
47
|
+
"test": "vitest run"
|
|
48
|
+
}
|
|
49
|
+
}
|