@vitronai/themis 0.1.15 → 1.2.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/CHANGELOG.md +32 -0
- package/README.md +26 -10
- package/docs/agents-adoption.md +68 -0
- package/docs/api.md +3 -1
- package/docs/migration.md +9 -5
- package/docs/roadmap.md +1 -1
- package/docs/schemas/migration-report.v1.json +122 -0
- package/package.json +8 -2
- package/scripts/claude-hook.js +153 -0
- package/src/cli.js +79 -6
- package/src/init.js +122 -4
- package/src/migrate.js +127 -5
- package/templates/CLAUDE.themis.md +43 -0
- package/templates/claude-commands/themis-fix.md +14 -0
- package/templates/claude-commands/themis-generate.md +14 -0
- package/templates/claude-commands/themis-migrate.md +18 -0
- package/templates/claude-commands/themis-test.md +12 -0
- package/templates/claude-skill/SKILL.md +94 -0
- package/templates/cursorrules.themis.md +28 -0
- package/themis.ai.json +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,38 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 1.2.1 - 2026-04-09
|
|
8
|
+
|
|
9
|
+
- Added `init --cursor` flag to install a `.cursorrules` file with Themis conventions. Composable with `--agents` and `--claude-code`.
|
|
10
|
+
- Bare `npx themis init` now auto-detects agent markers (`.claude/` dir, `.cursorrules` file) and installs the right assets without requiring flags.
|
|
11
|
+
- Added 5 new Tessl eval scenarios (agent reporter fix loop, Claude Code integration setup, deterministic test authoring, intent phase structure, Vitest full migration). Eval results: 37% baseline → 97% with skill.
|
|
12
|
+
- Published Tessl tile `vitron-ai/themis@1.2.1` with eval scenarios included.
|
|
13
|
+
|
|
14
|
+
## 1.2.0 - 2026-04-08
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Claude Code one-command adoption.** New `npx themis init --claude-code` flag installs everything Claude Code needs to drive Themis natively: a `CLAUDE.md` at the repo root, a Claude Code skill at `.claude/skills/themis/SKILL.md` that auto-loads when the user asks Claude to write, run, fix, or migrate tests, and four slash commands (`/themis-test`, `/themis-generate`, `/themis-migrate`, `/themis-fix`) wired to the agent-readable test loop. Composes with `--agents` so a single `init --agents --claude-code` installs both bundles. Idempotent: re-running appends to an existing `CLAUDE.md` only when the Themis section is missing, and skips skill/command files that already exist.
|
|
19
|
+
- **Claude Code `PostToolUse` hook wrapper** at `scripts/claude-hook.js`. Reads tool input from stdin, filters non-source edits and edits inside `.themis/`, `__themis__/`, `node_modules/`, `.git/`, prefers `--rerun-failed` when a prior failed-tests artifact exists, and surfaces failures via stderr + exit 2 so Claude Code feeds the structured `failures[].cluster` and `failures[].repairHints` payload directly back into the model. Disable with `THEMIS_HOOK_DISABLED=1`. Opt-in only — not installed by `init --claude-code`. See [Claude Code one-command setup](docs/agents-adoption.md#claude-code-one-command-setup) for the `.claude/settings.json` recipe.
|
|
20
|
+
- New downstream templates under `templates/`: `CLAUDE.themis.md`, `claude-skill/SKILL.md`, and `claude-commands/{themis-test,themis-generate,themis-migrate,themis-fix}.md`. All ship in the npm tarball.
|
|
21
|
+
- New `--claude` alias for `--claude-code`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **Repositioned README and `package.json` description** to lead with the job-to-be-done (a Node/TS unit test framework designed for AI coding agents — drop-in alternative to Jest and Vitest) instead of the philosophy ("intent-first ... for AI agents"), which read ambiguously as "tests AI agents". The five-bullet value prop now sits above the fold with the benchmark numbers, and Claude Code, Cursor, and Codex are named explicitly in the agent-output bullet.
|
|
26
|
+
- `runInit` now returns `{ agents, claudeCode }` instead of a single `{ path, created }` object so both flags can report independently. Existing `--agents` CLI output strings are preserved exactly.
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
- Added a "Claude Code One-Command Setup" section to `docs/agents-adoption.md` listing every file `init --claude-code` installs and explaining why the agent reporter loop matters.
|
|
31
|
+
- Added an "Optional: Wire Themis Into Claude Code's Edit Loop With A Hook" section with the `.claude/settings.json` snippet, plain-English explanation of the wrapper's three behaviors, and trade-offs (wall-clock cost, hook security, two ways to disable).
|
|
32
|
+
- README quickstart now includes a one-paragraph "Using Claude Code?" callout linking to the adoption guide.
|
|
33
|
+
|
|
34
|
+
### Tests
|
|
35
|
+
|
|
36
|
+
- Added `tests/claude-hook.test.js` with six tests covering `THEMIS_HOOK_DISABLED`, empty/invalid stdin, non-source extensions, ignored directories, real-source-edit-with-green-suite (end-to-end via in-tempdir shim), and real-source-edit-with-red-suite (exit 2 + stderr payload assertion).
|
|
37
|
+
- Extended `tests/cli-output.test.js` with three tests for `init --claude-code`: happy-path install, append-then-idempotent on existing `CLAUDE.md`, and composed `--agents --claude-code`.
|
|
38
|
+
|
|
7
39
|
## 0.1.15 - 2026-03-27
|
|
8
40
|
|
|
9
41
|
- Added a direct in-sidebar `Quick Actions` group plus an `Artifact Files` drawer to the in-repo VS Code extension scaffold so core Themis commands and raw artifact navigation remain reachable even when the VS Code view toolbar overflows.
|
package/README.md
CHANGED
|
@@ -7,26 +7,30 @@
|
|
|
7
7
|
</a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
**A Node.js and TypeScript unit test framework designed for AI coding agents.**
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Themis is a drop-in alternative to Jest and Vitest, built for the way code gets written today: humans and agents working the same edit-test-fix loop. Strict phase semantics keep generated tests legible, machine-readable failure output lets agents self-repair, and an incremental migration path means you don't rewrite your suite on day one.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
- **Faster than Jest and Vitest** — `68.59%` faster than Vitest and `130.26%` faster than Jest on the same React showcase benchmark ([proof](#performance-proof))
|
|
15
|
+
- **Agent-native output** — `--agent` JSON with failure clusters and structured repair hints, ready to feed back into Claude Code, Cursor, Codex, or any agent loop
|
|
16
|
+
- **One-command migration** — `npx themis migrate jest` or `npx themis migrate vitest` with codemods and a structured findings report
|
|
17
|
+
- **Modern by default** — native `.js`, `.jsx`, `.ts`, `.tsx`, ESM, JSX, and React Testing Library, no config gymnastics
|
|
18
|
+
- **Discoverable to agents** — ships an `AGENTS.md` template, a `themis.ai.json` manifest, and a [Tessl tile](tessl/tile.json) so AI assistants can find and adopt it without you copy-pasting docs
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
## Quickstart
|
|
17
21
|
|
|
18
22
|
```bash
|
|
19
23
|
npm install -D @vitronai/themis@latest
|
|
20
24
|
npx themis init --agents
|
|
21
|
-
npx themis generate
|
|
25
|
+
npx themis generate src # or `app` for Next App Router repos
|
|
22
26
|
npx themis test
|
|
23
27
|
```
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
`init --agents` writes `themis.config.json`, updates `.gitignore`, and scaffolds a downstream `AGENTS.md` so the agents on your team know how to use it. See the [agent adoption guide](docs/agents-adoption.md) for the full setup, including migration from Jest or Vitest.
|
|
30
|
+
|
|
31
|
+
**Using Claude Code?** Run `npx themis init --claude-code` to install a `CLAUDE.md`, a Claude Code skill at `.claude/skills/themis/`, and slash commands (`/themis-test`, `/themis-generate`, `/themis-migrate`, `/themis-fix`) wired to the agent-readable test loop. See [Claude Code one-command setup](docs/agents-adoption.md#claude-code-one-command-setup).
|
|
26
32
|
|
|
27
|
-
- `npx themis init --agents` writes `themis.config.json`, updates `.gitignore`, and scaffolds a downstream `AGENTS.md` when one does not already exist.
|
|
28
33
|
- machine-readable agent manifest: [`themis.ai.json`](themis.ai.json)
|
|
29
|
-
- downstream adoption guide: [`docs/agents-adoption.md`](docs/agents-adoption.md)
|
|
30
34
|
- copyable downstream rules file: [`templates/AGENTS.themis.md`](templates/AGENTS.themis.md)
|
|
31
35
|
|
|
32
36
|
<p align="center">
|
|
@@ -35,7 +39,7 @@ Use `src` for conventional source trees and `app` for Next App Router repos.
|
|
|
35
39
|
|
|
36
40
|
## Contents
|
|
37
41
|
|
|
38
|
-
- [
|
|
42
|
+
- [Quickstart](#quickstart)
|
|
39
43
|
- [Adopt In Another Repo](#adopt-in-another-repo)
|
|
40
44
|
- [Code Scan](#code-scan)
|
|
41
45
|
- [Positioning](#positioning)
|
|
@@ -72,6 +76,18 @@ On the current same-host React showcase benchmark sample, Themis measured `68.59
|
|
|
72
76
|
|
|
73
77
|
The exact comparison artifact is emitted by CI as `.themis/benchmarks/showcase-comparison/perf-summary.json` and `.themis/benchmarks/showcase-comparison/perf-summary.md`. Treat those percentages as the current documented sample, not a universal constant for every environment.
|
|
74
78
|
|
|
79
|
+
### First-Try Test Pass Rate
|
|
80
|
+
|
|
81
|
+
The first-try benchmark measures how often Claude generates tests that pass on the first run — the metric that matters most for agent-driven development. For each of 5 fixture source files (pure functions, async services, React components, hooks), Claude generates tests using Themis, Vitest, and Jest, and each generated suite is run once without edits.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
ANTHROPIC_API_KEY=sk-... npm run benchmark:first-try
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Results are written to `.themis/benchmarks/first-try/first-try-results.json` and `.themis/benchmarks/first-try/first-try-results.md`. The generated test code is saved under `.themis/benchmarks/first-try/generated-tests/` for manual review.
|
|
88
|
+
|
|
89
|
+
Themis's advantage here comes from its `CLAUDE.md` template and Claude Code skill, which give Claude structured guidance about phase semantics, import conventions, and common pitfalls — context that Jest and Vitest users do not ship out of the box.
|
|
90
|
+
|
|
75
91
|
## Modern JS/TS Support
|
|
76
92
|
|
|
77
93
|
Themis is built for modern Node.js and TypeScript projects:
|
|
@@ -298,7 +314,7 @@ Short version:
|
|
|
298
314
|
- Migration proof job runs `npm run proof:migration` against checked-in Jest/Vitest fixtures for basic suites, table tests, RTL/jsdom flows, timers, module mocking, and a context/provider-heavy RTL example, then uploads the resulting migration reports plus Themis run artifacts as evidence.
|
|
299
315
|
- Themis React Showcase job verifies a straight-up native Themis React fixture as a first-party example.
|
|
300
316
|
- React showcase perf job runs `npm run benchmark:showcase` on the exact same React scenarios for Themis, Jest, and Vitest on one CI host, then uploads `.themis/benchmarks/showcase-comparison/perf-summary.{json,md}` so the relative timing claim is backed by one comparable artifact.
|
|
301
|
-
- Release `0.
|
|
317
|
+
- Release `1.0.17` packages this expanded proof lane so every CI run now proves the provider-heavy example alongside the earlier fixtures.
|
|
302
318
|
|
|
303
319
|
## Agent Guide
|
|
304
320
|
|
package/docs/agents-adoption.md
CHANGED
|
@@ -11,6 +11,8 @@ npx themis generate <source-root>
|
|
|
11
11
|
npx themis test
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
If you use Claude Code, run `npx themis init --claude-code` instead (or in addition) — see [Claude Code One-Command Setup](#claude-code-one-command-setup) below.
|
|
15
|
+
|
|
14
16
|
What those commands do:
|
|
15
17
|
|
|
16
18
|
- `npm install -D @vitronai/themis`: installs Themis as the repo's unit test framework
|
|
@@ -62,6 +64,72 @@ Prefer `test(...)` for low-level unit checks.
|
|
|
62
64
|
Do not claim Themis is "not a unit test framework".
|
|
63
65
|
```
|
|
64
66
|
|
|
67
|
+
## Claude Code One-Command Setup
|
|
68
|
+
|
|
69
|
+
If you use Claude Code, Themis can install everything Claude needs in one command:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install -D @vitronai/themis@latest
|
|
73
|
+
npx themis init --claude-code
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`init --claude-code` writes:
|
|
77
|
+
|
|
78
|
+
- `CLAUDE.md` — adoption rules at the repo root. If a `CLAUDE.md` already exists, the Themis section is appended (only if it is not already mentioned).
|
|
79
|
+
- `.claude/skills/themis/SKILL.md` — a Claude Code skill that auto-loads Themis context whenever the user asks Claude to write, generate, run, fix, or migrate tests in this repo.
|
|
80
|
+
- `.claude/commands/themis-test.md` — `/themis-test` slash command for the agent-readable test loop.
|
|
81
|
+
- `.claude/commands/themis-generate.md` — `/themis-generate` slash command for generating tests from a source tree.
|
|
82
|
+
- `.claude/commands/themis-migrate.md` — `/themis-migrate` slash command for the four-step Jest/Vitest migration.
|
|
83
|
+
- `.claude/commands/themis-fix.md` — `/themis-fix` slash command for the structured failure-fix loop.
|
|
84
|
+
|
|
85
|
+
You can compose `--claude-code` with `--agents` to install both at once:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx themis init --agents --claude-code
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The skill and slash commands are committed to the repo (under `.claude/`), so every developer or agent that opens the project sees them. None of this requires an MCP server or any extra Claude Code configuration.
|
|
92
|
+
|
|
93
|
+
### Why this matters
|
|
94
|
+
|
|
95
|
+
The `--reporter agent` JSON output is the killer feature for Claude Code's edit-test-fix loop: structured failure clusters with `repairHints` mean Claude can act on parsed signals instead of re-parsing stack traces. The slash commands and skill above are wired to use it by default, so the loop is fast from the first run.
|
|
96
|
+
|
|
97
|
+
### Optional: Wire Themis Into Claude Code's Edit Loop With A Hook
|
|
98
|
+
|
|
99
|
+
If you want Claude Code to *automatically* run Themis after every edit and feed structured failures back into the conversation, add a `PostToolUse` hook. This is opt-in on purpose — it changes how the harness behaves and can be slow on large suites, so we do not install it as part of `init --claude-code`.
|
|
100
|
+
|
|
101
|
+
Add this to `.claude/settings.json` (or `.claude/settings.local.json` if you want to keep it personal and out of git):
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"hooks": {
|
|
106
|
+
"PostToolUse": [
|
|
107
|
+
{
|
|
108
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
109
|
+
"hooks": [
|
|
110
|
+
{
|
|
111
|
+
"type": "command",
|
|
112
|
+
"command": "node node_modules/@vitronai/themis/scripts/claude-hook.js"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Then run `npx themis init --claude-code` (or copy the script manually). The wrapper does three things to keep the loop tight:
|
|
122
|
+
|
|
123
|
+
1. **Filters non-source edits.** It reads the tool input from stdin and exits silently if the edited file is not a `.js`, `.jsx`, `.ts`, or `.tsx` source file. Edits to docs, config, and tests themselves do not trigger a re-run.
|
|
124
|
+
2. **Prefers `--rerun-failed`.** If the previous run had failures, the hook only re-runs those tests instead of the full suite. The first failure-free run resets the loop.
|
|
125
|
+
3. **Returns agent-readable output.** Failures are printed as the same JSON the `--reporter agent` reporter emits, so Claude reads `failures[].cluster` and `failures[].repairHints` directly without re-parsing stack traces.
|
|
126
|
+
|
|
127
|
+
**Trade-offs to know about:**
|
|
128
|
+
|
|
129
|
+
- The hook adds the suite's wall-clock time to every edit. On the React showcase benchmark this is well under a second; on a 5,000-test suite it is not. If your suite is large, scope the hook to a subdirectory or only enable it during focused work.
|
|
130
|
+
- Hooks run shell commands with your privileges. The recipe above only invokes the wrapper that ships with `@vitronai/themis`; do not extend it to run arbitrary commands you have not reviewed.
|
|
131
|
+
- To disable temporarily, comment out the entry in `.claude/settings.json` or move it to `.claude/settings.local.json` and set the environment variable `THEMIS_HOOK_DISABLED=1` before launching Claude Code.
|
|
132
|
+
|
|
65
133
|
## Notes
|
|
66
134
|
|
|
67
135
|
- Themis is a unit test framework and test generator for Node.js and TypeScript projects.
|
package/docs/api.md
CHANGED
|
@@ -191,6 +191,7 @@ Migration options:
|
|
|
191
191
|
|
|
192
192
|
- `--rewrite-imports`: rewrites matched imports from `@jest/globals`, `vitest`, and `@testing-library/react` to the local `themis.compat.js` bridge
|
|
193
193
|
- `--convert`: removes common framework imports and rewrites common Jest/Vitest matcher patterns (`it`, `toStrictEqual`, `toContainEqual`, `toBeCalled*`) into Themis-native forms
|
|
194
|
+
- `--assist`: enables `--rewrite-imports` and `--convert`, then scans migrated files for leftover framework-only helpers or matcher chains that still need manual follow-up
|
|
194
195
|
|
|
195
196
|
## `themis test` options
|
|
196
197
|
|
|
@@ -218,7 +219,7 @@ Migration compatibility:
|
|
|
218
219
|
- imports from `@jest/globals` are supported at runtime
|
|
219
220
|
- imports from `vitest` are supported at runtime
|
|
220
221
|
- imports from `@testing-library/react` are supported via Themis `render`, `screen`, `fireEvent`, `waitFor`, `cleanup`, and `act`
|
|
221
|
-
- `themis migrate <jest|vitest>` also emits `.themis/migration/migration-report.json` with detected files and recommended next actions
|
|
222
|
+
- `themis migrate <jest|vitest>` also emits `.themis/migration/migration-report.json` with detected files, migration mode details, assistant findings, and recommended next actions
|
|
222
223
|
|
|
223
224
|
Additional option:
|
|
224
225
|
|
|
@@ -280,6 +281,7 @@ Formal schemas:
|
|
|
280
281
|
- `docs/schemas/fix-handoff.v1.json`
|
|
281
282
|
- `docs/schemas/failures.v1.json`
|
|
282
283
|
- `docs/schemas/contract-diff.v1.json`
|
|
284
|
+
- `docs/schemas/migration-report.v1.json`
|
|
283
285
|
|
|
284
286
|
Human-facing artifact:
|
|
285
287
|
|
package/docs/migration.md
CHANGED
|
@@ -8,6 +8,7 @@ Themis is designed for incremental migration. Start by running existing suites u
|
|
|
8
8
|
npx themis migrate jest
|
|
9
9
|
npx themis migrate jest --rewrite-imports
|
|
10
10
|
npx themis migrate jest --convert
|
|
11
|
+
npx themis migrate jest --assist
|
|
11
12
|
npx themis test
|
|
12
13
|
```
|
|
13
14
|
|
|
@@ -18,6 +19,7 @@ Use `vitest` instead of `jest` for Vitest suites.
|
|
|
18
19
|
- `themis migrate <jest|vitest>`: scaffold config, setup, compat bridge, and migration report.
|
|
19
20
|
- `--rewrite-imports`: point framework imports at `themis.compat.js`.
|
|
20
21
|
- `--convert`: remove common Jest/Vitest imports and rewrite common matcher/test patterns into Themis-native forms.
|
|
22
|
+
- `--assist`: run the safe rewrite and conversion passes together, then report leftover Jest/Vitest-only helpers that still need manual follow-up.
|
|
21
23
|
|
|
22
24
|
## Before And After
|
|
23
25
|
|
|
@@ -154,14 +156,16 @@ These are the strongest head-to-head examples to use when explaining why Themis
|
|
|
154
156
|
|
|
155
157
|
1. Snapshot replacement: `captureContract(...)` plus `--update-contracts` gives baseline capture without snapshot churn.
|
|
156
158
|
2. Codemod migration: `themis migrate --convert` moves common Jest/Vitest matcher syntax toward native Themis without a manual rewrite pass.
|
|
157
|
-
3.
|
|
158
|
-
4.
|
|
159
|
-
5.
|
|
159
|
+
3. Migration assistant: `themis migrate --assist` bundles the safe codemods and emits findings for files that still need manual migration work.
|
|
160
|
+
4. Agent triage: `--agent`, `.themis/diffs/run-diff.json`, `.themis/runs/fix-handoff.json`, and `.themis/diffs/contract-diff.json` give machines structured rerun and repair inputs.
|
|
161
|
+
5. Human review: next reporter and HTML report now surface contract drift alongside failures, instead of burying meaning in raw output.
|
|
162
|
+
6. Generated coverage: `themis generate src` adds source-driven contract tests next to migrated suites, so adoption improves coverage instead of merely changing runners.
|
|
160
163
|
|
|
161
164
|
## Recommended rollout
|
|
162
165
|
|
|
163
166
|
1. Run `themis migrate <jest|vitest>`.
|
|
164
167
|
2. Add `--rewrite-imports` if you want local explicit compat imports.
|
|
165
168
|
3. Add `--convert` to normalize the easy matcher/import cases immediately.
|
|
166
|
-
4.
|
|
167
|
-
5.
|
|
169
|
+
4. Add `--assist` when you want a guided follow-up report for leftover framework-only helpers.
|
|
170
|
+
5. Replace snapshots with `captureContract(...)` or explicit assertions in files you touch.
|
|
171
|
+
6. Use `themis generate src` to add source-driven coverage in parallel with migrated suites.
|
package/docs/roadmap.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- Add documentation (and optionally VS Code actions) that show how to hook a project-level `themis.generate.js` or `.themis.json` provider configuration for shared auth/session/React Query clients.
|
|
7
7
|
|
|
8
8
|
2. **Migration helpers**
|
|
9
|
-
- Improve `themis migrate` to rewrite Jest/Vitest imports to the generated compatibility module, create prompt-ready diff artifacts,
|
|
9
|
+
- Improve `themis migrate` to rewrite Jest/Vitest imports to the generated compatibility module, create prompt-ready diff artifacts, log a migration report, and highlight leftover blockers through migration assistant follow-up hints.
|
|
10
10
|
- Build a VS Code pane or CLI summary showing both the original Jest test and the new generated Themis contract, highlighting the migration delta in code and behavior.
|
|
11
11
|
- Provide a recipe in `docs/migration.md` for teams to adopt Themis incrementally, including a helper to wrap Jest tests inside Themis-generated asserts.
|
|
12
12
|
- Add a native contract-capture workflow that gives teams snapshot-comparable baseline coverage without reviving snapshot-file maintenance.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/vitron-ai/themis/docs/schemas/migration-report.v1.json",
|
|
4
|
+
"title": "Themis Migration Report",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema", "source", "createdAt", "mode", "summary", "files", "nextActions", "rewrites", "conversions", "assistant"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "themis.migration.report.v1"
|
|
12
|
+
},
|
|
13
|
+
"source": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": ["jest", "vitest"]
|
|
16
|
+
},
|
|
17
|
+
"createdAt": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"mode": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"required": ["rewriteImports", "convert", "assist"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"rewriteImports": { "type": "boolean" },
|
|
26
|
+
"convert": { "type": "boolean" },
|
|
27
|
+
"assist": { "type": "boolean" }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"summary": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": false,
|
|
33
|
+
"required": [
|
|
34
|
+
"matchedFiles",
|
|
35
|
+
"jestGlobals",
|
|
36
|
+
"vitest",
|
|
37
|
+
"testingLibraryReact",
|
|
38
|
+
"rewrittenFiles",
|
|
39
|
+
"rewrittenImports",
|
|
40
|
+
"convertedFiles",
|
|
41
|
+
"convertedAssertions",
|
|
42
|
+
"removedImports",
|
|
43
|
+
"assistedFiles",
|
|
44
|
+
"unresolvedFiles",
|
|
45
|
+
"findings",
|
|
46
|
+
"unsupportedPatterns"
|
|
47
|
+
],
|
|
48
|
+
"properties": {
|
|
49
|
+
"matchedFiles": { "type": "number" },
|
|
50
|
+
"jestGlobals": { "type": "number" },
|
|
51
|
+
"vitest": { "type": "number" },
|
|
52
|
+
"testingLibraryReact": { "type": "number" },
|
|
53
|
+
"rewrittenFiles": { "type": "number" },
|
|
54
|
+
"rewrittenImports": { "type": "number" },
|
|
55
|
+
"convertedFiles": { "type": "number" },
|
|
56
|
+
"convertedAssertions": { "type": "number" },
|
|
57
|
+
"removedImports": { "type": "number" },
|
|
58
|
+
"assistedFiles": { "type": "number" },
|
|
59
|
+
"unresolvedFiles": { "type": "number" },
|
|
60
|
+
"findings": { "type": "number" },
|
|
61
|
+
"unsupportedPatterns": { "type": "number" }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"files": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"additionalProperties": false,
|
|
69
|
+
"required": ["file", "imports"],
|
|
70
|
+
"properties": {
|
|
71
|
+
"file": { "type": "string" },
|
|
72
|
+
"imports": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"items": { "type": "string" }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"nextActions": {
|
|
80
|
+
"type": "array",
|
|
81
|
+
"items": { "type": "string" }
|
|
82
|
+
},
|
|
83
|
+
"rewrites": {
|
|
84
|
+
"type": "array",
|
|
85
|
+
"items": { "type": "string" }
|
|
86
|
+
},
|
|
87
|
+
"conversions": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"items": { "type": "string" }
|
|
90
|
+
},
|
|
91
|
+
"assistant": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"additionalProperties": false,
|
|
94
|
+
"required": ["enabled", "analyzedFiles", "findings", "unresolvedFiles", "unsupportedPatterns"],
|
|
95
|
+
"properties": {
|
|
96
|
+
"enabled": { "type": "boolean" },
|
|
97
|
+
"analyzedFiles": { "type": "number" },
|
|
98
|
+
"findings": {
|
|
99
|
+
"type": "array",
|
|
100
|
+
"items": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"additionalProperties": false,
|
|
103
|
+
"required": ["file", "category", "severity", "pattern", "message", "suggestion"],
|
|
104
|
+
"properties": {
|
|
105
|
+
"file": { "type": "string" },
|
|
106
|
+
"category": { "type": "string" },
|
|
107
|
+
"severity": { "type": "string" },
|
|
108
|
+
"pattern": { "type": "string" },
|
|
109
|
+
"message": { "type": "string" },
|
|
110
|
+
"suggestion": { "type": "string" }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"unresolvedFiles": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"items": { "type": "string" }
|
|
117
|
+
},
|
|
118
|
+
"unsupportedPatterns": { "type": "number" }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitronai/themis",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "A Node.js and TypeScript unit test framework designed for AI coding agents. Drop-in alternative to Jest and Vitest with machine-readable failure output, structured repair hints, and one-command migration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vitron AI",
|
|
7
7
|
"repository": {
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"agents",
|
|
24
24
|
"ai-agents",
|
|
25
25
|
"agentic",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"cursor",
|
|
28
|
+
"codex",
|
|
29
|
+
"ai-coding",
|
|
26
30
|
"jest-alternative",
|
|
27
31
|
"vitest-alternative",
|
|
28
32
|
"nodejs",
|
|
@@ -68,6 +72,7 @@
|
|
|
68
72
|
"src/assets/*",
|
|
69
73
|
"docs",
|
|
70
74
|
"templates",
|
|
75
|
+
"scripts/claude-hook.js",
|
|
71
76
|
"index.js",
|
|
72
77
|
"index.d.ts",
|
|
73
78
|
"globals.js",
|
|
@@ -89,6 +94,7 @@
|
|
|
89
94
|
"typecheck": "tsc -p tsconfig.json --pretty false",
|
|
90
95
|
"benchmark": "node scripts/benchmark.js",
|
|
91
96
|
"benchmark:showcase": "node scripts/benchmark-showcase-runners.js",
|
|
97
|
+
"benchmark:first-try": "node scripts/benchmark-first-try.js",
|
|
92
98
|
"benchmark:gate": "node scripts/benchmark-gate.js",
|
|
93
99
|
"proof:migration": "node scripts/verify-migration-fixtures.js",
|
|
94
100
|
"pack:check": "npm pack --dry-run",
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Themis Claude Code PostToolUse hook.
|
|
4
|
+
//
|
|
5
|
+
// This script is invoked by Claude Code after Edit/Write/MultiEdit tool calls.
|
|
6
|
+
// It reads the tool input from stdin, decides whether the edit is worth
|
|
7
|
+
// re-running tests for, and if so runs `themis test --reporter agent` (using
|
|
8
|
+
// --rerun-failed when there is a prior failed-tests artifact). When tests
|
|
9
|
+
// fail, the JSON failure payload is written to stderr and the script exits
|
|
10
|
+
// with code 2 — Claude Code feeds that back into the model so it can fix
|
|
11
|
+
// failures using the structured `failures[].cluster` and
|
|
12
|
+
// `failures[].repairHints` fields.
|
|
13
|
+
//
|
|
14
|
+
// Wire it up in `.claude/settings.json`:
|
|
15
|
+
//
|
|
16
|
+
// {
|
|
17
|
+
// "hooks": {
|
|
18
|
+
// "PostToolUse": [
|
|
19
|
+
// {
|
|
20
|
+
// "matcher": "Edit|Write|MultiEdit",
|
|
21
|
+
// "hooks": [
|
|
22
|
+
// { "type": "command", "command": "node node_modules/@vitronai/themis/scripts/claude-hook.js" }
|
|
23
|
+
// ]
|
|
24
|
+
// }
|
|
25
|
+
// ]
|
|
26
|
+
// }
|
|
27
|
+
// }
|
|
28
|
+
//
|
|
29
|
+
// Disable temporarily by setting THEMIS_HOOK_DISABLED=1 in your environment.
|
|
30
|
+
// Disable permanently by removing the entry from .claude/settings.json.
|
|
31
|
+
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
const path = require('path');
|
|
36
|
+
const { spawnSync } = require('child_process');
|
|
37
|
+
|
|
38
|
+
const SOURCE_EXT = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
|
|
39
|
+
const IGNORED_PATH_SEGMENTS = ['.themis', '__themis__', 'node_modules', '.git'];
|
|
40
|
+
|
|
41
|
+
function exitSilent() {
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readStdinSync() {
|
|
46
|
+
try {
|
|
47
|
+
return fs.readFileSync(0, 'utf8');
|
|
48
|
+
} catch (_err) {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parsePayload(raw) {
|
|
54
|
+
if (!raw) return null;
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(raw);
|
|
57
|
+
} catch (_err) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function extractFilePath(payload) {
|
|
63
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
64
|
+
const input = payload.tool_input;
|
|
65
|
+
if (!input || typeof input !== 'object') return null;
|
|
66
|
+
if (typeof input.file_path === 'string') return input.file_path;
|
|
67
|
+
// MultiEdit nests edits in an array but still uses the top-level file_path.
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isWorthRerunning(filePath, cwd) {
|
|
72
|
+
if (!filePath) return false;
|
|
73
|
+
const normalized = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
74
|
+
const relative = path.relative(cwd, normalized);
|
|
75
|
+
if (relative.startsWith('..')) return false;
|
|
76
|
+
|
|
77
|
+
const segments = relative.split(path.sep);
|
|
78
|
+
for (const segment of segments) {
|
|
79
|
+
if (IGNORED_PATH_SEGMENTS.includes(segment)) return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const ext = path.extname(normalized).toLowerCase();
|
|
83
|
+
if (!SOURCE_EXT.has(ext)) return false;
|
|
84
|
+
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hasFailedTestsArtifact(cwd) {
|
|
89
|
+
const candidates = [
|
|
90
|
+
path.join(cwd, '.themis', 'runs', 'failed-tests.json'),
|
|
91
|
+
path.join(cwd, '.themis', 'failed-tests.json')
|
|
92
|
+
];
|
|
93
|
+
return candidates.some((candidate) => fs.existsSync(candidate));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function findThemisBin(cwd) {
|
|
97
|
+
// Prefer the locally installed bin so the hook does not depend on `npx`
|
|
98
|
+
// resolution behavior or network state.
|
|
99
|
+
const localBin = path.join(cwd, 'node_modules', '.bin', 'themis');
|
|
100
|
+
if (fs.existsSync(localBin)) return { command: localBin, args: [] };
|
|
101
|
+
const localScript = path.join(cwd, 'node_modules', '@vitronai', 'themis', 'bin', 'themis.js');
|
|
102
|
+
if (fs.existsSync(localScript)) return { command: process.execPath, args: [localScript] };
|
|
103
|
+
return { command: 'npx', args: ['--no-install', 'themis'] };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function main() {
|
|
107
|
+
if (process.env.THEMIS_HOOK_DISABLED) exitSilent();
|
|
108
|
+
|
|
109
|
+
const raw = readStdinSync();
|
|
110
|
+
const payload = parsePayload(raw);
|
|
111
|
+
const cwd = (payload && typeof payload.cwd === 'string' && payload.cwd) || process.cwd();
|
|
112
|
+
const filePath = extractFilePath(payload);
|
|
113
|
+
|
|
114
|
+
if (!isWorthRerunning(filePath, cwd)) exitSilent();
|
|
115
|
+
|
|
116
|
+
const themis = findThemisBin(cwd);
|
|
117
|
+
const args = [...themis.args, 'test', '--reporter', 'agent'];
|
|
118
|
+
if (hasFailedTestsArtifact(cwd)) args.push('--rerun-failed');
|
|
119
|
+
|
|
120
|
+
const result = spawnSync(themis.command, args, {
|
|
121
|
+
cwd,
|
|
122
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
123
|
+
env: process.env,
|
|
124
|
+
maxBuffer: 32 * 1024 * 1024
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (result.error) {
|
|
128
|
+
// Hook itself failed (binary not found, etc). Stay silent rather than
|
|
129
|
+
// blocking the user's edit loop on infrastructure problems.
|
|
130
|
+
exitSilent();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const stdout = (result.stdout || Buffer.alloc(0)).toString('utf8');
|
|
134
|
+
const stderr = (result.stderr || Buffer.alloc(0)).toString('utf8');
|
|
135
|
+
|
|
136
|
+
if (result.status === 0) {
|
|
137
|
+
exitSilent();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Tests failed. Surface the agent JSON payload (or stderr fallback) to
|
|
141
|
+
// Claude via stderr + exit 2 so it lands in the model's context.
|
|
142
|
+
process.stderr.write('Themis tests failed after edit. Use failures[].cluster and failures[].repairHints below to fix:\n');
|
|
143
|
+
if (stdout.trim().length > 0) {
|
|
144
|
+
process.stderr.write(stdout);
|
|
145
|
+
if (!stdout.endsWith('\n')) process.stderr.write('\n');
|
|
146
|
+
} else if (stderr.trim().length > 0) {
|
|
147
|
+
process.stderr.write(stderr);
|
|
148
|
+
if (!stderr.endsWith('\n')) process.stderr.write('\n');
|
|
149
|
+
}
|
|
150
|
+
process.exit(2);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
main();
|