Turn goals into tracked work, kept aligned to one authoritative state file.
Taskrail is a deterministic execution harness for humans and AI agents. It turns goals into structured tasks, keeps every transition aligned to a single authoritative state file, and advances work through validation, verification, and explicit follow-up.
It is built on durable primitives — Git for history and review, plain Markdown with YAML frontmatter for specs, tasks, and state. No database. No hidden automation. No opaque dashboards. Your repo stays inspectable, and the same taskrail commands work whether a person or an agent is at the keyboard.
taskrail init # adopt Taskrail in an existing repo, non-destructively
taskrail validate # confirm the layout and state are consistent
taskrail status # see the active spec, task counts, and what's nextFrom there, the daily loop is next → start → complete → verify (see Commands).
- Why Taskrail
- What It Is Not
- Install
- Commands
- Quickstart
- What a Verification Leaves Behind
- State Contract
- Repository Layout
- Development
- Status
- License
- Read Next
- Deterministic: the workflow is
validate → next → start → complete → verify, and next-task selection follows status, dependencies, priority, and stable tie-breaking — same repo, same answer, every time. - State-first: one authoritative
planning/STATE.mdis the continuity and control surface for all work. - Repo-native: work is tracked as Markdown task files with an explicit, machine-checkable schema — specs under
specs/, tracked work underplanning/. No database, no hidden automation. - Verification is first-class: completing implementation and verifying it are distinct steps; verification records pass/fail outcomes, writes inspectable artifacts, and opens follow-up tasks as needed.
- Retrofit-friendly:
taskrail init(orretrofit) drops the contract into an existing repository with no rewrite. - Agent-ready: every command has a
--jsonpath where it matters, so coding agents drive the same workflow humans do.
- Not a built-in LLM provider integration — Taskrail is provider-agnostic and manual-first. (
importstructures notes; it never calls a model.) - Not a sandbox, container, or worktree orchestrator.
- Not a background daemon, distributed worker pool, or multi-lane scheduler.
- Not a semantic spec-to-task generator or drift detector —
importproduces structural drafts only; LLM-assisted generation is deferred.
Homebrew (macOS and Linux):
brew install tessariq/tap/taskrail
taskrail --versionThis pulls the release binary from the tessariq/homebrew-tap tap.
Windows (WinGet):
winget install Tessariq.Taskrail
taskrail --versionBuild from source (needs Go 1.26):
git clone https://github.com/tessariq/taskrail.git
cd taskrail
go install ./cmd/taskrail
taskrail versionPlain go build/go install produce a development build that reports version
0.0.0-dev. To produce a release build that reports a real version, inject it at
build time:
VERSION=vX.Y.Z
go build -ldflags "-X main.version=${VERSION}" -o taskrail ./cmd/taskrail
# or, via Taskfile:
VERSION="${VERSION}" task release
./taskrail version # -> vX.Y.ZTagged v* releases are built and published automatically with
GoReleaser — Linux/macOS/Windows binaries for
amd64/arm64, with archives, checksums, and notes from CHANGELOG.md. See
docs/workflow/releasing.md for the release checklist.
The core loop is five commands — the ones you run every day:
taskrail validate # check the repo is consistent
taskrail next --json # pick the next eligible active-spec task
taskrail start T-001 # mark it active
taskrail complete T-001 --note "implemented" # mark implementation done
taskrail verify T-001 --result pass --summary "acceptance met"Every command takes --json where it matters, so agents drive the same loop.
Idle next selection is anchored to the active spec: it considers only todo
tasks whose spec_ref points at the active spec, so higher-priority older-spec
work is skipped rather than selected. When only older-spec work is runnable,
next reports no eligible task and lists the skipped tasks under warnings
(skipped_non_active_spec). An already-active task that points outside the
active spec is still returned so you can continue or resolve it, with a
selected_non_active_spec warning. Recover older work explicitly with
start <id>, or run next --include-off-spec for a one-shot pick that ranks
todo tasks across all specs and flags an off-spec selection (off_spec /
selected_off_spec). To move an off-spec task onto the active spec instead of
running it where it is, use
task repoint.
Beyond the core loop
- Adopt an existing repo —
initandretrofitscaffoldspecs/+planning/non-destructively;importturns rough notes into spec/task drafts without an LLM;repairreconciles mechanicalSTATE.mddrift. - See where work stands —
status,stats, andcoveragereport a live snapshot, aggregate metrics, and advisory spec-linkage, all read-only.statusalso breaks down open work (todo/in_progress/blocked) by how much targets the active spec versus points away from it, listing the away tasks and theirspec_ref; the away set matches the active-spec filternextuses for idle selection. - Author and steer specs — the
specfamily (list,show,add,activate,diff) inspects and evolves versioned specs;spec diffpreviews the mechanical area-set delta before activation. - Draft missing work — the optional
taskrail-decomposeandtaskrail-gapskills turn uncovered areas and structural gap signals into reviewable proposals; only an explicittask neworimport --applywrites tracked tasks. - Handle the messy parts —
block/unblockpark and resume work,task newscaffolds a task with the next free id,task renameatomically re-slugs a task's id, filename, and inbound dependency references, andtask repointmoves an open task'sspec_refonto another area.
Run taskrail --help, or taskrail <command> --help, for the full command list and every flag.
coverage and coverage --gaps sit one word apart and answer different questions —
keep them distinct:
coverageanswers "is this spec area linked to any task?" — decomposition coverage, orphan tasks, and two-directional drift.coverage --gapsanswers "does a covered area lack a verification/companion task, have a dependency-graph anomaly, or look under-decomposed?" — it emits structural candidates (missing-verification,dependency-anomaly,under-decomposed-area) over areas that already have tasks.
Both are read-only — they never write STATE.md or task files and never make
validate fail — and advisory by default. --gaps opts into gating only through
--fail-on <category>, which exits non-zero when a signal of that category is
present (mirroring coverage --min); the report itself is unchanged.
The hard limit: --gaps is mechanical only. It reports count, graph, and state
signals — never a semantic "this needs a test" judgement. Its signals are
candidates, not violations: false positives are expected, and each one is
something a human or agent inspects and promotes into a real task, not a rule the
repo broke. For the semantic half — "is this area actually missing work?" — use
the taskrail-gap skill, which layers agent judgement on top of these structural
candidates.
Taskrail ships shell completion via Cobra. Load it for your shell (or add the line to your shell profile):
source <(taskrail completion bash) # bash
taskrail completion zsh > "${fpath[1]}/_taskrail" # zsh
taskrail completion fish | source # fishRun taskrail completion --help for per-shell install steps. Completion is
read-only: it never writes STATE.md or task files. Beyond every command and
flag, it completes spec versions for spec show/spec activate, real
<path>#<anchor> values for task new --spec-ref and task repoint --spec-ref,
and the active spec's bare anchors for their --area flags (the anchors it offers
are exactly the ones validate accepts, so a completed reference authors or
re-points a task that passes validate).
Initialize Taskrail inside an existing repository, then confirm it is sane:
taskrail init
taskrail validateTasks live under planning/tasks/ as Markdown with YAML frontmatter:
---
id: T-001
title: Bootstrap repository structure
status: pending
priority: high
spec_ref: specs/v0.1.0.md#summary
dependencies: []
---
# T-001 Bootstrap repository structure
## Description
Create the initial Taskrail structure, specs, and planning area.
## Acceptance
- `planning/STATE.md` exists.
- `taskrail validate` passes.Let Taskrail pick the next eligible task, start it, and advance it:
taskrail next --json
taskrail start T-001
taskrail complete T-001 --note "implementation landed"
taskrail verify T-001 --result pass --summary "validate passes; acceptance met"When verification reveals more work, spawn a follow-up task in the same step:
taskrail verify T-001 \
--result fail \
--summary "missing dependency check" \
--create-followup \
--followup-title "Add dependency validation" \
--followup-priority highAuthor a task against the active spec without copying the spec path by hand —
--area <anchor> is shorthand for --spec-ref <active-spec-path>#<anchor>:
taskrail task new --title "Add drift breakdown" --area status-active-spec-drift-breakdown
taskrail spec show v0.4.0 --anchors # list the active spec's valid anchors--area and --spec-ref are mutually exclusive; an unknown anchor fails before
anything is written and points you at spec show <active-version> --anchors.
Before advancing the active spec, inspect what changed between two versions with a read-only, mechanical anchor-set delta:
taskrail spec diff v0.3.0 v0.4.0 # added / removed / candidate-rename areasAdded areas are the ones a migration must decompose into tasks; removed areas are
the ones whose existing tasks become orphaned drift; rename candidates are
best-effort only (an added and a removed anchor sharing a normalized stem) and
labeled for you to verify, never asserted as fact. Like coverage and validate
it is side-effect-free: it never writes STATE.md or task files and never gates
validation. --json mirrors the output with structured added/removed/renamed
lists.
A task's id and its filename are two encodings of one identifier: validate
enforces filename == "<id>.md", so a slugged filename requires a slugged id.
task new produces that pairing directly — --title "X" derives a slug and
writes T-<n>-x-slug with a matching T-<n>-x-slug.md, --slug overrides the
slug source, and passing neither keeps the bare T-<n> / T-<n>.md form. Every
case passes validate with no follow-up edit. Accented letters transliterate to
ASCII first, so --title "Über Fußball" yields T-<n>-ueber-fussball and
--title "Łódź Điện" yields T-<n>-lodz-dien rather than a mangled slug — however
your keyboard encoded the accent. Title-derived slugs keep only complete tokens up
to the roughly 50-character cap; if the first token alone exceeds the cap, the
bounded bare-id fallback is used instead of cutting that token. If the value you
pass normalizes to no slug at all (--slug "", --slug "!!!",
a fully non-Latin title), the bare T-<n> id is written and a warning naming the
source goes to stderr — --json on stdout stays clean.
Because the id and filename move together, you cannot rename a file for
readability on its own. A bare git mv T-<n>.md T-<n>-add-slug.md changes only
the filename, leaving the frontmatter id: as T-<n>, so the next validate
fails with task <id> filename must be <id>.md. The fix is task rename, which
re-slugs atomically: it rewrites the id: field, renames the file, repoints the
body's # <id> <title> heading, rewrites every inbound dependencies: reference
to the task, re-projects STATE.md, and re-runs validate.
taskrail task rename T-<n> --slug add-slug # or --title "Add slug"; --dry-run previewsRename is symmetric with creation: an explicitly empty selector, or one that
normalizes to no slug, strips
the slug instead of failing, renaming T-<n>-<slug>.md back to T-<n>.md (with
the same stderr warning), so a bad slug can be undone. The length cap is
symmetric too — a --title-derived slug is capped the same way task new
caps it, while an explicit --slug is normalized but not length-capped.
task rename re-encodes the identifier only: it changes the id/slug and filename
but never rewrites the title: frontmatter field. Re-slugging a task and
retitling its human-readable title are distinct operations, and there is no
task retitle command in this version — so task rename --title "New Title"
derives a new slug and leaves the title unchanged, by design. To change the
visible title, edit the task's title: field directly.
--dry-run writes nothing, and its reported validation previews the state the
rename would leave behind, not the one it would replace — so re-slugging to heal
a filename must be <id>.md drift answers "would this fix it?". The preview covers
the whole change set (id, filename, inbound dependency refs, and the current_task
pointer when it names the task); violations the rename does not touch still show up.
After spec activate, open tasks still pointing at the previous spec are off-spec:
next skips them, status lists them under the active-spec drift breakdown, and
next --include-off-spec recovers one to run where it is. To move an open task
onto the active spec instead, task repoint rewrites its spec_ref — the one
edit that would otherwise mean hand-editing frontmatter.
taskrail task repoint T-<n> --area status-active-spec-drift-breakdown # active-spec anchor
taskrail task repoint T-<n> --spec-ref specs/v0.2.0.md#some-area # explicit, cross-spec
taskrail task repoint T-<n> --area some-area --dry-run # preview, writes nothing--area resolves the anchor against STATE.md's active spec exactly as task new --area does, so an unknown anchor fails before any write and points at spec show <active-version> --anchors. --area and --spec-ref are mutually exclusive.
--dry-run writes nothing, and its reported validation previews the state the
repoint would leave behind, not the one it would replace — so previewing a fix
for a broken spec_ref answers "would this make the repo valid?". Violations the
repoint does not touch still show up.
Repoint re-encodes one reference field: it never touches the id, slug, filename,
title, status, or dependencies, and never rewrites another task file. It is not a
status mutator and not a bulk migrator. Completed and cancelled tasks are delivered
history and are rejected. Because it re-projects planning/STATE.md, run git status afterwards and stage the regenerated file with the change.
Bootstrap drafts from rough notes without any LLM — preview first, then apply:
taskrail import notes.md --to tasks # preview the structural task drafts
taskrail import notes.md --to tasks --emit-prompt # print an agent prompt for a richer draft
taskrail import --apply draft.json # validate an agent draft and write real filesAn apply that fails during writing exits non-zero and still reports what it wrote
or may have touched — the spec and task paths in text mode, the same envelope
marked "partial": true with --json. Review those paths before retrying: a
failed spec write may leave an empty or truncated file, and re-applying the same
draft creates any already-written tasks a second time under new ids.
Typical flow:
- Write a goal as a Markdown task inside
planning/tasks/. validatethe repository.nextto select deterministically, thenstart.completethe implementation.verifyto record the outcome and leave artifacts — opening follow-up tasks as needed.
Every verification writes repo-local evidence under planning/artifacts/verify/<task-id>/<timestamp>/:
planning/
STATE.md # single authoritative state surface
tasks/
T-001.md # task with frontmatter schema
artifacts/
verify/
T-001/
20260619T113646Z/
plan.md # verification plan
report.json # machine-readable outcome
report.md # human-readable outcome
These are plain files — no proprietary formats, no database required. The
planning/artifacts/ tree is gitignored, reproducible local output: verify
creates it on demand, taskrail init never pre-creates it, and neither committed
state nor validate depends on it surviving a Git round-trip. No .gitkeep
placeholder is required or tracked.
planning/STATE.md is the authoritative execution state. It carries the active spec, current task, status summary, blockers, the next action, and the last verification result, plus pointers to relevant artifacts. Do not hand-edit machine-managed state fields — let the taskrail transitions update them.
.
├── AGENTS.md # guidance for coding agents
├── CHANGELOG.md
├── README.md
├── cmd/taskrail/ # CLI entry point
├── internal/ # core packages
├── lefthook.yml # opt-in local git hooks (mirror CI)
├── mise.toml # optional pinned developer toolchain (mise)
├── planning/ # authoritative tracked work and STATE.md
├── scripts/
└── specs/ # versioned, normative product specs
The packaged skill set lives in internal/taskrail/skills/ (embedded; installed
by taskrail init --with-skills). This repository adopts it: committed copies in
.agents/skills/ and .claude/skills/ are kept byte-identical to the package by
task check:skills. Installed skills additionally record the Taskrail version
that wrote them in a taskrail_version frontmatter key, so an install left behind
by an older binary stays detectable: every command warns on stderr when the
recorded version is not the running one, naming the affected skills and both
versions. The warning is advisory — it never fails validate or blocks a
transition — and taskrail init --with-skills --force resolves it. The committed
copies in this repository carry no marker, since parity keeps them byte-identical
to the unstamped package; byte-identical marker-free copies are silent rather than
reported as unknown-version. Do not run --force here, since stamping the
committed copies would break task check:skills.
mise can pin and provision the developer toolchain (Go,
task, lefthook) from the committed mise.toml. It is optional convenience —
direct go commands and the Taskfile.yml targets work without it:
mise install # provision the pinned toolchain on a fresh clone
mise run setup # provision, build taskrail onto PATH, wire the opt-in git hooksmise run setup (and task taskrail:install) build the working-tree
./cmd/taskrail into ./bin and mise puts ./bin on PATH, so a bare taskrail
resolves to the current build with no TASKRAIL override. task taskrail:check
fails loudly if the on-PATH binary is stale versus the working tree. In this
source checkout, run that check immediately before ${TASKRAIL:-taskrail} state
writers; the packaged skills carry the same source-only guard. Adopter
repositories without Taskrail's build tooling are unaffected.
The mise.toml pins are the single source of truth: the go pin matches go.mod
and the lefthook pin matches the hooks guidance below. CI provisions the same
toolchain via jdx/mise-action, so local and
CI builds share one set of pinned versions. The build/test job runs as an OS matrix
over Linux, Windows, and macOS, catching cross-platform regressions (path
separators, line endings, file modes) before merge.
Optional git hooks mirror the CI checks locally via
lefthook. mise run setup wires them;
to install by hand:
go install github.com/evilmartians/lefthook@v1.13.6 # or: brew install lefthook
task hooks:installpre-commit:gofmt,go vet ./...,taskrail validate, skill package-parity check,task taskrail:check(the on-PATHtaskrailmust be the current working-tree build).commit-msg: Conventional Commit subject; rejects automated-attribution trailers.pre-push:go test ./....
Hooks are a convenience; CI (.github/workflows/ci.yml) remains the authoritative
gate. Do not bypass them with --no-verify.
See CONTRIBUTING.md for the PR checklist, the AI-assisted contribution policy, and tracked-work rules.
Taskrail is an in-progress open-source project. The current release is v0.4.0.
v0.1.0established the repository contract: deterministic task progression, the authoritativeSTATE.md, and verification as a first-class concept.v0.2.0makes adoption in existing repositories easy — guidedretrofit, LLM-freeimportof rough notes into spec/task drafts, opt-in shippable agent skills, a version-aware non-destructiveinit, and conservativeSTATE.mdrepair — while keeping the core CLI provider- and tooling-independent.v0.3.0adds read-only insight into tracked work —status,stats, andcoverage— plus thespeccommand family for inspecting and authoring specs,unblockto release blocked tasks, and Windows install via WinGet.v0.4.0adds active-spec task selection and authoring, slugged task creation and atomic rename/repoint operations, mechanical spec/gap review, and version-skew detection across the binary, repository layout, and installed skills.- Later work is tracked under
specs/README.md.
This repository also dogfoods the Taskrail workflow style — using planning/, docs/workflow/, and the packaged skill set it adopts like any adopter — until the product itself fully replaces that scaffolding.
Apache-2.0. See LICENSE.
specs/v0.4.0.md— current release scopespecs/README.md— spec reading order and versioningplanning/STATE.md— live execution stateAGENTS.md— guidance for coding agentsCHANGELOG.md
The versioned specs in specs/ remain the normative source of truth for release scope and behavior.