Reference

SKILL.md anatomy

Field-by-field reference for a skill file. Minimal example, full example, and what each part does.

A skill is defined entirely by a single Markdown file, SKILL.md, plus an optional examples/ directory. This page is the reference for every field, the body conventions, and how the pieces fit together.


File structure

skill-name/
├── SKILL.md           ← required
└── examples/          ← optional, but recommended
    ├── feat.md
    ├── fix.md
    └── chore.md

The skill directory can live anywhere: inside a plugin (a plugin's skills/ directory, discovered via the skills path in .tessl-plugin/plugin.json), in a standalone skills/ directory during local development, or checked into a service repo. Tessl resolves the path at install time.


Frontmatter

SKILL.md opens with a YAML frontmatter block delimited by ---. Two fields are required; everything else is optional.

---
name: commit-conventions
description: Use when the user asks for a commit message, or has staged changes and wants help summarizing them. Generates a Conventional Commits-formatted message from a diff.
---

name (required)

The skill's identifier within its plugin. Must be unique within the plugin and match the directory name. Use lowercase kebab-case.

name: commit-conventions

description (required)

The most important field. The agent reads description to decide whether to load this skill on any given turn. Write it as two parts:

  1. A trigger condition: what the user is doing or asking.
  2. A capability statement: what the skill does in response.
description: Use when the user asks for a commit message, or has staged changes and wants help summarizing them. Generates a Conventional Commits-formatted message from a diff.

Common mistakes:

Too vague Better
Helps with git Use when the user wants to write a commit message or summarize staged changes
Commit message helper Use when the user asks for a commit message. Generates Conventional Commits-formatted output from a diff.
For writing PRs Use when the user is opening a pull request and wants a description. Summarizes commits between the branch and main.

Overlap between descriptions is the most common cause of a library underperforming. If two skills could plausibly fire on the same prompt, tighten both descriptions until they can't.


Body

The body is the skill's instructions to the agent. Write it as if briefing a competent colleague who already knows the domain, not as a tutorial for the user, and not as documentation. The agent reads this; the user typically never sees it.

There is no enforced structure, but a consistent shape makes skills easier to author, review, and improve.

Recommended body structure

# Skill Name

## When you're triggered

One sentence on the entry condition. Tells the agent when the body's instructions apply.

## What to do

Numbered steps. The agent should follow them in order.

## Supporting reference

Tables, lists, or named rules the instructions refer back to.

## Examples

Point to the `examples/` directory and name what's in it.

What to write

  • Step-by-step numbered lists for actions the agent should take in sequence.
  • Decision tables for classification problems (like the Conventional Commits type table).
  • Constraint lists for hard rules (subject line length, capitalization, trailing punctuation).
  • Explicit references to examples/ when examples exist: "See examples/ for diff → message pairs."

What to leave out

  • Explanations of what the skill does: the description field already says that.
  • Context the agent can read directly (the staged diff, the PR title, the function signature): don't restate it.
  • Hedging language ("you might want to", "in some cases"): be direct; the agent will follow what you write.
  • Padding. SKILL.md files that exceed roughly 400 lines are displacing other useful context. Split the skill before extending it.

The examples/ directory

Optional but recommended for any skill that involves generation or classification.

Each file in examples/ is an input/output pair. The format is not enforced, but a Markdown document with clearly labeled sections works well:

# feat — add CSV exporter

## Diff

```diff
+ export function exportToCSV(rows: Row[]) {
+   return rows.map(r => Object.values(r).join(",")).join("\n");
+ }
```

## Message

```
feat(export): add CSV exporter

Adds a single-call CSV exporter used by the new download button.
```

Three to five examples is usually enough. Aim for variety: cover the most common types (feat, fix, chore) and at least one edge case you've seen the agent get wrong.

Examples don't change behavior automatically: they are referenced from the body via a line like "See examples/ for diff → message pairs." The body reference is what tells the agent to use them.


Minimal example

The smallest valid SKILL.md:

---
name: commit-conventions
description: Use when the user asks for a commit message or wants help summarizing staged changes. Generates a Conventional Commits-formatted message from a staged diff.
---

# Commit Conventions

## What to do

1. Read the staged diff (`git diff --cached`).
2. Identify the dominant change type: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, or `perf`.
3. Write a subject line: `<type>(<optional scope>): <imperative description>`, max 72 characters, lowercase start, no trailing period.
4. Add a body paragraph only if there is a consequence future readers would want flagged: breaking changes, migrations, security fixes.
5. Show the user the message. Do not run `git commit` unless they ask.

This passes lint. It will work. You can ship it.


Full example

The same skill, fleshed out with a type table, subject rules, and an examples reference:

---
name: commit-conventions
description: Use when the user asks for a commit message, or has staged changes and wants help summarizing them. Generates a Conventional Commits-formatted message from a diff.
---

# Commit Conventions

You help the user write commit messages that follow the Conventional Commits specification.

## When you're triggered

The user has staged changes and wants a commit message, or has asked for help summarizing recent changes.

## What to do

1. Read the staged diff (`git diff --cached`) if you haven't already.
2. Identify the dominant change type using the table below.
3. Compose a message of the form `<type>(<optional scope>): <imperative subject>`.
4. Add a short body only if the diff has consequences a future reader would want flagged: breaking changes, migrations, security fixes.
5. Show the user the message before they commit. Don't run `git commit` unless they ask.

## Type table

| Type | When |
|---|---|
| `feat` | New user-visible capability. |
| `fix` | Bug fix the user can perceive. |
| `refactor` | Internal restructure with no behavior change. |
| `docs` | Documentation only. |
| `test` | Tests only. |
| `chore` | Tooling, deps, build, CI: anything below the user's perception. |
| `perf` | Measurable performance improvement. |

## Subject line rules

- Imperative mood ("add", not "added" or "adds").
- Lowercase first letter, no trailing period.
- Hard limit 72 characters.
- Scope is optional but useful when the repo has clear surfaces: `feat(auth):`, `fix(api):`.

## Examples

See `examples/` for diff → message pairs covering `feat`, `fix`, and `chore`.

Validation

Run the linter before installing or publishing:

tessl skill lint ./path/to/skill-name

Lint checks that required frontmatter fields are present, the description is non-trivial, and any files referenced from the body actually exist. Lint passing is a structural guarantee, not a behavioral one. Use tessl review run for structural quality scoring, and evals to test behavior.