tix — a terminal issue tracker
tix is a kanban-style issue tracker that lives entirely in your repository: issues are plain Markdown + YAML frontmatter files. There is no database, no server, and no proprietary format — you get a terminal UI (TUI), a full CLI, and optional GitLab/Linear sync in one Rust binary.

Heavily AI assisted. This project was designed, specified, and built end-to-end with AI agents under a spec-kitty-SPEC style governed workflow (spec → plan → tasks → implement → review → merge): all 6 work packages, 27 functional requirements, and 33 subtasks, each passing real gate checks (
cargo test,clippy -D warnings,cargo fmt) before merge. The mission trail lives inkitty-specs/issue-tracker-tui-01M27PTN/; the integration surfaces are documented indocs/spec-kitty-integration.md,docs/openspec-integration.mdanddocs/mcp.md.tix init-agentswrites an AI-agent usage section intoAGENTS.md, so other agents can operate this tracker autonomously.
Install
The crate is published to crates.io as tix-tracker (the binary is still
called tix). See docs/packaging.md for the full
maintainer workflow (crates.io account, publishing, binary releases).
# or, with prebuilt binaries (fast, no Rust toolchain needed):
Local development installs (same binary) work too:
Requires a Rust toolchain (edition 2024, MSRV 1.85). The only runtime
dependency is your favorite editor for tix edit.
Quickstart
CLI
| Command | What it does |
|---|---|
tix init |
Create a default tix.toml (and add *.tixbak / *.md.bak to .gitignore) |
tix create |
New issue (title/body/status/priority/assignee/labels/estimate/files) |
tix new |
The same, in the TUI: opens the board with the create-issue form already open |
tix list |
One line per issue — number, id, title — scoped to your issues (the git author identity) in the board's entry column; --all for every active issue, plus --status/--filter, --columns, --sort, --group, --show, --format xml|yml|toml and --with-archived (details) |
tix ready |
The next issue to start: unblocked, in a backlog/unstarted column, highest priority first (then oldest) — so the first row is what to pick up next (details) |
tix view <issue> |
Full issue (frontmatter + body) |
tix status |
Board summary: count per column + total |
tix search <query> |
Substring-search issue titles and bodies (case-insensitive; --with-archived includes archived issues) |
tix lint |
Validate issue frontmatter and report diagnostics |
tix render |
Render the current board once without entering the TUI |
tix clean |
Remove the backup files tix writes beside an issue before it rewrites one — *.tixbak and *.md.bak; confirms each file unless -f/--force |
tix edit <issue> |
Edit fields via flags, or full file via $EDITOR (--files) |
tix move <issue> <column> |
Move between columns; done records a commit hash (auto-captured from HEAD, or --commit auto/<hash>) |
tix link <a> <b> --type <kind> |
Add a typed relationship — blocks, blocked_by, duplicates, or related (required) |
tix link list / tix link edit <id> / tix link remove <id> |
Inspect, retarget/re-kind, and remove relationships by their stable id |
tix unlink <a> <b> --type <kind> |
Remove a relationship of that kind |
tix chain <issue> |
Print the transitive blocks chain an issue holds up |
tix archive <issue> / tix restore <issue> |
Hide an issue from the default views, or bring it back — by dot-prefixing its filename (details) |
tix bulk edit|link|unlink |
Batch over a selection; preview is the default, --apply writes (details) |
tix cycle ... / tix milestone ... |
Create, list, edit, rename, and delete planning items; creating a cycle warns when its dates overlap another |
tix bulk edit|link|unlink|archive |
Batch over a selection; preview is the default, --apply writes (details) |
tix cycle ... / tix milestone ... |
Create, list, edit, rename, and delete planning items |
tix view list|show|create|update|duplicate|delete|apply|status|share |
Save, reuse, and share named filter/sort/group/visibility views (details) |
tix template ... |
Manage stable-ID issue/form templates: list, show, create, update, duplicate, delete (details) |
tix recurring ... |
Recurring series: create, edit, list, show, detect, generate, pause/resume/end, skip/defer/ignore — generation and every lifecycle change need --yes (details) |
tix cycle schedule ... |
Repeating cycle schedules: list, show, create, edit, delete, detect, generate, and the lifecycle verbs close/extend/skip/pause/resume/adjust-future/ignore — every mutation needs --confirm (details) |
tix wip show|set|unset|unit |
Per-column work-in-progress limits: item or estimate ceilings and a warn/block policy, enforced by every move surface (details) |
tix time add|edit|delete|show|history|total |
Per-issue time entries in whole minutes, with an optional YYYY-MM-DD date, an author and an optional note; every correction and deletion appends one attributable History event (details) |
tix workflow categories | show | transitions |
Print the status-category vocabulary, how this board resolves each column, and which transitions are permitted |
tix health set|show|rollup|override|history|board |
Project and issue health: an audited state per issue, derived rollups over parents, cycles and milestones under a versioned policy, and a portable export (details) |
tix initiative create|edit|list|show|delete|associate|dereference / tix roadmap list|show |
Strategic initiatives over the existing registries and a source-attributed roadmap projection (details) |
tix delete <issue> --yes |
Remove an issue file |
tix export --target gitlab|linear |
Push local issues to a remote tracker (details) |
tix export --target beads |
Write local issues into a beads workspace through bd (details) |
tix import --target gitlab|linear |
Pull open issues from a remote tracker |
tix import --target speckit |
Mirror a local GitHub Spec Kit project as labeled issues and subissues; idempotent |
tix import --target beads |
Pull a beads workspace into local files through bd |
tix beads detect|status|list|show|watch |
Read a beads workspace through bd, read-only (details) |
tix beads push|pull|reconcile |
Keep tix and beads in step through bd (refused while the mode is mirror) |
tix init --beads off|mirror|sync |
Record the beads mode; sync installs the .beads/hooks pipe |
tix init-agents |
Write/refresh the AI usage section in AGENTS.md |
tix version |
Print the crate version plus the commit and time this binary was built — how to tell whether cargo run actually rebuilt |
An issue can be referenced by number, id, filename fragment, or title fragment.
tix init-agents detects .kittify/, openspec/ and .specify/ independently
and writes the corresponding tracking blocks into AGENTS.md. When it sees them,
it also prints the matching detection lines to stdout so scripts can tell which
blocks were emitted.
TUI
Launch with tix (no arguments) from the repository root; tix new starts the
same session with the create-issue form already open. A missing tix.toml is
offered a one-key escape — create the defaults and continue — instead of being
refused. The complete, configurable
key reference is docs/key-bindings.md; the list here is
only a default overview.
A session that reaches the TUI opens a "did you know?" tips modal on a random
tip from the list compiled into the binary (tips.toml at the root of this
repository); ←/→ walk it, r shows another random one, Enter/Esc closes it,
and d turns the startup tips off by writing tips_on_startup = false to
tix.toml.
Nine top-level modes share one shell with a vertical navigation tree on the left
(clickable limbs per board, cycle, milestone and template). A mode's function
key switches to it only when the active mode does not bind that key itself, so
F2 inside a form is still the picker.
- Arrows / h/l — move between columns, j/k — select issues
- Enter — view the full issue, n — new issue form
- e — edit the issue in your
$EDITOR - d — delete (with confirmation)
- m — manage cycles and milestones (browse with ↓/↑ or j/k, switch segments with Tab/Shift+Tab, n create, e edit, x delete; q/Esc returns to the board)
- / — filter the live board; a bare number such as
/55opens issue 55, and the active-filter row shows the current query - v — saved views:
Enterapplies,nsaves the live board,erenames,uupdates,dduplicates,xdeletes (details) - q / Esc — quit
| Mode | Key | What it shows | Guide |
|---|---|---|---|
| New Issue | F1 |
the create-issue form, with an explicit Triage/Todo target | new issue |
| Triage | F2 |
the triage inbox | triage |
| Board | F3 |
the kanban board | board keys |
| Cycles | F4 |
cycles grouped current / future / archived, with statistics | cycles |
| Milestones | F5 |
the milestone registry with per-milestone progress | config |
| Repos | F6 |
the child repositories this project coordinates | navigation |
| Stats | F7 |
every scope's health, with its contributors and diagnostics; Tab switches between the Health, Time and Burndown tabs — the Time tab's totals, entry rows and audit, and the Burndown tab's daily remaining/completed series, coverage markers and end-date forecast |
health, time tracking, burndown |
| Schedule | F8 |
recurring series and repeating cycle schedules — Tab switches the Series and Cycle schedules tabs |
recurring, cycle schedules |
| Portfolio | F10 |
the project registry, with its Projects, Epics and Roadmap tabs | projects |
| Library | F11 |
saved views and issue/form templates, listed and inspected | key bindings |
| Config | F12 |
dashboards, cycles, milestones, types, and attributes in one surface | config |
Board highlights: arrows and h/j/k navigate, PgUp/PgDn page a column,
Enter opens an issue, n/F1 creates, Shift+C clones, u toggles the
sub-issue tree, e edits in $EDITOR, V views read-only, d deletes, m
manages cycles and milestones, / searches the live board, ? opens searchable
help, q/Esc quits. In the issue detail view, n/E/d add, re-kind and
remove typed relationships, and Shift+P sets, changes or clears the parent —
all through the same service the CLI and MCP use, so a relationship created in
the TUI is byte-identical to one created with tix link.
The TUI is mouse-aware (click, double-click, drag, right-click, scroll, hover) and never sacrifices keyboard operation. Editor round trips are conflict-safe: a file changed by another writer is never clobbered by a stray keypress — see the edit-conflict and edit-repair modals.
Storage format
Issues are files under docs/issues/… with YAML frontmatter and a Markdown
body. tix re-renders the frontmatter in its own style on write (custom keys are
preserved, ## History verbatim), and the layout, related_issues, and custom
frontmatter keys are all configurable. See
storage format.
Relationships and parents
Four typed relationship kinds (blocks, blocked_by, duplicates, related)
replace the retired links: key; only blocks drives the blocked-move guard. An
issue has at most one parent — a decomposition tree, not an ordering edge. See
relationships and
parent issues.
Projects
A project is a first-class delivery container: stable identity, name,
description, status, an optional lead and dates, and a set of member issues with
progress and health. This is the documented anchor for mission
first-class-projects-01M3BYBW:
- The anchor is a
[projects.<project-id>]registry intix.tomlplus aprojects:list of ids in issue frontmatter. There is no new record format, no database, no hosted project service, no user-identity concept, and no implicit project inheritance (a child does not join its parent's projects). Markdown and configuration remain canonical. - The id is the identity; the name is a display label.
tix project createallocates a stable generated id (the registry key), and every association stores that id — so renaming a project changes the label only and never re-keys anything. A deleted id is never reused. - An issue may belong to several projects. Membership is derived on read, never stored, so repeated reads of the same Markdown are identical.
Associate and dissociate:
Filtering, grouping and views:
tix project show prints the project's progress and the completed/overdue/
blocked/unassigned health counts, computed once by the shared service and read by
every surface (CLI, the F10 Portfolio page, and MCP) so they cannot
disagree.
Missing references are never dropped. Deleting a project removes only the
registry entry; the issues that referenced it keep their ids, tix lint reports
each as a missing_project_reference naming the issue and the id, and the
F10 page and MCP surface them too. Repair by associating the issue with a live
project or removing the dangling id (tix project remove <id> <issue>). Deleting
a project never deletes an issue.
The registry reference — field names, optionality, id-never-reused and the
empty-section omission — is in the
tix.toml reference.
Epics
An epic is a cross-cutting delivery container: stable identity, a display
name, optional description, status, owner, dates and a weight, with a set of
member issues, child epics, progress and blocked work. This is the documented
anchor for mission epics-work-items-01M3BYW4:
- The anchor is a
[epics.<epic-id>]registry intix.tomlplus anepics:list of ids in issue frontmatter. There is no new record format, no database, no hosted planning service, no task entity, and no second relationship mechanism. Markdown and configuration remain canonical. - The id is the identity; the name is a display label.
tix epic createallocates a stable generated id (the registry key, never a name), and every reference stores that id — so renaming an epic changes the label only and never breaks a reference. A deleted id is never reused. - An issue may reference several epics and an epic may have one parent epic. Membership and hierarchy are derived on read, never stored, so repeated reads of the same Markdown are identical.
- Tasks are ordinary issues. A
task-labelled issue that references an epic is a member like any other: there is no task file, entity or frontmatter key (C-001). - Milestone and cycle references are name-keyed, matching how the existing
[milestones]/[cycles]registries already work; epicparentandprojectsare id-keyed. That asymmetry is deliberate.
Associate and dissociate:
Filtering, grouping and views:
tix epic show prints the epic's hierarchy, its completion progress, its
weight-aware figure and its blocked members, computed once by the shared service
and read by every surface (CLI, the F10 Portfolio page's Epics tab, and
MCP) so they cannot disagree by value.
Missing references are never dropped. Deleting an epic removes only the
registry entry; the issues and child epics that referenced it keep their ids,
tix lint reports each as a missing_epic_reference naming the holder and the
id (as it does an epic's own project/milestone/cycle/parent references), and the
F10 page and MCP surface them too. Repair by referencing a live epic or
removing the dangling id (tix epic remove <id> <issue>). Deleting an epic
never deletes an issue or a child epic. A relationship a tracker cannot represent
stays local with a diagnostic naming the same relationship (C-007).
The registry reference — field names, optionality, id-never-reused and the
empty-section omission — is in the
tix.toml reference.
Roadmap
An initiative is a strategic outcome — what the work is for — connected to
the projects, epics and milestones that deliver it. The roadmap is a read-only
projection that joins those existing services; it introduces no portfolio database,
no second metric and no implicit issue ownership. This is the documented anchor
for mission roadmap-initiatives-01M3C8SX:
- The anchor is an
[initiatives.<id>]registry intix.toml. Markdown and configuration remain canonical; the section is omitted when the registry is empty. - The id is the identity; the name is a display label.
tix initiative edit --namerenames the label only and re-keys nothing, so every association survives. A deleted id is never reused. - Associations are explicit: a project by id, an epic by id, a milestone by name. A repeated association collapses to one, and a well-formed reference that names nothing is stored and stays diagnosable — never inferred from an owner.
- One filter, one order: CLI flags, the TUI filter, MCP arguments and the
portable document all parse into the same normalized filter; rows are ordered by
target date ascending, then stable id, and an undated initiative is the explicit
undatedstate that sorts last — never an invented period.
tix roadmap show prints, for each referenced source entity, its <kind>:<id>
provenance, its resolution, the owning service's own progress value and the shared
health policy's risk state and policy version — a join, not a blend. There is no
aggregate percentage, score or average, and the explicit states are complete,
partial, empty, missing_reference and unknown. A genuine zero is a value:
0/0 is complete, never confused with "nobody said" (unknown) or "nothing is
referenced" (empty).
Missing references are never dropped. A dangling association stays on the row
(with [missing]), in the detail pane and in the ordered diagnostics naming the field
and the reference. tix lint reports issue-held project/epic references (and an
epic registry's own references); an initiative's references are config-held and are
diagnosed on the roadmap surfaces rather than by lint. The F10 Portfolio page's
Roadmap tab and the MCP tools surface the same values. See the
roadmap guide for the record, the filter semantics, the summary
provenance and the portable document.
Cycle schedules
A repeating cycle schedule declares in tix.toml that a cycle starts on a
cadence; tix generates each cycle as an ordinary docs/cycles/*.md record with a
stable id (sprint:000001) and a named reference an issue's cycles value can
hold. Nothing runs in the background, and nothing is generated without --confirm.
- The id is the identity; the name is a display label — a rename changes nothing else, and a deleted id is never reused.
- Detection is read-only and every mutation is confirmed (
--confirm). TheF8Schedule page's Cycle schedules tab drives the same service the CLI does. - Closing a cycle captures an immutable snapshot under
docs/cycle_snapshots/, written once and never recomputed, so the evidence survives later schedule edits. - A fixed UTC offset only (
UTCor±HH:MM): a named IANA zone is refused, so a schedule does not follow daylight saving;cron-likecadence is refused by name.
The full reference — every field, the generated cycle, the seven lifecycle actions,
the snapshots and the limits — is in
docs/cycle-schedules.md.
Cycle capacity
Capacity is what a cycle, a team or an assignee may commit to, counted in an
estimate unit the board already declares. tix capacity configures the ceiling and
reports committed, completed, remaining, overflow and utilization from one shared
calculation — the same values the TUI's F8 Capacity tab, the MCP capacity
tools and tix export --target capacity report.
- A scope is a name or its stable id —
sprint:000001and the instance's own name are one scope — and it is explicit and visible. Existing units convert only explicitly (1w=5dunder the board's declared ratio); a missing or incompatible unit is an actionable diagnostic, never a silent zero, and an issue with no estimate stays visible as unestimated. - Overflow is a report, not a mutation: a shared warning naming the scope, the committed value, the ceiling, the excess and the unit. Reading a capacity surface writes nothing.
- Closing a cycle captures immutable capacity evidence under
docs/cycle_snapshots/, written once and never recomputed; a reopen appends one History event and leaves the prior snapshot byte-identical.
The full reference — the configuration shape, the formulas, the warning vocabulary,
the detail views, the close snapshots and the portable/remote boundary — is in
docs/capacity.md.
Time tracking
Time tracking records actual effort against an issue: a whole number of minutes,
an optional YYYY-MM-DD calendar date, an author and an optional note. There is no
timer — no start/stop, no clock read, no timezone conversion and no overlap rule —
and current entries live in the issue's own frontmatter (time_entries), never in a
database or a hosted service.
- Every mutation is one attributable History event, naming the entry and its before/after values; a correction and a deletion each keep the prior value, so the audit trail is never rewritten. A refused mutation (an unconfirmed delete, a missing reason, an unknown entry, a blank author) writes nothing.
- An absent value is
-, never0. A scope that selected no entry (nothing) and a scope that selected entries with no usable duration (unknown) are different facts from a recorded0, and a malformed entry is reported with its kind and its remedy without blocking any other read. - One shared calculation feeds every surface: the CLI, the TUI's
F7Stats page's Time tab and the issue detail pane, the six MCP time tools, andtix export --target time— so no surface can print a number another would not. - Time entries are not estimates: the issue's free-form
estimateis never converted, and no time operation changes an issue's status or ownership.
The full reference — the entry shape, the store representation, the four surfaces,
the never-zero rule and the escalation boundaries — is in
docs/time-tracking.md.
Batch editing
tix bulk edit|link|unlink over a selection: preview is the default, --apply
writes, and each item reports its own outcome with no rollback. See
batch editing.
Archiving
tix archive/tix restore dot-prefix an issue's filename to hide it from the
default views without deleting it; the number is never recycled. See
archiving.
Saved issue views
A saved view names a filter/sort/group/visibility definition so it can be reused
from the CLI, the TUI, and MCP. Views live in tix.toml under
[views.<view-id>] — there is no database and no hosted view service.
- The
v-…id is the identity; the name is a display label, so renaming a view changes nothing else and a deleted id is never reused. - Plans use the existing query grammar only (
--filter,--sort,--group,--hide-columns,--show-mismatch) — no second query language, and unsupported fields are refused before anything is written. - Scope (
repository,project,user) is explicit.projectoutranksrepository; a name shared by two equally specific views is ascope_conflict, never an implicit pick.userscope is share metadata only — not an account and not a permission. - Applying a view is read-only and informational: it marks the active view and reports divergence; it never silently overwrites the live filters and never touches an issue.
tix view delete <id> --confirmremoves only the saved definition — never an issue, comment, or relationship.- Unknown metadata round-trips verbatim, and the same normalized plan/result feeds the CLI, TUI, MCP, and portable output.
The full reference — scope precedence, divergence, deletion safety, portable
format, and the escalation boundaries — is in
docs/saved-views.md.
Workflow status categories
Map board columns onto the global six-word vocabulary (backlog … canceled).
Declaring a category_map opts the board into the transition policy; an unmapped
column falls back to unstarted. See
workflow status categories.
Triage inbox
An opt-in inbox, declared by a [triage] section in tix.toml, holds unreviewed
work before it joins the normal workflow. Every decision is one History event,
and the CLI, the TUI (F2) and MCP read one snapshot through the same service.
See triage and the design reference
docs/triage.md.
Templates
Reusable issue/form definitions live in tix.toml under [issue_templates.<id>],
keyed by an immutable stable ID with a mutable display name, an explicit
repository/board/team scope, normal create defaults, ordered data-only
prompts, and typed references; unknown metadata is preserved across round
trips. tix template list|show|create|update|duplicate|delete manages them, and
tix create --template <id> [--scope …] merges a template with the normal create
flags through a pure resolver before calling the same create core as any other
create — there is no second issue writer, no hosted service, and no executable
template code. Required prompts and missing references fail before any file
is written, so a failed form never creates a partial issue. See
templates.
External sync
tix export/import targets GitLab (through glab) and Linear (through
linear-cli — the canonical
nesszer/linear-cli crate, pinned in mise.toml), plus a local
tix import --target speckit. Repeated runs are idempotent, and a blocking
relation exports to Linear's relations add. tix export --target beads writes
local issues into a beads workspace
through the bd CLI (bd import), never its Dolt database. See
external sync.
Configuration
tix.toml holds the issues directory, [export] targets, keybindings and legacy
tables; boards live in docs/boards/<name>.md. The full key reference is in
configuration, the TUI editing surface in
config mode, and the metadata stores in
navigation.
Documentation
Beyond the guides linked above, docs/for-users/ also holds
navigation (mode shell and metadata stores),
new issue, cycles
and config mode, plus
key bindings and MCP. The stable-ID
template registry is documented in templates, and the
recurring series surface — rules, identity, the three surfaces, idempotency and
its escalation boundaries — in recurring, and the
work-in-progress limits — the TOML shape, the estimate units, the override rules
and the boundaries — in wip, and the repeating cycle schedules —
the TOML shape, the generated cycles, the lifecycle actions and the
snapshot/offset limits — in cycle schedules, and cycle
capacity — the scopes, the explicit conversions, the formulas, the close snapshots
and the portable/remote boundary — in capacity, and per-issue
time tracking — the entry shape, the four surfaces, the never-zero rule and the
boundaries — in time tracking.
Version
Current version: 0.11.0. See CHANGELOG.md.
License
MIT OR Apache-2.0.