A simple, Git-friendly task tracker that stores tasks as Markdown files.
Trask can be built and installed using Cargo.
Clone the repository:
git clone <repository-url>
cd traskInstall Trask:
cargo install --path .This installs the trask executable into Cargo's binary directory.
Verify the installation:
trask --helpIf trask is not found after installation, make sure Cargo's binary directory is in your PATH:
export PATH="$HOME/.cargo/bin:$PATH"To build Trask without installing it:
cargo buildThe debug executable will be available at:
target/debug/trask
Run it directly with:
cargo run -- --helpFor an optimized release build:
cargo build --releaseThe release executable will be:
target/release/trask
You can run it directly:
./target/release/trask --helpOr install the optimized release binary:
cargo install --path .Trask includes an optional language server for editor integration.
Install the LSP binary with:
cargo install --path . --bin trask-lspVerify the installation:
which trask-lspThe language server communicates over standard input/output and is intended to be started by an editor.
To use the Trask LSP with Helix, add the following to:
~/.config/helix/languages.toml
[language-server.trask-lsp]
command = "trask-lsp"
[[language]]
name = "markdown"
file-types = [
"md",
{ glob = "TASK.md" },
]
language-servers = ["trask-lsp"]This keeps TASK.md as Markdown while allowing the Trask LSP to provide task-specific completion and diagnostics.
Restart Helix or restart the language server after changing the configuration:
:lsp-restart
Trask's language server can provide task-specific completion and diagnostics for Markdown task files, and go-to-definition for task IDs referenced from source code. Install the LSP binary first (see Trask LSP).
Create ~/.config/nvim/lsp/trask.lua:
return {
cmd = { "trask-lsp" },
filetypes = {
"markdown",
"go",
"rust",
"python",
},
root_dir = function(bufnr, on_dir)
local root = vim.fs.root(bufnr, { ".trask" })
if root then
on_dir(root)
end
end,
}This starts trask-lsp for Markdown, Go, Rust, and Python buffers in a project containing a .trask marker. The marker is used to locate the Trask project root.
Enable the configuration by adding the following line to ~/.config/nvim/init.lua:
vim.lsp.enable("trask")When using LazyVim, create ~/.config/nvim/lua/plugins/trask-gd.lua:
return {
{
"neovim/nvim-lspconfig",
opts = {
servers = {
["*"] = {
keys = {
{
"gd",
function()
Snacks.picker.lsp_definitions()
end,
desc = "Goto Definition (includes Trask tasks)",
has = "definition",
},
},
},
},
},
},
}With trask-lsp attached to the source buffer and advertising go-to-definition support, place the cursor on a task ID in a code comment and press gd to open the corresponding TASK.md. For other symbols, the Snacks picker continues to provide the normal LSP definition results.
For example, a comment containing TODO: 20260919-154500 can link to tasks/20260919-154500/TASK.md.
Restart Neovim after changing the configuration. If the LSP was already running, restart it as well.
Trask is a Rust application and requires a current Rust toolchain with Cargo.
Check that Rust and Cargo are installed:
rustc --version
cargo --versionIf you need to install Rust, use rustup.
This project was inspired by Tsoding's Tatr, an improvised task-tracking system designed to be more powerful than source-code TODOs without requiring a full issue tracker.
The task directory layout and TASK.md concept are directly inspired by Tatr:
tasks/
└── <task-id>/
└── TASK.md
This project is an independent Rust implementation with its own design and implementation.
Thanks to Alexey Kutepov (Tsoding) for the original idea and inspiration.
Tasks live in a tasks/ directory at the root of your project.
Trask uses a .trask marker file to identify the project root. Commands can therefore be run from the project root or from a subdirectory within the project.
Each task gets its own directory named with its unique task ID. Every task directory contains a mandatory TASK.md file.
Task IDs are generated using the UTC timestamp at creation time:
YYYYMMDD-HHMMSS
For example:
project/
├── .trask
├── tasks/
│ ├── 20260919-154500/
│ │ └── TASK.md
│ └── 20260919-160200/
│ └── TASK.md
└── ...
Task directories can also contain attachments such as screenshots or other files.
Run init from the root of your project:
trask initThis creates:
.trask
tasks/
The .trask file identifies the project root. Once initialized, Trask commands can also be run from subdirectories.
Running init on an already initialized project will return an error.
Create a task with:
trask new "Fix ad request timeout"The new command also has the following aliases:
trask n "Fix ad request timeout"
trask add "Fix ad request timeout"
trask a "Fix ad request timeout"Trask automatically generates a unique timestamp-based task ID. If possible the ID created is also copied to the system clipboard automatically.
For example:
tasks/
└── 20260919-154500/
└── TASK.md
The generated TASK.md looks like:
# Fix ad request timeout
- STATUS: OPEN
- PRIORITY: 4294967295
- TAGS:
# Description
The default status is OPEN.
The default priority is u32::MAX (4294967295), which causes newly created tasks to appear first when using the default priority sort.
Edit the TASK.md file directly to add or modify the task description.
Each task contains a TASK.md file with the following format:
# Task title
- STATUS: OPEN
- PRIORITY: 50
- TAGS: rtb, exchange, timeout
# Description
Task description goes here.
The description can contain normal Markdown.STATUS describes whether the task is open or closed.
OPEN
CLOSED
Trask represents these statuses internally using the TaskStatus enum.
A newly created task always starts with:
OPEN
To close a task, edit its TASK.md:
- STATUS: CLOSEDWhen using trask list, closed tasks are excluded by default.
Use --closed or -c to include closed tasks.
PRIORITY is an unsigned integer.
- PRIORITY: 50
The default priority for newly created tasks is 4294967295 (u32::MAX).
When listing tasks, priority is sorted in descending order by default, so higher-priority tasks appear first.
TAGS is a comma-separated list:
- TAGS: rust, helix, tooling
Whitespace around tags is ignored.
An empty tag list is valid:
- TAGS:
The # Description heading marks the beginning of the task description:
# Description
Task description goes here.Everything after the # Description heading is treated as the task description and may contain normal Markdown.
List open tasks:
trask listYou can also use the shorthand:
trask lBy default, closed tasks are excluded and tasks are sorted by priority in descending order.
Example:
20260919-160200 [OPEN] [4294967295] Add Helix integration
20260919-154500 [OPEN] [100] Fix ad request timeout
A closed task such as:
20260919-161030 [CLOSED] [10] Initial project setup
is not shown by default.
Use --closed or -c to include closed tasks:
trask list --closedor:
trask l -cExample:
20260919-160200 [OPEN] [4294967295] Add Helix integration
20260919-154500 [OPEN] [100] Fix ad request timeout
20260919-161030 [CLOSED] [10] Initial project setup
The --closed option does not mean "show only closed tasks." It means include closed tasks in the results.
Use --sort or -s to choose the sort order:
trask list --sort priority
trask list -s priorityThe available sort values are:
| Sort | Long form | Short value |
|---|---|---|
| Priority | priority |
p |
| ID | id |
i |
| Title | title |
t |
Priority is the default sort:
trask list --sort priority
trask list --sort p
trask l -s pHigher priorities appear first.
Sort tasks by their timestamp-based ID:
trask list --sort id
trask list --sort i
trask l -s iExample:
20260919-154500 [OPEN] [100] Fix ad request timeout
20260919-160200 [OPEN] [4294967295] Add Helix integration
20260919-161030 [CLOSED] [10] Initial project setup
Sort tasks alphabetically by title:
trask list --sort title
trask list --sort t
trask l -s tUse --reverse or -r to reverse whichever sort is selected:
trask list --reverse
trask list -rFor example:
trask l -s p -rreverses the default priority ordering.
You can also combine reverse with the other sort options:
trask l -s i -r
trask l -s t -rUse --tag or -t to show only tasks containing a specific tag:
trask list --tag rustor:
trask l -t rustFor example, given:
# Fix ad request timeout
- STATUS: OPEN
- PRIORITY: 100
- TAGS: rtb, exchange
# Description
Investigate why ad requests occasionally exceed the 100ms timeout.# Add Rust parser
- STATUS: OPEN
- PRIORITY: 50
- TAGS: rust, tooling
# Description
Improve the Markdown parser.# Update dashboard
- STATUS: OPEN
- PRIORITY: 25
- TAGS: frontend, dashboard
# Description
Update the dashboard UI.then:
trask l -t rustreturns only:
20260919-160200 [OPEN] [50] Add Rust parser
The --tag / -t option can be specified multiple times:
trask l -t rust -t toolingWhen multiple tags are specified, a task must contain all of the requested tags.
For example:
trask l -t rust -t rtbonly returns tasks containing both rust and rtb.
All list options can be combined.
Include closed tasks and sort by priority:
trask l -c -s pInclude closed tasks and reverse the priority sort:
trask l -c -s p -rShow only Rust tasks and sort by title:
trask l -t rust -s tShow Rust tasks, including closed tasks, sorted by ID:
trask l -c -t rust -s iShow Rust and tooling tasks, including closed tasks, sorted by reverse priority:
trask l -c -t rust -t tooling -s p -rDisplay a task using its ID:
trask show 20260919-154500The show command also has the shorthand alias:
trask s 20260919-154500Example:
ID: 20260919-154500
Title: Fix ad request timeout
Status: OPEN
Priority: 100
Tags: rtb, exchange, timeout
# Description
Investigate why ad requests occasionally exceed the 100ms timeout.
Delete a task using its ID:
trask delete 20260919-154500The delete command also has the following aliases:
trask d 20260919-154500
trask remove 20260919-154500
trask r 20260919-154500The command removes the task associated with the specified task ID.
Tasks are ordinary files, so they work naturally with Git.
git add tasks/
git commit -m "Add ad request timeout task"Task history can be inspected using normal Git tools:
git log -- tasks/20260919-154500/TASK.mdand:
git blame tasks/20260919-154500/TASK.mdAttachments stored alongside a task are also tracked normally by Git.
trask init
trask new <title>
trask n <title>
trask add <title>
trask a <title>
trask show <id>
trask s <id>
trask list [OPTIONS]
trask l [OPTIONS]
trask gist
trask g
trask delete <id>
trask d <id>
trask remove <id>
trask r <id>
Initialize a Trask project:
trask initCreates the .trask marker and tasks/ directory.
Create a new task:
trask new <title>Aliases:
trask n <title>
trask add <title>
trask a <title>A timestamp-based task ID is automatically generated.
Display a task:
trask show <id>
trask s <id>List tasks:
trask list
trask lOptions:
-s, --sort <priority|p|id|i|title|t>
-r, --reverse
-t, --tag <tag>
-c, --closed
Sort aliases:
priority / p
id / i
title / t
Examples:
trask l
trask l -c
trask l -s p
trask l -s i
trask l -s t
trask l -r
trask l -c -r
trask l -t rust
trask l -t rust -t tooling
trask l -t rust -s t
trask l -t rust -s p -r
trask l -c -t rust -s i
trask l -c -t rust -t tooling -s p -rGroup tasks by status and tags:
trask gist
trask gDelete a task:
trask delete <id>Aliases:
trask d <id>
trask remove <id>
trask r <id>Example:
trask d 20260919-154500The tool intentionally uses a small, predictable Markdown format instead of introducing a database or complex serialization format.
The structured fields are:
title
status
priority
tags
description
The task status is represented internally by a TaskStatus enum with two values:
Open
Closed
These are serialized to Markdown as:
OPEN
CLOSED
Everything after the # Description heading is treated as the task description and may contain normal Markdown.
The task ID belongs to the task directory rather than the TASK.md file:
tasks/<task-id>/TASK.md
Task IDs use the UTC timestamp format:
YYYYMMDD-HHMMSS
For example:
tasks/20260919-154500/TASK.md
This also allows additional files to be stored alongside the task:
tasks/
└── 20260919-154500/
├── TASK.md
├── screenshot.png
└── notes.txt
The .trask marker allows Trask to discover the project root when commands are run from a subdirectory:
project/
├── .trask
├── tasks/
└── src/
└── ...