@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 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.
@@ -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";
@@ -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
+ }