Version: 0.1.0
A minimal, file-based task tracking system designed for use with git repositories. Tasks are stored as individual markdown files with YAML frontmatter, enabling both human editing and programmatic access.
.tasks/
config.yaml # Optional: project-level configuration
.gitattributes # Git merge strategy configuration
open/ # Ready to work on
a1b2c3d4.md
in-progress/ # Currently being worked on
c3d4e5f6.md
blocked/ # Waiting on dependencies
e5f6g7h8.md
closed/ # Completed successfully
f7g8h9i0.md
cancelled/ # Will not be done
h9i0j1k2.md
Status is determined by directory location, not stored in the file. Tasks move between directories when their status changes. This makes find .tasks/open -name '*.md' a trivial way to list active work.
{id}.md
Where {id} is an 8-character Crockford base32 identifier like a1b2c3d4.
IDs are generated from random bytes to ensure uniqueness even with concurrent task creation.
Examples:
a1b2c3d4.mdc3d4e5f6.md
---
yatl_version: 1
title: Fix login bug with special characters
id: a1b2c3d4
created: 2025-11-25T10:30:45Z
updated: 2025-11-25T14:22:00Z
author: brian
priority: high
tags:
- bug
- auth
blocked_by: []
---
Users cannot log in when their password contains special characters like `&` or `<`.
## Reproduction
1. Create account with password `test&pass`
2. Log out
3. Attempt to log in
4. Observe 500 error
## Acceptance Criteria
- [ ] Special characters in passwords work correctly
- [ ] Add test coverage for edge cases
---
# Log: 2025-11-25T11:00:00Z brian
Found the root cause - password is being interpolated into SQL without proper escaping in the legacy auth path.
---
# Log: 2025-11-25T14:22:00Z brian
Fixed in commit abc1234. Need to add tests before closing.| Field | Type | Description |
|---|---|---|
yatl_version |
integer | Task format version (current: 1). Used for migrations. |
title |
string | Human-readable title |
id |
string | Unique 8-character Crockford base32 identifier |
created |
ISO 8601 | Creation timestamp in UTC |
updated |
ISO 8601 | Last modification timestamp |
Note: Status is NOT stored in the file. It is derived from the directory the file is in.
| Field | Type | Default | Description |
|---|---|---|---|
author |
string | - | Creator's identifier |
priority |
enum | medium |
One of: low, medium, high, critical |
tags |
list | [] |
Freeform labels |
blocked_by |
list | [] |
Task IDs that must close before this can proceed |
blocks |
list | [] |
Task IDs that this task blocks (reverse of blocked_by) |
parent |
string | - | Parent task ID (for hierarchies) |
children |
list | [] |
Child task IDs (for hierarchies) |
Status is determined by which directory the task file is in:
┌──────────────────────────────────────┐
│ │
v │
open ──────> in-progress ──────> closed │
│ │ │
│ v │
└────────> blocked ────────────────────┘
│
└──────────────────────────> cancelled
open/: Ready to be worked onin-progress/: Currently being worked onblocked/: Cannot proceed until dependencies resolveclosed/: Completed successfullycancelled/: Will not be done
When you add a blocker to a task (yatl block A B), task A is automatically moved to blocked/.
When a blocking task is closed, all tasks it was blocking are checked - if they have no remaining blockers, they are automatically moved back to open/.
The log section follows the YAML frontmatter and main description. Each log entry begins with a horizontal rule and H1 header:
---
# Log: {ISO-8601-timestamp} {author}
{message content}
Each log entry:
- Starts with
---followed by# Log: {ISO-8601-timestamp} {author} - Contains freeform markdown (can include H2+ headings within the entry)
- Is append-only (never edit previous entries)
The ---\n# Log: pattern is used because it's extremely unlikely to appear in normal markdown content, making parsing robust even when log entries contain markdown headings.
This structure makes concurrent additions merge cleanly with git's union merge strategy.
List of task IDs that must be closed or cancelled before this task can proceed.
blocked_by:
- a1b2c3d4
- c3d4e5f6When you add a blocker using yatl block, the task is automatically moved to the blocked/ directory.
A task is ready when:
- It is in the
open/directory - All tasks in
blocked_byhave been closed or cancelled
Optional .tasks/config.yaml:
# Default author for new tasks (falls back to git config user.name)
default_author: brianCreated automatically by yatl init:
*.md merge=unionThe merge=union strategy concatenates both sides for text conflicts, which works well for the append-only log section. Frontmatter conflicts still need manual resolution, but they're small and obvious.
yatl: short description
yatl(close): fix login bug
yatl(new): add oauth support
yatl(update): reprioritize auth work
Agents can work with tasks by:
- CLI: Use
yatlcommands directly - Reading: Parse YAML frontmatter + markdown body
- Creating: Generate file with proper naming and format
- Updating: Modify frontmatter fields, append to log
- Querying: Use
find,grep, or parse all files
for each file in .tasks/open/*.md:
parse frontmatter
if blocked_by is empty:
yield task
else:
for dep_id in blocked_by:
if task_status(dep_id) not in [closed, cancelled]:
break
else:
yield task
Note: Tasks in blocked/ are not considered ready. The automatic blocking system moves tasks to blocked/ when blockers are added and back to open/ when all blockers are resolved.
| Feature | yatl | Beads | git-bug |
|---|---|---|---|
| Storage | Markdown files | JSONL | Git objects |
| Status | Directory-based | Field-based | Field-based |
| Dependencies | Yes (blocked_by) | Yes (4 types) | No |
| Merge handling | Git default + union | Custom driver | Lamport timestamps |
| Query | File parsing | SQL (SQLite) | Custom index |
| Setup | Install binary | Install binary | Install binary |
| Human editable | Yes | No | No |
| Agent friendly | Yes | Yes | Needs CLI |
Things explicitly not in v0.1 that could be added:
- Templates:
.tasks/templates/bug.md, etc. - Time tracking: Log entries with duration metadata
- Kanban board generation: Static HTML from task data
- SQLite cache: For faster queries on large task sets
- Git hooks: Automatic syncing