Configuration files
Plugin configuration
plugin.json
The .tessl-plugin/plugin.json file is the configuration file for a plugin. It includes important metadata that helps Tessl identify, version and describe plugins.
Plugins can contain four types of content:
rules: Rule files (markdown) that provide subjective guidance and instructions to agents, such as coding standards, best practices, and organizational policies.
skills: Skill files (SKILL.md format) that extend AI coding agents with specialized knowledge, workflows, or tool integrations. Skills follow the Agent Skills Specification.
MCP servers: Model Context Protocol servers, declared in a bundled
.mcp.jsonfile, that connect agents to external tools and services during a session.hooks: Deterministic commands that run automatically in response to agent events (for example, running a linter after an edit), declared with the
hooksandnativeHooksfields.
A plugin can contain one or more of these content types. For example, a plugin might have only rules, only skills, only MCP servers, or any combination of the four.
Here's an example .tessl-plugin/plugin.json:
{
"name": "tessl/skill-optimizer",
"version": "0.8.1",
"description": "Optimize your skills and plugins: review SKILL.md quality, generate eval scenarios, run evals, compare across models, diagnose gaps, and re-run until scores improve.",
"private": false
}The following sections show examples of plugins with different content types. Remember that plugins can contain rules, skills, MCP servers, or hooks.
For example, here's what the ./tessl-plugin/plugin.json for a private, rules-only plugin might look like:
Plugins can also contain skills, which are reusable capabilities that extend AI coding agents. Here's an example of a plugin with skills:
And here's a plugin that combines rules, and skills:
.tessl-plugin/plugin.json supports the following fields:
name (string, required): Name for the plugin in workspace/plugin-name format
version (string, required): Semantic version of the plugin
description (string, required): Brief description of the plugin
private (boolean, optional): Controls plugin visibility in the registry. Set to false to make the plugin publicly discoverable by all users, or true to restrict access to your workspace only. Defaults to true if not specified. Note: Authentication with tessl login is required to publish plugins regardless of this setting.
rules (string or string[], optional): Path to a directory of rule files, or an array of specific .md file paths. Rules are always-loaded guidance for agents — the plugin.json equivalent of steering. Omit to use convention discovery, which scans ./rules/ for .md files automatically.
skills (string or string[], optional): Path to a directory containing skill subdirectories, or an array of specific skill directory paths. Each skill directory must contain a SKILL.md file. Skills are reusable capabilities that extend AI coding agents with specialized knowledge, workflows, or tool integrations. Omit to use convention discovery, which scans ./skills/ for subdirectories containing SKILL.md automatically.
mcpServers (string, optional): Pointer to the plugin's bundled .mcp.json file at the plugin root. The value is fixed — either ".mcp.json" or "./.mcp.json". Declaring it is optional and non-gating: the .mcp.json file is discovered by convention at the plugin root whether or not the manifest mentions it, so the field only lets authors be explicit. Note that this string field is a pointer to the file — not to be confused with the mcpServers map inside the .mcp.json file itself (see below), which lists the actual servers. See Bundled .mcp.json for the file's shape.
hooks (object, optional): Cross-agent (generic) hooks, deterministic commands that run in response to agent events. A map of event name to hook groups, authored once and dispatched to every supported agent. See Hooks for the shape.
nativeHooks (object, optional): Per-agent (native) hooks, hook entries written in an individual agent's own hook format, keyed by agent id. Use this when a hook needs an agent-specific event or field the generic hooks field can't express. See Hooks.
A note on plugin.json validation:
rules,skills,mcpServers,hooks, andnativeHooksare all optional. A plugin with none of these relies entirely on convention discovery. There is no requirement that at least one content type be explicitly declared.
Bundled .mcp.json
.mcp.jsonA plugin declares MCP (Model Context Protocol) servers by adding a .mcp.json file at the plugin root — the same format Claude Code already reads in a repository. This is the file the mcpServers pointer field above points at; it is discovered by convention even when the field is omitted. (The pointer field shares the mcpServers name with the map inside this file, described below, but the two are distinct: the field names the file, the map lists the servers.)
Note: This is a different file from the
.mcp.jsonTessl writes into a consuming project (see MCP configuration files below). The plugin-bundled.mcp.jsonis authored and shipped inside the plugin package to declare the servers it provides. The project.mcp.jsonis generated by Tessl in the consuming repository to wire the agent up to Tessl's owntessl mcpserver. They share a name and format but serve different purposes.
The file has a top-level mcpServers map of server name to server config. Each config is one of two transport types, selected by its type field:
stdio — a server the agent launches as a local subprocess:
type(string, required):"stdio".command(string, required): The executable to run.args(string[], optional): Arguments passed to the command.env(object, optional): Environment variables (string values) set for the process.
http — a server reachable over HTTP:
type(string, required):"http".url(string, required): The server endpoint. Must be anhttporhttpsURL.headers(object, optional): HTTP headers (string values) sent with each request.
How bundled servers are installed
When a plugin is installed, Tessl materialises its bundled servers into each supported agent's own native MCP configuration — the file that agent already reads, in the format and location it expects. By default servers are written into the project; installing with --global writes them into each agent's user-scope configuration instead.
Tessl reconciles these servers on every install and prunes them when the plugin is uninstalled. Your own hand-added MCP servers are always left untouched. If you hand-edit a server Tessl wrote, it stops recognising the entry as its own and leaves it in place with a warning rather than overwriting it.
Hooks
Hooks are deterministic commands that run automatically in response to events within an agent harness: running a linter after an edit, validating a spec when a route handler changes, or posting a summary when a session ends. A plugin declares hooks in .tessl-plugin/plugin.json using one of two tiers, expressed as two separate top-level fields:
hooks: the cross-agent (generic) tier. You author one command against Tessl's generic event schema, and Tessl dispatches it to each agent that supports the event. This is the paved road: use it whenever the hook's logic is agent-independent (a linter, a validator, a notifier) so it works across agents without per-agent authoring. Not every agent implements every event; an event an agent doesn't support is skipped for that agent (see the note below).nativeHooks: the per-agent (native) tier. You write hook entries in an individual agent's own hook format, keyed by agent id. This is the escape hatch: use it when a hook needs an agent-specific event or field that the generic schema can't express. Tessl carries these entries through near-verbatim. It does not own the shape, so each agent validates its own entries.
A plugin can use either tier or both.
Cross-agent hooks (hooks)
hooks)The hooks field is a map of event name to an array of hook groups. The supported event names are:
PreToolUse: before the agent runs a tool.PostToolUse: after a tool call completes.UserPromptSubmit: when the user submits a prompt.SessionStart: when a session begins.Stop: when the agent finishes responding.
Note: Agents vary in which of these events they implement. Gemini, for example, supports only
PreToolUse,PostToolUse, andSessionStart. When an agent does not implement a declared event, that event is skipped for that agent (the rest still install), so prefer events supported by the agents you target.
Each hook group has an optional matcher and one or more command hooks:
matcher(string, optional): A tool-name filter: a single tool name (Bash), a pipe-separated list (Edit|Write), or a regex. Omit it to fire the group for every tool call. It only narrows tool-call events (PreToolUse,PostToolUse); onUserPromptSubmit,SessionStart, andStopthere is no tool name to match, so omit it.hooks(array, required): One or more command hooks (see below).
A command hook has a type of "command" and takes one of two forms, selected by whether args is present:
Exec form (
argspresent):commandis the program to spawn (argv[0], e.g.bashornode) andargsare its arguments. The program is spawned directly, with no shell, so pipes, globs, and&&are not interpreted.Shell form (
argsabsent):commandis a full command string run viash -c, so shell features like pipes, globs, and&&work.
Command hook fields:
type(string, required): Always"command".command(string, required): The program (exec form) or the shell command string (shell form).args(string[], optional): The program's arguments. Its presence selects exec form; its absence selects shell form.[]is a valid exec form (a program with no arguments).env(object, optional): Environment variables for the hook. A value can be a static string, or{ "fromEnv": "VAR" }to forward the named variable from the agent's shell environment at hook-fire time.
To reference a file bundled inside your plugin, use the ${TESSL_PLUGIN_DIR} token: it is substituted across command and args with the plugin's installed location. A bare relative path is not anchored to the plugin; it resolves against the event's working directory (the project root) at spawn time, like any argument. In shell form, quote the token ("${TESSL_PLUGIN_DIR}/lint.sh") so paths with spaces survive.
Note: Shell form runs under POSIX
sh -c(on Windows this relies on git-bash). PowerShell andcmd.exeare not used.
Per-agent hooks (nativeHooks)
nativeHooks)The nativeHooks field is a map keyed by agent id (for example claude-code). Each agent's value is that agent's own native hook config, keyed by the agent's own event names, which Tessl passes through near-verbatim. Because Tessl does not own this shape, refer to the target agent's own hook documentation for the events and fields it accepts.
${TESSL_PLUGIN_DIR} works here too, but is resolved at install time (not hook-fire time) into a path the agent will find when the hook fires: anchored to the agent's project-directory variable or a repo-relative path in project scope, and an absolute path in global scope. Resolved paths that would escape the plugin directory are rejected.
How hooks are installed
When a plugin is installed, Tessl materialises its hooks into each supported agent's own native hook configuration: the file that agent already reads.
Cross-agent hooks are installed as a
tessl hook rundispatch entry: the agent invokes Tessl when the event fires, and Tessl runs your declared command, translating the agent's native event payload to and from the generic schema.Per-agent hooks are written into the agent's config as your own entries (after
${TESSL_PLUGIN_DIR}is resolved).
By default hooks are written into the project; installing with --global writes them into each agent's user-scope configuration instead. Agents without a user-scope hook location are the exception: OpenHands, for instance, has no global hook config, so --global skips it with a warning rather than writing an unread file. Tessl tracks the entries it writes and prunes them when the plugin is uninstalled, leaving your hand-added hooks untouched.
Project configuration
tessl.json
The tessl.json file is the manifest for your repository's plugin dependencies. It specifies which plugins are installed and their versions:
This file is created automatically when you run tessl init, tessl install, or tessl project create. Tessl manages the dependencies in this file as you install or uninstall plugins.
Verify block
The optional verify block in tessl.json configures tessl change verify. It groups the standalone verifier JSON files in your repository, points each group at the files it should check, and sets the severity of its findings. The block is the runtime source of truth for verification — tessl change verify reads it plus the verifier files its groups reference.
verify
verifygroups(object, optional): Named groups of verifiers. Keys are arbitrary group names; values are group entries (see below).
Group entry
Each entry under verify.groups accepts:
path(string or string[], required): Path or glob (or list of them) to the verifier JSON files this group runs.level(string, required): Severity for the group's findings — one ofinfo,warn, orerror.errormakes a failing check blocking in CI;warnandinfoare advisory.include(string[], optional): Globs of files this group targets. Files added later that match are covered automatically.exclude(string[], optional): Globs of files to drop from the group's targets.content(object, optional): Content-based file filtering (see Content predicate).enabled(boolean, optional): Set tofalseto skip the whole group.verifiers(object, optional): Per-verifier overrides, keyed by verifier file name. Each override acceptsinclude,exclude,content,enabled, andlevel— letting the same verifier be advisory (warn) in one project and blocking (error) in another without editing the verifier JSON.
Content predicate
The content field (on a group or a per-verifier override) keeps or drops files by what they contain, before the judge runs:
containsAny(string[]): Keep files containing at least one of these literal strings.containsAll(string[]): Keep files containing every one of these literal strings.regexAny(string[]): Keep files matching at least one JavaScript regular-expression pattern.regexAll(string[]): Keep files matching every JavaScript regular-expression pattern.notContainsAny(string[]): Drop files containing any of these literal strings.notRegexAny(string[]): Drop files matching any of these JavaScript regular-expression patterns.
Verifier JSON files
The files referenced by a group's path describe the invariant to check. A verifier file accepts:
name(string, required): Short human label for CLI output. The filename remains the stable identifier.instruction(string, required): What the judge should check for.relevant_when(string, required): When the verifier applies.context(string, required): Background the judge needs to score the file.scope(string, required): The unit the judge evaluates —file,paths, ordiff.scope_reason(string, required): Why that scope is the right target shape.checklist(array, required): One or more check items, each with aname, arule, and an optionalrelevant_when.references(array, optional): Informational provenance for where the rule came from.project_when(object, optional): A precondition evaluated once per run (no LLM call); when it is false the verifier is skipped entirely. Supportspackage_json_has_dep,file_exists,glob_matches, and the combinatorsany_of,all_of, andnot.covered_by(array, optional): Links to existing lint rules or tests that already enforce the rule. When every link is fresh the judge is skipped and the verifier reports ascovered; when any link is stale the judge runs anyway and the staleness is surfaced.
Validate a verifier file with tessl change verify lint <file>, and preview which files a group matches with tessl change verify --dry-run --all --show-files before running judge calls. See Setting up agentic code review for the end-to-end workflow.
Tessl project link
tessl.json stores the Tessl configuration for this repository, including its project link.
A Tessl project gives Tessl a stable place to attach eval runs and other repository-connected data, so your results stay tied to your codebase over time.
For example:
If the local project link is missing or no longer correct, use tessl project link or tessl project repair. See Manage projects from the CLI.
Project Mode (Managed vs Vendored)
The mode field in tessl.json controls how plugin content is managed in your repository. You can choose between two modes depending on your workflow and requirements:
Managed mode
Default for existing repositories - plugin contents are gitignored like node_modules
Behavior:
plugin contents in
.tessl/plugins/are automatically added to.gitignoreplugins are reinstalled from the registry based on
tessl.jsonWorks like package managers (npm, pip) - dependencies not committed
Keeps repository clean and small
Team members run
tessl installafter cloning the repository
Best for:
Repositories with frequent plugin updates
Teams who prefer lighter repositories
Standard development workflows with internet access
Vendored mode
Default for new repositories - plugin contents are committed to your repository
Behavior:
Plugin contents in
.tessl/plugins/are committed to version controlExact plugin versions are checked into the repository
Works offline without registry access
Team members get plugins automatically when cloning
Ensures reproducible builds in all environments
Best for:
Air-gapped or restricted network environments
Repositories requiring complete offline capability
Ensuring exact reproducibility without external dependencies
Compliance requirements for vendoring all dependencies
Switching modes
You can change modes at any time by updating the mode field in tessl.json:
After changing the mode:
Tessl will automatically update
.gitignoreaccordinglyIn managed mode,
.tessl/plugins/will be added to.gitignoreIn vendored mode,
.tessl/plugins/will be removed from.gitignoreCommit the changes to apply the new mode for your team
Default behavior
New projects (running
tessl initin a fresh project): Defaults to vendored modeExisting projects (running
tessl initin a project with existing plugins): Defaults to managed modeYou can explicitly set the mode in
tessl.jsonto override the default
.tessl directory
The .tessl directory contains Tessl's configuration and cached data:
The .tessl/.gitignore file is automatically managed based on your project mode:
Managed mode: The
plugins/directory andRULES.mdare added to.gitignore(not committed to version control)Vendored mode: The
plugins/directory is removed from.gitignore(committed to version control), butRULES.mdremains ignored as it's generated from plugin content
Plugin directory structure
Below we see a plugin structure as it's being authored:
evals/ is only present if scenarios were generated
skills/<your-skill-name>/SKILL.md is suggested structure, otherwise plugin.json will need a specific pointer
Agent rule files
Tessl creates and updates rule files for AI coding agents to help them understand your repository context and installed plugins. The location and format of these files varies by agent:
Cursor:
.cursor/rules/tessl__*.mdc- plugin-specific rules (auto-generated, not committed to git).cursor/rules/tessl_context.mdc- Instructions for gathering context from Tessl MCP
Claude Code:
CLAUDE.md- Context file with instructions for gathering context from Tessl MCP.tessl/RULES.md- Consolidated rules from all installed pluginsAGENTS.md- If this file exists, Tessl adds a reference to.tessl/RULES.md
These files are created when you run tessl init --agent <agent-name> or when Tessl auto-detects an agent in your repository.
MCP configuration files
When configuring AI agents, Tessl adds MCP (Model Context Protocol) server configuration to connect the agent to Tessl's MCP server. The location varies by agent:
Cursor:
.cursor/mcp.jsonClaude Code:
.mcp.jsonin the repository root
These files configure the agent to run tessl mcp start as an MCP server, enabling the agent to access Tessl's tools and context.
This is the agent host configuration Tessl writes into a consuming project. It is distinct from the bundled .mcp.json a plugin author ships inside their package to declare the servers the plugin provides.
AGENTS.md
The AGENTS.md file provides repository context to AI coding agents. This file is similar to CLAUDE.md and other agent context files used by various AI coding assistants.
Note: Tessl does not create or manage AGENTS.md directly. However, if AGENTS.md exists in your repository, Tessl will automatically add a reference to .tessl/RULES.md when you configure an agent with tessl init. This allows your agent to access plugin-specific guidance and context.
If you're using AGENTS.md in your repository:
Add your own repository context, coding conventions, and patterns
Tessl will append a section linking to
.tessl/RULES.md(marked with<!-- tessl-managed -->)The content is used by AI agents during code generation
.tesslignore
Exclude files from plugin validation and packing.
Purpose
The .tesslignore file allows you to exclude files from orphaned file warnings and prevent them from being included in published plugin packages.
Location
Place a .tesslignore file in the root of your plugin directory (same level as .tessl-plugin/plugin.json).
Syntax
Uses gitignore-style patterns:
Default ignored files
These files are always ignored, even without a .tesslignore file:
AGENTS.mdCLAUDE.mdGEMINI.md
Important rules
Links to ignored files cause errors: If your docs link to a file that's in
.tesslignore, validation will fail. This prevents broken links in published plugins.Manifest files can't be ignored: You cannot put
docs,rules, orskillsentrypoints in.tesslignore.
Example .tesslignore
Eval scenario configuration
scenario.json
scenario.json lives next to task.md and criteria.json in a scenario directory. It declares what the platform sets up in the working directory before an agent solves the task — fixtures to install, local files to copy in, and setup scripts to run.
scenario.json is optional. With nothing but task.md and criteria.json, conventional defaults take over (see Conventional defaults).
Fields
description(string, optional): Human-readable summary of the scenario.fixtures(object, optional): Named external content the platform installs into the working directory. Keys are arbitrary names; values are fixture objects (see Fixture types). Order-independent.include(string[], optional): Paths inside the scenario directory copied into the working directory at the same relative path. Each entry must be a directory.setup(string[], optional): Paths to scripts inside the scenario directory, run in order after fixtures and includes are installed.
Fixture types
Two fixture types are supported in scenario.json:
commit — snapshot of a git repo at a specific ref.
repoUrl(string, required): HTTPS clone URL.ref(string, required): Commit SHA or any ref resolvable in the remote.installPath(string, optional, default.): Write destination, confined to the working directory.exclude(string[], optional): Glob patterns of paths to exclude from the snapshot.
directory — content from a local path relative to the scenario.
path(string, required): Read source, resolved relative to the scenario directory but not confined to it; it may resolve to a location outside the scenario directory, including parent traversal.installPath(string, required): Write destination, confined to the working directory.include(string[], optional): Glob patterns of files to keep from the directory. When omitted, the whole directory is installed; when present, only matching files are kept. Applied beforeexclude.exclude(string[], optional): Glob patterns of files to remove from the directory. Applied afterinclude.
Conventional defaults
Without explicit configuration, two on-disk conventions take effect:
If a
resources/directory sits alongsidetask.md, it's automatically copied into the working directory atresources/. Equivalent to"include": ["./resources"].If a
setup.shfile sits alongsidetask.md, it's automatically run after fixtures and includes install. Equivalent to"setup": ["./setup.sh"].
A scenario directory containing only task.md, criteria.json, resources/, and setup.sh therefore needs no scenario.json at all.
Working directory assembly order
Install fixtures (all entries, order-independent).
Copy includes (paths preserved).
Run setup scripts in
setuporder, with auto-detectedsetup.shlast.
Legacy format
A singular fixture object (without the s) at the top level is read as a commit fixture named codebase for backward compatibility with scenarios generated before the fixtures plural form. Prefer the plural form for new scenarios.
User preferences
Tessl stores user preferences globally to customize your experience. You can view and modify these preferences using the tessl config commands.
Available preferences
shareUsageData- Whether to share telemetry and usage data with Tessl (defaults totrue)agents- Which agents to configure ontessl init(default to auto-detect when empty); see supported agents intessl init --help.
Managing preferences
View all current preferences:
View a specific preference:
Set a preference:
Opting out of telemetry
To opt out of sharing telemetry and usage data:
For more information about data collection, see Sharing Usage Data.
Last updated

