tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
{start}
## tix — issue tracker (AI agent usage)

Conventional usage for autonomous issue management.  Run from the repository root.

### Viewing
- `tix ready`                             the unblocked, highest-priority work to start next
- `tix list`                              my issues in the board's entry column, one line each
- `tix list --all`                        every active issue, ignoring the default scope
- `tix list --status "in progress"`       filter by column
- `tix list --columns num,id,title`       choose and order the displayed columns
- `tix list --format toml`                records as xml, yml or toml instead of the table
- `tix status`                            board summary: column -> count
- `tix view|show <number|slug>`          full issue incl. frontmatter + body
- `tix render`                           render the current board once and exit
- `tix init-agents`                       regenerate this AGENTS.md section
- `tix version`                            the crate version, build commit and build time

### Creating
- `tix create --title "Title" --priority high --labels bug --assignee @me`
- `tix create`                            interactive (needs a TTY)
- `tix new`                               open the TUI with the create-issue form already open
- `--assignee @me` resolves to the configured git author identity; bare
  handles like `@alice` are stored as-is. Without `--assignee`, the assignee
  defaults to the git identity, falling back to unassigned when git is absent
  (never an error)

### Updating
- `tix edit <number> --title "New" --priority urgent`
- `tix edit <number> --assignee <value>`  physically moves the issue into the
  new assignee's segment; `--assignee ""` clears it (moves to unassigned/)
- `tix move <number> "in progress"`       move between board columns
- `tix move <number> done --commit <hash>` hand-picked finish (commit recorded)
- `tix move <number> done` (no `--commit`) auto-captures the current `HEAD`;
  `--commit auto` resolves the closing commit from git history instead
- `tix edit <number> --commit auto` back-fills the commit that last touched the issue file
- `tix link <a> <b> --type <kind>`  create a typed relationship;
  `--type` is required and there is no default kind, so an untyped
  relationship is never recorded by accident. Kinds: `blocks`, `blocked_by`
  (an input spelling that stores the equivalent `blocks` edge), `duplicates`,
  `related`
- `tix link list [<sel>] [--kind <kind>]`  list relationships, ordered by
  `(kind, target)`
- `tix link edit <id> [--to <sel>] [--type <kind>]`  retarget and/or re-kind;
  the relationship's id survives
- `tix link remove <id>` / `tix unlink <a> <b> --type <kind>`  remove one
  relationship (idempotent — an absent edge is a no-op)
- `tix chain <sel>`  print the transitive dependency chain an issue holds up
- `tix list --show relationships` / `tix view <sel> --show relationships`
  add relationship and blocked information to list and detail output

### Working an issue (status, decisions, questions)
- **REQUIRED — the status moves as the work moves.** An issue that is being
  worked on is moved to the board's active column while it is in flight
  (`tix move <number> "in progress"`), and to `done` with the closing commit
  when it lands (`tix move <number> done --commit <hash>`). A `todo` issue whose
  work is already finished makes the board lie to every other agent reading it
- **REQUIRED — "in review" is a human-review column.** Moving an issue (or a
  work package) to "in review" hands it to a person to look at — it is not a
  self-review. Before you move it there, run every automatic check yourself
  (linting, formatting, the test suite, a build) and leave the repository in a
  clean, running state, so the human is reviewing finished work, not a red
  build. Move work there only when your own checks are done and you are
  waiting on a human.
- **REQUIRED — when you move work to "in review", write a verification guide
  into the issue's comments.** The guide must let the human assert that the
  work package is complete without asking you: the URL where the feature is
  visible, the account to log in with, any prerequisites to set up first, and
  the exact command that runs the relevant test (for example `cargo test
  <filter>` or the one scenario that exercises the change).
- **REQUIRED — a decision or a question is recorded as a comment on the issue it
  belongs to, as it happens.** When you ask the human a question, when they
  answer it, and when a call is taken while working an issue (a scope decision,
  an approach rejected, a trade-off accepted), append it to that issue's
  `## Comments`: `tix edit <number>` opens the file (the comment section is
  plain markdown) and MCP has `comment_on_issue`. The body states the task; the
  comments state what was decided while doing it, so the next agent reads the
  answer instead of re-asking the question or re-opening a settled call. A
  decision that lives only in a chat log is a decision the board does not have

### Archiving (hiding an issue without deleting it)
- `tix archive <sel>…` / `tix restore <sel>…` rename the issue's file **in place**
  to a dot-prefixed name (`.2026-10-01-foo.md`). The filename *is* the marker:
  nothing else changes, `number`/`status`/`created` and every frontmatter field
  stay byte-identical, one `## History` event records the operation, and a
  redundant call is refused by name (`already_archived` / `not_archived`)
- Default views never read an archived file (`tix list`, `tix search`, the TUI
  board) — that is what keeps a repository with thousands of archived issues cheap
  to open. `--with-archived` is the scope flag that loads them and
  `archived:true|false` narrows the loaded set; in the TUI `a` toggles them and
  their cards read `[archived]`
- An archived issue still resolves (`tix show <sel>`, `tix restore <sel>`) and
  **keeps its number**: `tix create` allocates above every issue, archived ones
  included, so a number is never recycled onto a different issue
- `tix bulk archive` archives each selected issue by delegating to the store's
  archive service — the one archive implementation, so a file is renamed in place
  and nothing is deleted

### Parent issues (a decomposition, not a dependency)
- An issue has **at most one** parent, stored as a canonical `parent:` issue id.
  Numbers and title/filename fragments remain input aliases. This is
  a decomposition tree, **not** a typed dependency edge: use `--type blocks` for
  *must happen before*, and `parent:` for *part of*
- `tix create --title T --parent <sel>`  create a child
- `tix edit <sel> --parent <sel>`  set or change a parent; `--parent ''` clears it
- `tix parent show <sel>` / `tix parent children <sel>` / `tix parent list`
  the hierarchy, one issue's direct children, and the whole board
- A self-parent and a cycle are refused, and a refused write changes nothing.
  Deleting a parent **keeps** its children: the reference survives and `tix lint`
  reports it as `unresolved_parent` so it can be repaired
- `tix search parent:<sel>`  the direct children of `<sel>`

### Workflow status categories
- Board columns stay in `tix.toml`; a board maps its columns into the **global**
  six-word vocabulary with `[kanban.<name>.category_map]`. `Issue.status` still
  holds the **column name**, so every existing board keeps working unchanged
- Categories: `backlog`, `unstarted`, `active`, `blocked`, `completed`, `canceled`
  (`tix workflow categories` prints them, in the order adjacency is evaluated
  against). `completed` and `canceled` are both terminal; only `completed` means
  the work is done, so a `canceled` issue is never counted as complete anywhere
- A column with no mapping resolves to the **safe default** (`unstarted`), and
  `tix lint` reports that as one `unmapped_column_default` warning per board.
  A column the board does not configure at all is `(unmapped)` everywhere
- Transition policy is **opt-in per board**: only a board that declares a
  `category_map` gets the terminal lock, so a legacy board keeps completion
  detection and the commit-clearing rule and gains no new refusals
- Leaving a terminal category needs an explicit reason: `tix move <n> <col>
  --reason <text>` or `tix edit <n> --status <col> --reason <text>`. The reason is
  recorded in the issue's History, and MCP takes the same argument
- A board whose `category_map` omits `completed` is **refused at config load**;
  a board with no `category_map` at all is not
- `tix lint` reports a hand-edited `status:` that contradicts the issue's own
  recorded decisions (`direct_edit_policy_violation`) and **writes nothing** — no
  repair, no appended entry
- `tix workflow show|transitions` report the resolved vocabulary, where each
  category came from, and which transitions are permitted

### Triage inbox
- Opt-in: the inbox exists because `tix.toml` has a `[triage]` section. `tix init`
  writes one (with the `triage`/`wontfix` columns it needs, and `wontfix` mapped to
  `canceled`), so a new repository starts with an inbox; removing the section is
  what turns it off
  (`entry_status`, `declined_status`, optional `accepted_status`, optional
  `deferred_status` = `entry_status`). With no section there is no inbox, and every
  `tix triage` command refuses and names the missing section
- `tix triage list [--age|--count]` and `tix triage status` read the queue. Age comes
  from the entry stamp; an issue that recorded none has an **unknown** age, never `0`
- One decision: `tix triage accept <sel> [--to <col>]`, `tix triage decline <sel>`,
  `tix triage duplicate <sel> --of <sel>`, `tix triage defer <sel>`.
  `--reason` is required for decline, duplicate and defer — a missing one is refused
  (`missing_reason`) before anything is written
- Decline moves the issue to the configured `canceled` column and never deletes or
  archives it; `canceled` is terminal but **not** complete. Duplicate records a typed
  `duplicates` relation and never merges the files. Defer refreshes the entry stamp
  and leaves the issue in the inbox
- A batch previews by default and writes only with `--apply`: `tix triage batch
  --select <list>|--filter <query> --decision <accept|decline|duplicate|defer>
  [--reason <text>] [--to <col>] [--of <sel>] [--apply]`. Each selected issue gets
  exactly one outcome (`applied`/`no-op`/`rejected`/`failed`), and a rejected item
  does not roll back the ones that succeeded
- One applied decision is one `## History` event (`triage_accepted`,
  `triage_declined`, `triage_duplicated`, `triage_deferred`) carrying actor,
  timestamp, prior state, result and reason; a no-op or a rejection appends none, and
  events are never rewritten
- The TUI opens the same inbox with `t` (`a`/`d`/`u`/`f` decide, `p` is the marked
  set); MCP exposes `triage_list`, `triage_status`, `triage_preview` and
  `triage_apply`, the write requiring `confirm: true`
- `tix import --triage-policy entry|remote` decides whether an import enters the
  inbox; absent the flag it routes only when the project has a `[triage]` section, and
  the report always names the mapping used

### Saved issue views (#172)
- A saved view names a filter/sort/group/visibility definition; definitions live
  in `tix.toml` under `[views.<view-id>]` — no database, no hosted view service
- `tix view create --name "Active" --filter "status:todo" --sort due` saves one;
  `tix view list` prints id, name, scope and resolution; `tix view show <id>`
  prints the normalized plan. The `v-…` id is the identity; the name is a label
- `tix view apply <id>` is **read-only**: it marks the active view, rebuilds the
  board through the shared filter path, and prints the plan and the result issue
  numbers; `tix view status <id>` reports divergence without reconciling anything
- `tix view update` keeps the stable id; `tix view duplicate` allocates a new one;
  `tix view delete <id> --confirm` removes **only the definition**, never issues
- Scope is explicit (`repository` | `project` | `user`); `user` is share metadata
  only, not an account or a permission. Plans use the existing query grammar
- The TUI opens the list with `v` (Enter applies); MCP exposes the `view_*` tools
  with the same normalized plan; see `docs/saved-views.md`

### Recurring series (#175)
- A **series** is an issue whose `series:` frontmatter states a rule (the families
  `daily`, `weekly`, `monthly`, `interval`, and a `cron-like` macro vocabulary of
  `@daily`/`@weekly`/`@monthly`); its **occurrences** are issues generated from
  it. Identity is the immutable `series.id`, and a date is the canonical key
  `<series-id>@<YYYY-MM-DD>`: a display name is a label, never a handle, and an
  ambiguous one is refused rather than guessed
- `tix recurring list` / `tix recurring show <sel>` / `tix recurring detect
  [--date YYYY-MM-DD]` read series state and what is due. Detection is
  **read-only**: it creates no issue, appends no History entry and changes no
  series, and the TUI start/refresh warning is the same detector's answer
- `tix recurring generate <sel> --date D --yes` creates the occurrence
  idempotently (a key that is taken is a no-op, never a second issue), and
  `pause`/`resume`/`end` plus `skip`/`defer`/`ignore` are the lifecycle actions.
  **Every mutation needs `--yes`**; MCP takes the same operations with
  `confirm: true`, and an unconfirmed call is refused with `not_confirmed` before
  the store is touched
- One applied action is exactly one append-only `transition_accepted` History
  entry naming the action, the occurrence and the caller's reason; a repeated
  action is a no-op that appends nothing. Generation writes the occurrence, its
  link and the series cursor in one transaction

### Work-in-progress limits (#176)
- A board can cap each column: `[kanban.<board>.wip]` in `tix.toml`, edited with
  `tix wip show|set|unset|unit`. `max_items` counts cards, `max_estimate` counts
  estimate in the `unit` you name, and `policy` is `warn` or `block` — required,
  because a limit that silently warns is a policy nobody configured. A board with no
  `wip` section behaves exactly as it did before limits existed
- **Every** move asks one service: `tix move`, the TUI's `>`/`<` and its context
  menu and drag, the triage inbox, MCP's `move_issue`, and `tix bulk edit`. A move
  within the limit is allowed and appends nothing; `warn` proceeds and reports its
  decision; `block` refuses unless an authorized override carries it; and an estimate
  that cannot be counted in the column's unit refuses the move rather than counting
  as zero
- `tix move <sel> <col> --override --reason WHY --yes` (MCP: `override` +
  `reason` + `yes`) is the only way past a `block`, and all three are required. The
  override binds the limit it was authorized against, so a limit that changed in the
  meantime is `stale_override_limit`; an applied override appends exactly one
  `transition_accepted` entry carrying actor, time, reason and the before/after
  figures
- `tix status`, `tix list --show wip`, the board's column headers and `tix lint`
  report the usage. Lint reports a violation as a **warning** and repairs nothing.
  A bulk batch judges each item against the state that item would see, refuses the
  blocked items without rolling back the applied ones, and exits non-zero when it
  was partial

### Storage layout
- New issues are stored grouped by assignee:
  `docs/issues/<assignee>/YYYY-MM-DD-<slug>.md` (bare handles `@alice` and
  full identities `Name <email>` both work; a missing assignee lands in
  `docs/issues/unassigned/`; legacy flat files at `docs/issues/` still read).

### Closing
- `tix move <number> done`                auto-captures HEAD (or prompts without git)
- `tix delete <number> --yes`             remove an issue

### External sync (if available)
- `tix export --target gitlab`            push local issues to GitLab
- `tix export --target linear`            push local issues to Linear
- `tix import --target gitlab`            pull open issues from GitLab
- `tix import --target linear`            pull open issues from Linear

### Safety
- Root `tix.toml` defines columns; run `tix init` only to create the default.
- `.md` files under `docs/issues/` are the source of truth — a plain editor works too.
- `tix init` keeps `.tix-backups/` (and the legacy `*.tixbak` / `*.md.bak`)
  in `.gitignore` (a `# tix:start` / `# tix:end` block) so the backups `tix`
  writes before rewriting an issue are never committed; `tix clean` prunes
  `.tix-backups/` to the newest 10 per issue and removes any legacy scattered
  backup still on disk (confirming each, or all at once with `-f`/`--force`).
{end}