How awit works¶
Files are the state; each command is one process; Git is the sync protocol. This page is the conceptual tour. Decisions, flags and the package layout live in the Design Spec; bytes on disk live in the On-disk Schema.
The store¶
One item is one file: .awit/items/<id>.md, YAML frontmatter plus a Markdown
body, filename stem equal to the id key. Comments and --file attachments
live under .awit/comments/<id>/ and are appended to the item's refs.
Refs are repo-root relative with forward slashes.
The loop¶
flowchart LR
A[awit prime<br/>token-light graph] --> B[awit next --claim<br/>lock top unblocked item]
B --> C[awit show id --full<br/>item + resolved refs]
C --> D[awit comment id<br/>research notes]
D --> E[awit close id<br/>unblocks downstream]
E --> A
Each step is one process invocation; state between steps lives only in the
files and in Git. --claim writes status: in_progress, assignee,
claimed_at, then commits awit: claim <id> touching only that file — the
commit turns a double-claim across worktrees into a merge conflict instead
of silent duplication.
The graph¶
flowchart TD
A[Scan .awit/items] --> B[Parse frontmatter]
B -->|parse error, conflict markers, id mismatch| Q[Quarantine]
B --> C[Create nodes]
C --> D[Wire Deps / Unblocks edges]
D -->|dangling dep| Q
D --> E[Tarjan SCC]
E -->|SCC size > 1 or self-loop| Q
E --> F[Condensed DAG]
F --> G[Ready / Blocked classification]
G --> H[Memoized unblock counts]
H --> I[next · prime · critical path]
Every command rebuilds this graph from scratch — O(V+E); hundreds of items
resolve in well under 10 ms. status (open/in_progress/closed) is
stored; ready/blocked eligibility is derived: ready means not closed, no
manual hold, every dep closed. A stored blocked_reason holds an item
regardless of deps.
Ranking: what to do next¶
Each ready item scores its transitive unblock count — unique non-closed
downstream nodes. prime sorts by unblocks desc, ID asc (deterministic,
prompt-cacheable); next ranks the same way but breaks ties randomly so two
agents racing a claim diverge. The critical path is the longest path over
open nodes. Priority is a label, never a field: awit next -l p0 is the
priority check.
When files go wrong: quarantine¶
Cycles, dangling deps, unparseable frontmatter, Git conflict markers, ID
mismatches and duplicate IDs all become quarantine through one path.
Quarantined items are excluded from next, listed under GRAPH WARNINGS in
prime, and reported as FAIL by validate — each with the fix: command
that repairs it. The CLI never panics on a bad file. validate is the
intended pre-commit hook.
Holding work: blocks vs labels¶
A blocked_reason holds; the blocked label does not — create, update
and import print one stderr warning when a hold-less non-closed item newly
receives that exact label. awit block <id> --reason "<obstacle and release
condition>" stores the hold (open + claim cleared, one save); awit
unblock removes only it; release preserves it, close clears it.
External trackers¶
Local state pushes one way to a linked Gitea (tea) or GitLab (glab)
issue: close pushes closed, release pushes open, explicit update
--status pushes the mapped state. import is a one-time snapshot (number,
exact body, labels, open/closed); labels are copied once and never synced.
Local state is canonical: a failed push keeps the local mutation, prints one
stderr retry warning, and still exits 0.
→ Setup & Usage: External trackers
Keeping items/ small: archive¶
awit archive moves finished work out of the hot path: the archive set is
the fixed point of closed, non-quarantined items with no dependant outside
the set (anything else would become a DANGLING DEP fault on departure).
Comments collapse into the archived file; --file attachments move beside
it. There is no unarchive; git revert is the way back.