tasks
Specification

Configuration

The config.yml schema, the status/role indirection, and the transition rules.

.tasks/config.yml is OPTIONAL. A project without one is configured entirely by the defaults below, and MUST be treated as valid.

.tasks/config.yml
version: 1
active_phase: auth-rework
statuses: [TODO, DOING, REVIEW, SHIPPED]
roles:
  ready: TODO
  claimed: DOING
  done: SHIPPED
transitions:
  - [TODO, DOING]
  - [DOING, REVIEW]
  - [REVIEW, SHIPPED]
  - [SHIPPED, DOING]

Schema

Prop

Type

Unknown top-level keys MUST be rejected, failing the operation as a usage error.

Non-normative

Rejecting rather than ignoring is deliberate: a typo like status: for statuses: would otherwise leave the project silently running on defaults, and the resulting behavior would be baffling. This is the one place the convention chooses strictness over tolerance, because the file is small, closed, and hand-edited.

Invalid YAML, or a document that is not a mapping, MUST fail as a usage error naming the file.

Statuses

statuses is an ordered list of at least two distinct strings. Order is significant: it determines both the role defaults and the default transition rule.

Status names are opaque. An implementation MUST NOT attach behavior to any particular name — not DONE, not BLOCKED, not any other.

Roles

Every behavior binds to a role, never to a literal status name:

RoleMeaningDefault
readyThe status a task must hold to be a candidate for workfirst entry of statuses
claimedThe status a task moves to when claimedsecond entry of statuses
doneThe status that satisfies another task's dependencylast entry of statuses

Each key of roles is independently optional; an absent key takes its default.

Every role value MUST name a member of statuses. A role naming an unknown status MUST fail as a usage error.

Non-normative

This indirection is the reason a project can rename its whole lifecycle to [TODO, DOING, REVIEW, SHIPPED] and have every implementation keep working. It also means a consumer reading a foreign tree must resolve roles from that tree's config before interpreting any status — including when judging a cross-repository dependency.

Transitions

Default rule

When transitions is absent, a transition from status a to status b is legal if and only if:

|index(b) − index(a)| == 1

Positions are taken in statuses. A transition from a status to itself is never legal.

Two properties follow, and both are intended:

  • No forward skipping. A task cannot reach the last status without passing through every intermediate one.
  • The last status is not terminal. Stepping back from it is legal, and is the defined way to reopen completed work.

Explicit rule

When transitions is present it replaces the default rule entirely. It is not additive. Exactly the listed ordered pairs are legal; every other transition, including every backward one, is illegal.

Each element MUST be a two-element sequence of status names, and every name MUST be a member of statuses. A name outside statuses MUST fail as a usage error.

Replacement, not augmentation

An implementation MUST NOT merge explicit transitions with the default step rule. A project that lists only forward pairs has, correctly, made release and reopen impossible.

Coherence

Configuration MUST be validated when it is loaded, not when a command that depends on it happens to run. In addition to the per-field rules above:

  • The transition roles.ready → roles.claimed MUST be legal. A configuration where it is not makes claiming impossible by construction and MUST be rejected outright.

An implementation SHOULD name the offending key and suggest a repair in the error.

config.yml: the transition TODO -> REVIEW (roles.ready -> roles.claimed) is not legal,
so `tasks claim` could never succeed. Add it to `transitions` or adjust `roles`.

Non-normative

Only the ready → claimed edge is mandated because it is the one whose absence makes the project inert rather than merely awkward. A missing claimed → ready edge just means this project does not support releasing a claim, which is a legitimate choice.

active_phase

When set, active_phase is the default scope for queries whose caller did not specify one. Scope resolution order is:

  1. An explicit phase supplied by the caller.
  2. An explicit "all phases" request by the caller, which yields no scope.
  3. active_phase, if set.
  4. Otherwise, no scope — the whole tree.

active_phase MUST NOT restrict anything other than query scope. Direct addressing of a task by id, dependency resolution, and validation MUST always consider the entire tree.

A common confusion

Because active_phase silently narrows queries, a task outside it appears not to exist. An implementation SHOULD make the active scope visible in query output.

Migration

Changing statuses does not rewrite existing task files. Tasks holding a status no longer in the list become validation errors, and an implementation MUST NOT guess a mapping to repair them.

An implementation SHOULD document that the safe migration is to widen statuses to include both old and new names, move tasks across with normal status transitions, then narrow the list again.

On this page