zotoio/zoto-eval-system
Generate, run, and consciously update static (pytest) + LLM (@cursor/sdk) evals when AI primitives change — every proposed change requires user confirmation. YAML+JSONSchema results, surgical update on git diff, sovereign user-authored cases, /canvas-rendered cross-run compare.
Changelog
All notable changes to the Eval System plugin will be documented in this file.
[unreleased] — eval-system v3 (dual-mode host layout)
BREAKING
- Dual-mode host layout.
/z-eval-createnow stamps lean layout by default: repo-specific assets under.zoto/eval-system/(config.yml,manifest.yml,cache/,.gitignore, nestedpackage.json,scripts/eval-bridge.ts). Engine, scripts, and templates resolve from the installed plugin at runtime via the bridge andresolvePluginRoot(). Full self-contained runtime is opt-in viapnpm run eval:stamp-host-layout(CLI only — no new slash command). hostLayoutconfig marker —plugin(lean, default) orejected(self-contained). Scripts branch on this value; eject/un-eject CLIs patch it automatically.- Ejected primitives — on eject, eval-system agents/skills/commands copy to
.cursor/{agents,skills,commands}/eval-sys--*(flat-prefix default) or.cursor/*/eval-sys/(nested), not.zoto/eval-system/agents/. - Un-eject CLI —
pnpm run eval:un-ejectremoves vendored runtime and ejected primitives, restores lean layout, preserves config/manifest/evals. External repos that previously ran full-stamp create should run un-eject to adopt lean mode.
Fixed
- Static vitest catalogue paths —
stampVitestPerPrimitivenow emitsevals/test_<kind>_<name>.test.ts(same basename asregenerateVitest). Kind-prefixed slugs frombuildPrimitiveMetaFromPayloadno longer produce missingtest_skill_skill_*files that the catalogue still listed. - Discovery catalogues co-located JSON —
eval:discover/eval:updateprefer<kind-dir>/evals/<name>.jsonover the legacyplugins/<p>/evals/<kind>s/tree, so the manifest lists real JSON suites instead of empty coverage or generatedevals/test_*.test.tsrows that are not on disk. - Lean LLM Vitest reliability —
run-vitest.tsruns from the host repo cwd with the plugin Vitest onPATH;ensure-native-deps.tsbuildssqlite3when pnpm skipped lifecycle scripts;lean-root-vitest.jsonmerges harness devDeps into the consumer rootpackage.jsonat init/create; stampedvitest.config.tsscopes includes to host primitives (avoids plugin self-eval discovery); harness templates use#eval-engine/sdk-bridge.jsand comment-safe docs (no**/or fenced examples that break Vite config bundling).
Added
- Lean vs ejected
package.jsonsplit. Lean hosts mergetemplates/package-scripts/lean.json(onlyeval+eval:full+tsx/dotenv); full eval runtime deps and all other CLI scripts live in the installed plugin. Ejected hosts carry the full contract in.zoto/eval-system/package.json; root aliases delegate viapnpm -C .zoto/eval-system …. The orchestrator invokes plugin backends directly in lean mode (no consumereval:vitestalias required). run-vitest.ts/run-jest.ts— lean bridge targets that execute Vitest/Jest from the plugin install against the host'sevals/*config.ZOTO_EVAL_HOST_REPO— set byeval-bridge.tsso plugin scripts resolve the consumer repo when spawned withcwdat the plugin root.- Consumer-repo
.envloading. Stamped.zoto/eval-system/scripts/eval-bridge.tsloads<repoRoot>/.envviadotenvbefore everypnpm run eval:*command. Existing bridges are auto-upgraded on the next/z-eval-createlean-layout stamp when they lack the loader (no--force-bridgerequired). stamp-lean-layouthost env bootstrap. Lean create callsensure-host-env-and-gitignore.tsinline so.env.exampleand repo-root.gitignorerules are applied even wheneval:ensure-hostis not run separately./z-eval-inithost env bootstrap. Init invokesensure-host-env-and-gitignore.tsafter writing config so first-time hosts get.env.exampleand.gitignorecoverage before/z-eval-create.- Repo-root
README.mdandAGENTS.mdupsert.templates/host-package/README.md.tmplandAGENTS.md.tmplare stamped at the repository root on init, lean create, andeval:ensure-hostwhen missing (never overwrite existing files). Ejectedeval-ensure-host.ts.tmplmirrors the same behaviour from vendored templates. resolvePluginRoot()insrc/paths.ts— single resolution function with precedence: monorepoplugins/zoto-eval-system/→ZOTO_EVAL_PLUGIN_ROOT→ Cursor install dir (semver-aware).scripts/stamp-lean-layout.ts— lean create path: repo-specific assets, rooteval-bridge.ts, merged rootpackage.jsonaliases + minimal devDeps.scripts/eval-un-eject.ts— reverse eject, restore lean plugin-dependent mode.scripts/eval-bridge.ts(stamped at.zoto/eval-system/scripts/eval-bridge.ts) — cross-platform wrapper; loads<repoRoot>/.envthen execs plugin scripts viatsxwithout shell interpolation.eval:un-ejectpackage.json alias.
Changed
scripts/stamp-host-layout.ts— refactored as explicit eject CLI; copies runtime + stamps ejected primitives; setshostLayout: ejected./z-eval-init— runs rootpnpm installafter writing config (lean devDeps)./z-eval-create/zoto-create-evals— lean-only stamping; no longer callsstamp-host-layout.ts.- Monorepo dogfood — root
eval:*aliases may continue to invokeplugins/zoto-eval-system/scripts/andengine/directly (contributor shortcut); consumer lean hosts use.zoto/eval-system/scripts/eval-bridge.tsand nestedpackage.json.
[unreleased] — eval-system v3 (self-contained host layout)
Superseded (2026-06-01). The dual-mode host layout entry above replaces this section. Greenfield default is now lean (
hostLayout: plugin); full self-contained runtime is opt-in viapnpm run eval:stamp-host-layout. Historical notes below describe the intermediate self-contained-only rollout and remain for changelog archaeology.
Added
src/paths.ts— central path resolver for eval-system host installs. ExportsresolveEvalPaths,loadEvalPaths,resultSchemaPath,analyserSchemaPath, andanalyserAgentPath.- Layout detection —
legacy-root(repo-rootevals/+scripts/) vsself-contained(everything under.zoto/eval-system/). (Superseded: greenfield installs now default to lean; eject for self-contained.) - Dogfood wiring —
scripts/eval-orchestrate.ts,eval-discover.ts, andeval-analyse.tsnow resolve templates, cache, schema, and evalsDir vialoadEvalPaths()instead of hard-codedplugins/zoto-eval-system/…strings.
Changed
discovery_configfromeval:discovernow emits resolvedevalsDir(repo-relative) andlayoutfor manifest snapshots.
Added (Phase 2)
scripts/stamp-host-layout.ts— copies self-contained runtime (src/,templates/,engine/, vendored CLI scripts, analyser agent) into.zoto/eval-system/and writes nestedpackage.json.scripts/migrate-host-layout-v3.ts— moves legacy rootevals/+scripts/eval-*into.zoto/eval-system/, strips eval pollution from rootpackage.json, optional root aliases.templates/host-package/package.json— eval-only scripts and devDependencies for nested install.
Changed (Phase 2)
eval-stamp.ts— template/engine/evals paths resolved vialoadEvalPaths().zoto-create-evalsskill — stamps self-contained layout instead of merging rootpackage.json. (Superseded: create now stamps lean layout only.)templates/config.json—manifestPath/historyPathrelative to eval home (manifest.yml).eval-ensure-host.ts.tmpl— self-contained paths under.zoto/eval-system/.
Removed from Phase 2 plan
- Root
package-json-merger.tsmerge on greenfield hosts (legacy migration still strips old keys).
Changed (Phase 3 — self-contained script consolidation)
- Single source of truth for host CLI (KD-1). All host eval CLI now lives under
plugins/zoto-eval-system/scripts/(eval-analyse.ts,eval-stamp.ts,eval-orchestrate.ts,eval-discover.ts,eval-gc.ts,eval-cleanup-vendored.ts,eval-cleanup-stale.ts,eval-ensure-host.ts,check-analyser-payload-parity.ts,test.py). The previously divergent repo-rootscripts/eval-*.tscopies are gone; repo-rootscripts/now holds only monorepo CI/migration tooling. - Plugin-relative imports only (KD-2). Moved scripts import
../src/…and../engine/…; no more../plugins/zoto-eval-system/…or../../../scripts/…cross-boundary paths. engine/update.tsis the canonical updater (KD-4). It now imports from../scripts/within the plugin tree. The supersededplugins/zoto-eval-system/scripts/eval-update.tsand the staleeval-discover.tsfork were deleted.- Stamper sources from the plugin (KD-5).
stamp-host-layout.tscopiesHOST_SCRIPT_NAMESfromPLUGIN_ROOT/scripts/, not from a sibling monoreposcripts/folder;zotoAgentsRootis no longer required for normal stamp operations. - Monorepo dogfood retargeted (KD-6). Root
package.jsoneval:*aliases invoketsx plugins/zoto-eval-system/scripts/<name>.tsdirectly, so dogfood exercises the same canonical scripts marketplace consumers run.
Fixed (Phase 3)
- Standalone install now carries the full runtime.
install-local.tsincludesengine/andsrc/inPLUGIN_DIRS, so installing the plugin outside the monorepo (marketplace orinstall-local) no longer leaves skills/commands referencing a missing siblingscripts/folder or absentengine//src/modules.
[unreleased] — 2026-05-27
BREAKING
- All non-skill primitive evals are now stored as
.jsoninstead of co-located.test.tsfiles. The Vitest JSON loader plugin (evals/llm/_shared/vitest-json-loader.ts) synthesises in-memory test suites from<kind>/evals/<name>.jsonat discovery time. Skills retainskills/<name>/evals/evals.jsonper the Cursor Agent Skills spec. - New
runner+parameterscase shape documented. JSON cases may opt into TypeScript via arunnerfield pointing at a sibling.test.tsfile whose default export implementsRunnerFnfromevals/llm/_shared/runner-params.ts. See the README "Eval formats" / "Advanced TS escape hatch" sections. - Multi-primitive scenarios convention. Advanced flows spanning multiple primitives live under
evals/scenarios/<name>.test.ts./z-eval-createcopies an underscore-excluded example viascripts/eval-ensure-host.ts. eval:llmscript removed. The unifiedevals/vitest.config.tshandles static smoke, JSON evals, and scenarios in a single Vitest invocation.- Manifest schema enforces JSON eval paths.
manifest.ymleval_files[]entries now reference.jsonpaths for non-skill primitives. - Migration script
scripts/eval-migrate-ts-to-json.tsruns idempotently for any latecomers still on the old.test.tslayout.
Added
plugins/zoto-eval-system/templates/scenarios/_example-multi-primitive.test.ts.tmpl— canonical multi-primitive scenario walkthrough.zoto-help-evalsskill: new Q&A surface covering the runner escape hatch and the underscore-excluded scenarios convention; a new user-authored eval case (id: 4) asserts the help skill answers correctly when users ask "How do I write a TS eval?".
[unreleased] — 2026-05-26
Superseded by the 2026-05-27 BREAKING entry above. The statements below describe the intermediate co-located
.test.tslayout that preceded the JSON-first migration.
BREAKING
- Removed
llm.strategyandllm.codeFrameworkconfig fields. The eval system now uses a single unified LLM backend; all non-skill primitives emit a co-located<kind>/evals/<name>.test.tsfile. Skills retainskills/<name>/evals/evals.jsonper the Cursor Agent Skills spec. See the repo-rootCHANGELOG.mdfor the canonical entry. - Cleanup engine
strategy-switchbranch removed. Cleanup now only handles framework-switch (vitest ↔ jest for the static side).
Changed
- Relocated 38 stamped artefacts to co-located paths under
plugins/<p>/{commands,agents,hooks}/evals/and.cursor/{commands,agents,hooks}/evals/. User-authored content was preserved verbatim through a strict_meta.generated === truemigration gate. - Renamed harness module
evals/llm/_shared/run-code-strategy-suite.ts→evals/llm/_shared/run-llm-suite.ts; renamed exported entrydefineLlmCodeEval→defineLlmEvaland case typeCodeStrategyCaseDefinition→LlmCaseDefinition. - Vitest config rooted at repo root with include glob
**/evals/*.test.ts; the LLM-specificevals/llm/vitest.config.tsis removed.
Removed
- 10 redundant
evals/llm/test_skill_*.test.tsfiles (skill coverage moves entirely toevals.json). - 2 static-stamped Vitest pilots for skill primitives at
evals/test_skill_skill_*.test.ts.
[Unreleased]
Changed
- Renamed slash commands from
/zoto-eval-*to/z-eval-*. The old/zoto-eval-*names have been removed. - LLM code-strategy template refactored to thin-file pattern.
templates/llm/code-cursor-sdk/per-primitive-test.ts.tmplreduced from ~240 lines to ~30 lines. The inlinedCaseDefinitioninterface and the full runner loop are replaced by imports from_shared/code-strategy-case.ts(shared types, subtask 03) and_shared/run-code-strategy-suite.ts(central harness, subtask 04). Stamped test files now calldefineLlmCodeEval()instead of duplicating the describe/it/grader dispatch. All existing template placeholders ({{CASES_JSON}},{{TARGET_ID}},{{MODEL_ID}}, etc.) are preserved; no changes required ineval-stamp.tssubstitution logic.
Added
/z-eval-startslash command — operator-facing jump into the evaluator lifecycle; delegates tocommands/z-eval-workflow.mdafter the init gate (read-only; no subagents)/z-eval-jumpslash command — same read-only delegation as/z-eval-startfor docs and runbooks that prefer a “jump” verb/z-eval-operatorslash command — same read-only delegation as/z-eval-startfor runbooks and ops-facing docs/z-eval-workflowslash command — singleaskQuestionlifecycle router to the right/z-eval-*command (read-only; no subagents)zoto-advise-evalsskill implementing five-dimension gap taxonomy/z-eval-advisecommand with multi-turn askQuestion interaction
Documentation
- Voice / positioning pass for the equal-billing site revamp — README opening voice aligned with the landing-page hero (peer billing alongside the spec-system, no single-plugin-highlight wording).
[0.1.0] - 2026-05-03
Added
- Initial plugin scaffold with Cursor plugin manifest
- Two eval backends:
- Static pytest backend (
templates/static/pytest/) — conftest, fixtures, runner script - LLM backend (
templates/llm/agent-sdk/) —@cursor/sdk-driven runner with per-case logs, YAML results, graders (contains, regex, tool-called, llm-judge), and soft metrics (tokens, duration, verbosity, accuracy, confidence)
- Static pytest backend (
- Seven agents:
zoto-eval-generator,zoto-eval-configurer,zoto-eval-updater,zoto-eval-executor,zoto-eval-judge,zoto-eval-comparer - Seven commands:
/z-eval-configure,/z-eval-create,/z-eval-update,/z-eval-execute,/z-eval-judge,/z-eval-compare,/z-eval-help - Seven skills (each with ≥2 eval cases): configure, create, update, execute, judge, compare, help
- Diff-aware update system:
_meta.generatedcontract — user-authored cases are never mutated- Targeted mode (by file/glob) and rediscovery mode
--checkmode for CI (exit 2 on critical drift)- Append-only
.zoto/eval-system/manifest.history.yml
- Schemas:
config.schema.json,manifest.schema.json,result.schema.json,case-meta.schema.json /canvashand-off for cross-run comparison (skill emits structured dataset; host/canvastool renders)- Session-start hook nudging on stale runs, missing evals, detected drift
- Validation pipeline (
scripts/validate-plugin.ts) enforcing the_meta.generatedinvariant at compile time
[0.2.0] - eval-system v2 — 2026-05-04
Breaking / behavioural (from-current-state migration)
Host repos should treat the items below as the migration checklist when aligning with eval-system v2:
-
Config: New fields —
static.framework(pytest|vitest|jest),llm.strategy(code|declarative),llm.codeFramework(vitest|jest, required when LLM strategy iscode). Analyser:analyser.concurrency,analyser.maxCallsPerInvocation(subtask 04 guardrails). Runs:runs.retentionconsumed bypnpm run eval:gc/eval:gc:apply. Switching static framework or LLM strategy is not a silent edit: run/z-eval-configure, review thecleanup_plan, and confirm execution ofscripts/eval-cleanup-stale.ts. -
Outputs: The monolithic
results.ymlname is retired for new runs. Per backend:static.yml(static framework),llm.yml(LLM strategy). Merged roll-up:report.yml(references per-backend files; includes optionaldriftfrom the post-runeval:update --checkhook). Each run folder also has.run-meta.json(run id, frameworks, model, git ref). Legacyresults.ymlmay still be read for historical snapshots only. -
Mutual exclusion: Static framework and LLM strategy are chosen in config and drive mutually exclusive stamping paths; flipping them requires configure + cleanup — not an in-place tweak of a single field without the cleanup flow.
-
Generated cases: Rows/stamped files embed
_meta.primitive_analysiswith at leastsource_hash,analyser_version, andsummary, aligned with subtask 04’sAnalyserPayloadand the updater’s refresh path. -
User-authored preservation: Hard-coded — no opt-out. Guards live in
evals/_llm/_user-case-guards.ts(isGeneratedFilefor first-line marker on test sources,isGeneratedCaseforevals.jsonrows). -
Templates: Bats additional templates under
templates/additional/bats/are removed. Orphan generated assets from config or discovery changes may be grouped under cleanup reasonremoved-target(subtask 03).
Changed
- Help skill (
zoto-help-evals) is now project-tailored with mandatory citations. The skill no longer prints README sections verbatim. It anchors on the chosen section, reads the host repo's eval-system signals (.zoto/eval-system/config.yml,manifest.yml,.env*,package.jsoneval*scripts, latestevals/_runs/<run-id>/), and composes a paraphrased answer that cites the README inline using thestart:end:plugins/zoto-eval-system/README.mdcode-reference syntax. Project-specific facts must be backed by reading the actual file (no inferred defaults). The/z-eval-helpcommand and theHelp-Intent Routingrule were updated to match. The skill still does not callaskQuestion— the command pre-collects answers and the skill returnsneeds_user_inputwhen missing context. Eval cases for the help skill were rewritten accordingly.