tix-cli 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.8", "3.11", "3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - run: pip install -e . pytest ruff
20
+ - run: ruff check src tests
21
+ - run: pytest -q
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ .eggs/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ .coverage
12
+ htmlcov/
13
+ .DS_Store
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] — 2026-05-18
9
+
10
+ Initial public release.
11
+
12
+ ### Added
13
+ - Curses TUI over a tree of markdown ticket briefs.
14
+ - Keyboard-driven navigation, filter (`/`), edit (`e`), pickup → `wt` (`p`).
15
+ - Sticky status pins: `i` (active), `d` (done), `x` (cancelled).
16
+ - Move ticket between areas (`m`), copy slug (`y`), open Linear URL (`o`).
17
+ - Split-pane preview with `glow` / `$PAGER` fallback.
18
+ - `tix <project>` form: `chdir` into `./<project>` and set
19
+ `TICKETS_DIR=<project>/.claude/tickets`.
20
+ - `TIX_PRELOAD_HOOK` env var — runs a user-supplied shell command before the
21
+ TUI launches so external tooling can derive `status:`. tix itself is a pure
22
+ reader.
23
+ - Bundled `_TEMPLATE.md`, `_EPIC-TEMPLATE.md`, `_CHILD-TEMPLATE.md` accessible
24
+ via `importlib.resources` at `tix/templates/`.
25
+
26
+ ### Schema
27
+ - Filename is the slug.
28
+ - Epic = folder containing `_epic.md`.
29
+ - Status vocab pinned: `active`, `open`, `draft`, `done`, `cancelled`.
30
+ - Frontmatter parser is line-based (no PyYAML).
31
+
32
+ [0.1.0]: https://github.com/gitpancake/tix/releases/tag/v0.1.0
@@ -0,0 +1,68 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project
4
+
5
+ `tix` is a keyboard-driven curses TUI over a tree of markdown ticket briefs. Stdlib-only Python. Single surface:
6
+
7
+ - `src/tix/tui.py` — curses reader. Renders, filters, navigates, pickups.
8
+ - `src/tix/__main__.py` — CLI router (default → TUI; `tix <project>` → cd-and-set-TICKETS_DIR).
9
+
10
+ **tix is a pure reader.** It does not write `status:` frontmatter. Users who want status auto-derived wire up their own script via `TIX_PRELOAD_HOOK`. That contract is load-bearing — do not bundle a reconciler.
11
+
12
+ ## Invariants
13
+
14
+ 1. **No status writes from tix.** `i`/`d`/`x` sticky pins are the only mutation path, and even those just edit the ticket file's frontmatter directly — they never derive state from external signals.
15
+ 2. **Stdlib-only.** No PyYAML, no `rich`, no `prompt_toolkit`. Adds startup latency we don't accept. Frontmatter parser is intentionally line-based; preserve that contract.
16
+ 3. **Filename = slug.** Never derive a slug from frontmatter `id:` or filename munging. `path.stem` is authoritative.
17
+ 4. **Filesystem = DB.** No `.tix-cache`, no SQLite. `ACTIVE_LANES_FILE` is an *optional read-only* sidecar — tix consumes it if present (a preload hook might populate it) but never writes it itself.
18
+
19
+ ## Status vocab (pinned)
20
+
21
+ `active`, `open`, `draft`, `done`, `cancelled`. Adding one requires changes in two places:
22
+
23
+ - `tui.py` → `STATUS_META`, `FILTER_ORDER`
24
+ - `docs/ticket-schema.md` → contract update
25
+
26
+ Pre-migration title-case variants (`In Progress`, `Todo`, etc.) are kept as read-only aliases — don't extend that set.
27
+
28
+ ## TICKETS_DIR resolution
29
+
30
+ Order: `$TICKETS_DIR` (explicit) → `~/.claude/tickets` (fallback). No project-local autodiscovery. The `tix <project>` form is sugar that does `chdir` + sets `TICKETS_DIR` to `<project>/.claude/tickets` before the TUI imports.
31
+
32
+ ## Preload hook
33
+
34
+ `run_preload_hook()` reads `TIX_PRELOAD_HOOK` and runs it as a shell command before curses takes over. Output is captured. Failures are swallowed. This is the only extension point tix exposes — keep it minimal. Do not add an "after" hook or per-action hooks; users with that level of need should fork.
35
+
36
+ ## Editing rules
37
+
38
+ - `tui.py` runs inside `curses.wrapper`. Anything that prints or raises can wreck the terminal. Wrap subprocess calls in `try/except (OSError, subprocess.SubprocessError)`.
39
+ - Status bar rendering is hot. Don't read filesystem inside the render loop — `App.rebuild_rows()` is the bulk-read step.
40
+ - `subprocess.run` w/ `wt`/`git` returns are mostly best-effort. Keep them that way — surfacing transient failures into the TUI is worse than hiding them.
41
+
42
+ ## Tests
43
+
44
+ Fixture-driven under `tests/fixtures/tickets/`. No network. Run:
45
+
46
+ ```bash
47
+ pytest -q
48
+ ```
49
+
50
+ ## Distribution
51
+
52
+ - PyPI distribution name: `tix-cli`. Import name + executable: `tix`.
53
+ - One console script: `tix`.
54
+ - No native deps. `pipx install tix-cli` is the recommended install.
55
+ - Templates ship in `src/tix/templates/` and are accessed via `importlib.resources` (or `tix.__file__`-relative paths) — never hardcode an install prefix.
56
+
57
+ ## Org-specific bits to keep configurable
58
+
59
+ - `AREAS` in `tui.py` is currently a fixed list. **Don't hardcode org-specific area names** when extending — make it configurable via env or a config file before any new area lands.
60
+ - `LINEAR_WORKSPACE` is the only external-tracker breadcrumb. No GitHub Issues / Jira integration creep — that belongs in a preload hook.
61
+
62
+ ## Non-goals
63
+
64
+ - No remote sync.
65
+ - No mouse support.
66
+ - No realtime collab.
67
+ - No bundled markdown renderer — defer to `glow` / `$PAGER`.
68
+ - No bundled status reconciler — defer to `TIX_PRELOAD_HOOK`.
tix_cli-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Henry Pye
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
tix_cli-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.4
2
+ Name: tix-cli
3
+ Version: 0.1.0
4
+ Summary: Keyboard-driven terminal ticket explorer for a tree of markdown briefs.
5
+ Project-URL: Homepage, https://github.com/gitpancake/tix
6
+ Project-URL: Repository, https://github.com/gitpancake/tix
7
+ Project-URL: Issues, https://github.com/gitpancake/tix/issues
8
+ Project-URL: Changelog, https://github.com/gitpancake/tix/blob/main/CHANGELOG.md
9
+ Author: Henry Pye
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Henry Pye
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: curses,issue-tracker,markdown,tickets,tui
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Environment :: Console :: Curses
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: MacOS
38
+ Classifier: Operating System :: POSIX
39
+ Classifier: Programming Language :: Python :: 3
40
+ Classifier: Programming Language :: Python :: 3 :: Only
41
+ Classifier: Topic :: Software Development
42
+ Classifier: Topic :: Terminals
43
+ Requires-Python: >=3.8
44
+ Description-Content-Type: text/markdown
45
+
46
+ # tix
47
+
48
+ Keyboard-driven terminal ticket explorer for a tree of markdown briefs. Linear-like TUI, zero deps beyond the Python stdlib + an optional markdown pager.
49
+
50
+ ```
51
+ LANES STATE CTX
52
+ ◐ P1 teams-error-mapping active 74K
53
+ ○ P0 oauth-rotation-plan open —
54
+ ● audit-logs-rollout done —
55
+ ```
56
+
57
+ ## Why
58
+
59
+ - **The filesystem is the database.** A ticket *is* a markdown file. `grep`, `git log`, and `ls` all keep working.
60
+ - **No SaaS, no auth, no network.** Runs entirely against a local tree.
61
+ - **Linear-like keys.** `j/k`, `/` to filter, `Enter` to open, `p` to pick up into a `wt` lane.
62
+ - **Pure reader.** tix never writes `status:` for you. If you want auto-status derivation, wire up your own preload hook (see below).
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ pipx install tix-cli
68
+ # or, in a venv:
69
+ pip install tix-cli
70
+ ```
71
+
72
+ Installs one console script: `tix`.
73
+
74
+ ### From source
75
+
76
+ ```bash
77
+ git clone https://github.com/gitpancake/tix
78
+ cd tix
79
+ pipx install --editable .
80
+ ```
81
+
82
+ ## Quickstart
83
+
84
+ ```bash
85
+ mkdir -p ~/.claude/tickets/spikes
86
+ cp $(python -c 'import tix, pathlib; print(pathlib.Path(tix.__file__).parent / "templates" / "_TEMPLATE.md")') \
87
+ ~/.claude/tickets/spikes/my-first-ticket.md
88
+ tix
89
+ ```
90
+
91
+ Or point at a project tree:
92
+
93
+ ```bash
94
+ cd ~/code
95
+ tix my-project # browses ~/code/my-project/.claude/tickets
96
+ TICKETS_DIR=./docs/tickets tix
97
+ ```
98
+
99
+ ## Keys
100
+
101
+ | Key | Action |
102
+ |---|---|
103
+ | `↑` `↓` / `j` `k` | Move |
104
+ | `Ctrl-U` `Ctrl-D` | Half page |
105
+ | `g` `G` | Top / bottom |
106
+ | `Enter` `→` `l` | Open in `glow` (or `$PAGER`) |
107
+ | `Esc` `←` `h` | Collapse / back |
108
+ | `/` | Filter |
109
+ | `e` | Edit in `$EDITOR` |
110
+ | `p` | Pickup → `wt <slug>` |
111
+ | `i` | Pin status `active` |
112
+ | `d` | Pin status `done` |
113
+ | `x` | Pin status `cancelled` |
114
+ | `m` | Move ticket to area |
115
+ | `y` | Copy slug to clipboard |
116
+ | `o` | Open Linear URL (if `linear:` set) |
117
+ | `r` | Reload (re-runs preload hook if set) |
118
+ | `?` | Help |
119
+ | `q` | Quit |
120
+
121
+ ## Schema
122
+
123
+ A ticket is a markdown file with YAML-ish line-based frontmatter:
124
+
125
+ ```markdown
126
+ ---
127
+ status: open
128
+ priority: P1
129
+ area: integrations
130
+ linear: PROJ-123
131
+ ---
132
+
133
+ # teams-error-mapping
134
+
135
+ ## Context
136
+
137
+
138
+ ## Acceptance criteria
139
+ - [ ] …
140
+ ```
141
+
142
+ Full contract: [`docs/ticket-schema.md`](docs/ticket-schema.md).
143
+
144
+ - **Filename is the slug.** `teams-error-mapping.md`, never `PROJ-123.md`.
145
+ - **Epic = folder.** A directory containing `_epic.md` is an epic; numbered children (`01-foo.md`, `02-bar.md`) are its stories.
146
+ - **Status vocab is pinned:** `active`, `open`, `draft`, `done`, `cancelled`.
147
+
148
+ ## Configuration
149
+
150
+ | Env | Default | Purpose |
151
+ |---|---|---|
152
+ | `TICKETS_DIR` | `~/.claude/tickets` | Root of the ticket tree |
153
+ | `ACTIVE_LANES_FILE` | `~/.claude/active-lanes.json` | Optional sidecar map: slug → `{path, branch, repo, last_commit}`. Read by the TUI; tix never writes it. |
154
+ | `LINEAR_WORKSPACE` | *(unset)* | Slug used to derive `linear:` URLs (`o` key) |
155
+ | `TIX_PRELOAD_HOOK` | *(unset)* | Shell command run before launch. See below. |
156
+ | `EDITOR` | `vi` | Used by `e` |
157
+ | `PAGER` | `less` | Fallback when `glow` is absent |
158
+
159
+ `TICKETS_DIR` resolves in this order: explicit env var → `~/.claude/tickets`. There is no project-local autodiscovery; pass `tix <project>` or set `TICKETS_DIR` explicitly.
160
+
161
+ ## Preload hook
162
+
163
+ tix doesn't write `status:` — that's deliberate. If you want statuses derived from external signals (live worktrees, feature branches, merged PRs, calendar events, anything), put a script on disk and point at it:
164
+
165
+ ```bash
166
+ export TIX_PRELOAD_HOOK=~/bin/my-status-sync
167
+ tix
168
+ ```
169
+
170
+ The hook runs once before the TUI is drawn. Its stdout/stderr are discarded — curses is about to claim the screen, so the *next render* is the feedback, not the printed diff. The hook is best-effort: a missing or failing command never blocks launch.
171
+
172
+ The `r` key in the TUI re-runs the hook and reloads.
173
+
174
+ A reference implementation (filesystem + git + `gh`) lives in [gitpancake/.dotfiles](https://github.com/gitpancake/.dotfiles) as `claude/scripts/ticket-status-sync.py` — it derives `active` from live worktrees and `done` from merged PRs. Copy it, fork it, replace it.
175
+
176
+ ## Optional integrations
177
+
178
+ - **`wt`** — if a `wt` command is on PATH, the `p` key suspends curses, runs `git fetch && git checkout main && git merge --ff-only && wt <slug>`, then resumes.
179
+ - **`glow`** — preferred markdown pager for ticket preview. Falls back to `$PAGER` (default `less`).
180
+ - **`gh`** — used by some preload hooks (not by tix itself).
181
+
182
+ ## Non-goals
183
+
184
+ - No remote sync, no auth, no web UI.
185
+ - No mouse support.
186
+ - No notifications.
187
+ - No bundled status reconciler — wire your own via `TIX_PRELOAD_HOOK`.
188
+
189
+ ## License
190
+
191
+ MIT.
@@ -0,0 +1,146 @@
1
+ # tix
2
+
3
+ Keyboard-driven terminal ticket explorer for a tree of markdown briefs. Linear-like TUI, zero deps beyond the Python stdlib + an optional markdown pager.
4
+
5
+ ```
6
+ LANES STATE CTX
7
+ ◐ P1 teams-error-mapping active 74K
8
+ ○ P0 oauth-rotation-plan open —
9
+ ● audit-logs-rollout done —
10
+ ```
11
+
12
+ ## Why
13
+
14
+ - **The filesystem is the database.** A ticket *is* a markdown file. `grep`, `git log`, and `ls` all keep working.
15
+ - **No SaaS, no auth, no network.** Runs entirely against a local tree.
16
+ - **Linear-like keys.** `j/k`, `/` to filter, `Enter` to open, `p` to pick up into a `wt` lane.
17
+ - **Pure reader.** tix never writes `status:` for you. If you want auto-status derivation, wire up your own preload hook (see below).
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pipx install tix-cli
23
+ # or, in a venv:
24
+ pip install tix-cli
25
+ ```
26
+
27
+ Installs one console script: `tix`.
28
+
29
+ ### From source
30
+
31
+ ```bash
32
+ git clone https://github.com/gitpancake/tix
33
+ cd tix
34
+ pipx install --editable .
35
+ ```
36
+
37
+ ## Quickstart
38
+
39
+ ```bash
40
+ mkdir -p ~/.claude/tickets/spikes
41
+ cp $(python -c 'import tix, pathlib; print(pathlib.Path(tix.__file__).parent / "templates" / "_TEMPLATE.md")') \
42
+ ~/.claude/tickets/spikes/my-first-ticket.md
43
+ tix
44
+ ```
45
+
46
+ Or point at a project tree:
47
+
48
+ ```bash
49
+ cd ~/code
50
+ tix my-project # browses ~/code/my-project/.claude/tickets
51
+ TICKETS_DIR=./docs/tickets tix
52
+ ```
53
+
54
+ ## Keys
55
+
56
+ | Key | Action |
57
+ |---|---|
58
+ | `↑` `↓` / `j` `k` | Move |
59
+ | `Ctrl-U` `Ctrl-D` | Half page |
60
+ | `g` `G` | Top / bottom |
61
+ | `Enter` `→` `l` | Open in `glow` (or `$PAGER`) |
62
+ | `Esc` `←` `h` | Collapse / back |
63
+ | `/` | Filter |
64
+ | `e` | Edit in `$EDITOR` |
65
+ | `p` | Pickup → `wt <slug>` |
66
+ | `i` | Pin status `active` |
67
+ | `d` | Pin status `done` |
68
+ | `x` | Pin status `cancelled` |
69
+ | `m` | Move ticket to area |
70
+ | `y` | Copy slug to clipboard |
71
+ | `o` | Open Linear URL (if `linear:` set) |
72
+ | `r` | Reload (re-runs preload hook if set) |
73
+ | `?` | Help |
74
+ | `q` | Quit |
75
+
76
+ ## Schema
77
+
78
+ A ticket is a markdown file with YAML-ish line-based frontmatter:
79
+
80
+ ```markdown
81
+ ---
82
+ status: open
83
+ priority: P1
84
+ area: integrations
85
+ linear: PROJ-123
86
+ ---
87
+
88
+ # teams-error-mapping
89
+
90
+ ## Context
91
+
92
+
93
+ ## Acceptance criteria
94
+ - [ ] …
95
+ ```
96
+
97
+ Full contract: [`docs/ticket-schema.md`](docs/ticket-schema.md).
98
+
99
+ - **Filename is the slug.** `teams-error-mapping.md`, never `PROJ-123.md`.
100
+ - **Epic = folder.** A directory containing `_epic.md` is an epic; numbered children (`01-foo.md`, `02-bar.md`) are its stories.
101
+ - **Status vocab is pinned:** `active`, `open`, `draft`, `done`, `cancelled`.
102
+
103
+ ## Configuration
104
+
105
+ | Env | Default | Purpose |
106
+ |---|---|---|
107
+ | `TICKETS_DIR` | `~/.claude/tickets` | Root of the ticket tree |
108
+ | `ACTIVE_LANES_FILE` | `~/.claude/active-lanes.json` | Optional sidecar map: slug → `{path, branch, repo, last_commit}`. Read by the TUI; tix never writes it. |
109
+ | `LINEAR_WORKSPACE` | *(unset)* | Slug used to derive `linear:` URLs (`o` key) |
110
+ | `TIX_PRELOAD_HOOK` | *(unset)* | Shell command run before launch. See below. |
111
+ | `EDITOR` | `vi` | Used by `e` |
112
+ | `PAGER` | `less` | Fallback when `glow` is absent |
113
+
114
+ `TICKETS_DIR` resolves in this order: explicit env var → `~/.claude/tickets`. There is no project-local autodiscovery; pass `tix <project>` or set `TICKETS_DIR` explicitly.
115
+
116
+ ## Preload hook
117
+
118
+ tix doesn't write `status:` — that's deliberate. If you want statuses derived from external signals (live worktrees, feature branches, merged PRs, calendar events, anything), put a script on disk and point at it:
119
+
120
+ ```bash
121
+ export TIX_PRELOAD_HOOK=~/bin/my-status-sync
122
+ tix
123
+ ```
124
+
125
+ The hook runs once before the TUI is drawn. Its stdout/stderr are discarded — curses is about to claim the screen, so the *next render* is the feedback, not the printed diff. The hook is best-effort: a missing or failing command never blocks launch.
126
+
127
+ The `r` key in the TUI re-runs the hook and reloads.
128
+
129
+ A reference implementation (filesystem + git + `gh`) lives in [gitpancake/.dotfiles](https://github.com/gitpancake/.dotfiles) as `claude/scripts/ticket-status-sync.py` — it derives `active` from live worktrees and `done` from merged PRs. Copy it, fork it, replace it.
130
+
131
+ ## Optional integrations
132
+
133
+ - **`wt`** — if a `wt` command is on PATH, the `p` key suspends curses, runs `git fetch && git checkout main && git merge --ff-only && wt <slug>`, then resumes.
134
+ - **`glow`** — preferred markdown pager for ticket preview. Falls back to `$PAGER` (default `less`).
135
+ - **`gh`** — used by some preload hooks (not by tix itself).
136
+
137
+ ## Non-goals
138
+
139
+ - No remote sync, no auth, no web UI.
140
+ - No mouse support.
141
+ - No notifications.
142
+ - No bundled status reconciler — wire your own via `TIX_PRELOAD_HOOK`.
143
+
144
+ ## License
145
+
146
+ MIT.
@@ -0,0 +1,124 @@
1
+ # Ticket schema
2
+
3
+ The contract between tix and your ticket tree. Read this before hand-editing
4
+ anything under `$TICKETS_DIR` (default `~/.claude/tickets`).
5
+
6
+ ## The one rule
7
+
8
+ **Filename is the slug.** A ticket file's stem *is* its identifier — never an
9
+ external tracker ID, never an arbitrary number.
10
+
11
+ ```
12
+ good: oauth-rotation-plan.md
13
+ bad: PROJ-123.md
14
+ bad: DRAFT-7.md
15
+ ```
16
+
17
+ `tix` and `ls` stay legible without opening any file. External IDs go in the
18
+ optional `linear:` frontmatter field as a historical breadcrumb; nothing ever
19
+ syncs.
20
+
21
+ ## Two kinds of thing
22
+
23
+ | Kind | Lives at | Is |
24
+ |---|---|---|
25
+ | **Single ticket** | `<area>/<slug>.md` | One unit of change with acceptance criteria. |
26
+ | **Epic** | `<area>/<epic-slug>/` | A folder: `_epic.md` plus ordered `NN-<child>.md` children. |
27
+
28
+ A ticket lives in its area from creation — there is no staging folder. "Draft"
29
+ is a *status*, not a location: a freshly-scoped ticket is `status: draft` until
30
+ it's refined or picked up.
31
+
32
+ **Epic-ness is structural.** A directory containing `_epic.md` *is* an epic.
33
+ There is no separate registry — `find $TICKETS_DIR -name _epic.md` is the
34
+ epic index, and it is never stale.
35
+
36
+ ## Areas
37
+
38
+ The root is a small, fixed set of area buckets. This is what keeps the tree
39
+ browsable instead of sprawling into one folder per epic.
40
+
41
+ The default areas baked into `tix` are:
42
+
43
+ - `integrations/`
44
+ - `ops/`
45
+ - `platform/`
46
+ - `spikes/`
47
+ - `tooling/`
48
+
49
+ These names are intentionally generic. Pick whatever set suits your project — a
50
+ future release will make `AREAS` configurable via env or a config file. For now
51
+ edit `src/tix/tui.py`'s `AREAS` list if you fork.
52
+
53
+ Add buckets deliberately. If you reach for a sixth, ask whether it's really an
54
+ area or just an epic that belongs inside an existing one.
55
+
56
+ ## Frontmatter
57
+
58
+ ```yaml
59
+ ---
60
+ status: open # active | open | draft | done | cancelled
61
+ priority: P1 # P0 | P1 | P2 | P3 (blank = unprioritized)
62
+ area: integrations # one of the configured areas
63
+ linear: PROJ-123 # optional external-tracker breadcrumb
64
+ parent: <epic-slug> # only on epic children
65
+ ---
66
+ ```
67
+
68
+ The parser is intentionally line-based — no PyYAML, no nesting. Keep each value
69
+ on a single line.
70
+
71
+ ### Status vocab
72
+
73
+ - `draft` — scoped, not yet refined. The cheapest possible "I might want this."
74
+ - `open` — refined and ready to be picked up. Default for new well-formed tickets.
75
+ - `active` — being worked. A reconciler can derive this from live worktrees or
76
+ branches; you can also pin it manually with `tix`'s `i` key.
77
+ - `done` — shipped. A reconciler can derive it from merged PRs containing the
78
+ slug; you can also pin it with `d`. Sticky: a manual mark survives the next
79
+ reconciliation pass so research/ops tickets without a PR signal can still
80
+ close out.
81
+ - `cancelled` — dropped. **Terminal** — trumps every derived signal until you
82
+ reopen it. Set/cleared with `x`.
83
+
84
+ `tix` itself is a **pure reader** — it never writes `status:` for you. If you
85
+ want statuses derived from external signals, wire your own reconciler via the
86
+ `TIX_PRELOAD_HOOK` env var; it runs once before each TUI launch. A reference
87
+ implementation (filesystem + git + `gh`) lives in
88
+ [gitpancake/.dotfiles](https://github.com/gitpancake/.dotfiles) as
89
+ `claude/scripts/ticket-status-sync.py` — derives `active` from worktrees and
90
+ `done` from merged PRs. Fork, replace, or skip entirely.
91
+
92
+ ### Priority
93
+
94
+ Hand-driven, unlike status. Buckets are `P0` (drop everything) → `P3`
95
+ (eventually); blank sorts last. Within each group `tix` sorts by priority then
96
+ status, so P0/P1 work bubbles to the top. Edit the frontmatter directly or use
97
+ `+`/`−` from the TUI.
98
+
99
+ ## Epic shape
100
+
101
+ ```
102
+ <area>/<epic-slug>/
103
+ _epic.md # the durable, expansive brief
104
+ 01-<child-slug>.md # ordered child stories
105
+ 02-<child-slug>.md
106
+ ```
107
+
108
+ - **`_epic.md`** carries context, goal, epic-level acceptance criteria,
109
+ constraints, and an ordered story list.
110
+ - **`NN-<child>.md` children** are the deep per-story detail. The `NN-` prefix
111
+ encodes execution order so `ls` and `tix` show the sequence.
112
+
113
+ ## Slug conventions
114
+
115
+ Use a descriptive, kebab-cased phrase. Short enough to type, long enough to
116
+ read at a glance. Avoid encoding state in the name — that's what `status:` is
117
+ for.
118
+
119
+ ```
120
+ good: oauth-rotation-plan
121
+ good: webhook-replay-on-failure
122
+ bad: fix-the-thing
123
+ bad: work-in-progress-stuff
124
+ ```
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tix-cli"
7
+ version = "0.1.0"
8
+ description = "Keyboard-driven terminal ticket explorer for a tree of markdown briefs."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "Henry Pye" }]
13
+ keywords = ["tui", "tickets", "curses", "markdown", "issue-tracker"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console :: Curses",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: POSIX",
20
+ "Operating System :: MacOS",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Topic :: Software Development",
24
+ "Topic :: Terminals",
25
+ ]
26
+ dependencies = []
27
+
28
+ [project.scripts]
29
+ tix = "tix.__main__:main"
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/gitpancake/tix"
33
+ Repository = "https://github.com/gitpancake/tix"
34
+ Issues = "https://github.com/gitpancake/tix/issues"
35
+ Changelog = "https://github.com/gitpancake/tix/blob/main/CHANGELOG.md"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/tix"]
39
+
40
+ [tool.ruff]
41
+ line-length = 100
42
+ target-version = "py38"
43
+
44
+ [tool.ruff.lint]
45
+ select = ["E", "F", "W", "I", "B", "UP"]
46
+ ignore = ["E501"]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests"]
@@ -0,0 +1,3 @@
1
+ """tix — keyboard-driven terminal ticket explorer for a tree of markdown briefs."""
2
+
3
+ __version__ = "0.1.0"