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.
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:
| Role | Meaning | Default |
|---|---|---|
ready | The status a task must hold to be a candidate for work | first entry of statuses |
claimed | The status a task moves to when claimed | second entry of statuses |
done | The status that satisfies another task's dependency | last 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)| == 1Positions 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.claimedMUST 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:
- An explicit phase supplied by the caller.
- An explicit "all phases" request by the caller, which yields no scope.
active_phase, if set.- 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.