Reference
Data Format - How Kensa stores your tests on disk
Updated for v0.16.0Edit on GitHub
Kensa has no database. A project is just a folder, and everything in it is plain text you can read, grep, diff, and commit. This page documents the on-disk format so you know exactly what Kensa writes.
Project layout
my-tests-project/ ├── .tms/ │ ├── schema.yaml # case structure: system + custom field definitions │ ├── config.yaml # project settings (name, id format, next id, UI) │ ├── badges.yaml # optional: colored pill/emoji badge config │ ├── attachments/ │ │ └── <case-id>/... # files attached to a case │ ├── shared-steps/*.md # reusable step sequences │ ├── plans/*.json # test plans (case groupings) │ ├── runs/*.json # manual execution records │ ├── tools/ # tool presets (e.g. mobile/presets.yaml) │ └── trash/ # soft-deleted cases (recoverable) ├── suites/ │ ├── auth/ │ │ ├── 001.md │ │ ├── 002.md │ │ └── checkout/ # nested suite (folder) │ │ └── 003.md │ └── reports/ │ └── 004.md ├── CLAUDE.md # instructions for AI agents ├── .mcp.json # optional MCP server config └── README.md
- Suites are folders. Nesting is unlimited; the folder hierarchy is the suite hierarchy.
- Cases are
.mdfiles named<id>.md(the filename stem always matches the frontmatterid). Kensa enforces and can repair this invariant.
A test case file
A case is Markdown with a YAML frontmatter block:
---
id: 218
title: Login with valid credentials
priority: high
status: active
tags: [auth, smoke, regression]
preconditions: |
User is registered and email is verified.
No active session exists.
custom:
test_type: functional
browser: chrome
estimated_duration: 30
source_id: testrail:C12345
created_at: 2026-05-10T14:30:00Z
updated_at: 2026-05-10T16:45:00Z
---
## Steps
1. Open login page at /login
- Expected: Login form is displayed with email and password fields
2. Enter valid email "user@example.com"
3. Enter valid password
4. Click "Login" button
- Expected: User is redirected to /dashboard
- Expected: User name is displayed in header
## Description
Additional context about the case.
- System fields (
title,priority,status,tags,preconditions,steps, …) live at the top level of the frontmatter. - Custom fields live under
custom:. - Steps are an ordinary numbered Markdown list; expected results are
- Expected:sub-bullets. A reviewer reading the raw file on GitHub sees a perfectly legible test case. source_idrecords where an imported case came from (testrail:C12345,qase:1,csv:row-42) for traceability.
The schema (.tms/schema.yaml)
The schema defines the structure of your cases. As of the v2 format, system
fields are part of the schema too (marked system: true), so the case form,
the validators, and the CLI all introspect the same definitions.
version: 2
fields:
- key: priority
name: Priority
type: select
system: true
options: [low, medium, high, critical]
order: 0
- key: test_type
name: Test Type
type: select
required: false
default: functional
options: [functional, performance, security, accessibility]
order: 10
- key: browser
name: Browser
type: multiselect
options: [chrome, firefox, safari, edge]
order: 11
- key: estimated_duration
name: Estimated Duration (min)
type: number
default: 15
order: 12
Field types: text, textarea, select, multiselect, number, date,
checkbox, url.
Field properties: key (immutable identifier), name, type, required,
default, description, options (for select/multiselect), order, and
system.
The schema is versioned and migrations are non-destructive - see
features/schema-and-fields.md.
Project config (.tms/config.yaml)
version: 1
project:
name: My Tests Project
description: ...
id_format: numeric # numeric | prefixed
id_prefix: null # required when id_format = prefixed
next_id: 219
ui:
default_view: list
terminal_position: right # right | bottom
- ID format is either numeric (
001,002, …) or prefixed (AUTH-001). Thenext_idcounter is allocated atomically and reconciled against the highest id actually on disk, so concurrent git additions don't collide.
Badges (.tms/badges.yaml)
Optional, committed config that maps field values to colored pill or emoji badges so priority/status/custom-value chips render consistently for every contributor and for the CLI. No badges exist until you add the first one.
Trash
Deletes are soft. A removed case moves to .tms/trash/ (flat, with a timestamp
suffix on collision) and can be restored from the UI or the CLI. The only hard
delete is an explicit "empty trash" / kensa trash purge.
Why this format
- Git-native. Cases diff cleanly; PRs review test changes; blame and history are free.
- Tool-agnostic. Any editor, script, or AI agent can read and write cases - Kensa is not required to make sense of the files.
- Byte-stable round-trip. The TypeScript app and the Rust
kensaCLI both parse and re-serialize the format byte-for-byte identically, guaranteed by a shared parity test suite. Editing a case in either tool never produces noisy diffs.