{"id":51686737,"url":"https://github.com/moderately-ai/ticketsplease","last_synced_at":"2026-07-15T22:01:04.200Z","repository":{"id":367383206,"uuid":"1280479282","full_name":"moderately-ai/ticketsplease","owner":"moderately-ai","description":"git-native parallel-work ticketing — conflict-free parallel work assignment over git-versioned markdown tickets (Rust CLI)","archived":false,"fork":false,"pushed_at":"2026-06-25T19:33:18.000Z","size":172,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-25T20:08:58.961Z","etag":null,"topics":["ai-agents","cli","git","rust","tickets"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moderately-ai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-25T16:14:43.000Z","updated_at":"2026-06-25T19:33:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/moderately-ai/ticketsplease","commit_stats":null,"previous_names":["moderately-ai/ticketsplease"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/moderately-ai/ticketsplease","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moderately-ai%2Fticketsplease","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moderately-ai%2Fticketsplease/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moderately-ai%2Fticketsplease/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moderately-ai%2Fticketsplease/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moderately-ai","download_url":"https://codeload.github.com/moderately-ai/ticketsplease/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moderately-ai%2Fticketsplease/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35522129,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-15T02:00:06.706Z","response_time":131,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","cli","git","rust","tickets"],"created_at":"2026-07-15T22:00:32.487Z","updated_at":"2026-07-15T22:01:04.188Z","avatar_url":"https://github.com/moderately-ai.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ticketsplease\n\n\u003e git-native parallel-work ticketing — weave parallel work threads into one fabric\n\n`ticketsplease` (short alias `tkt`) manages development work as **git-versioned markdown tickets** carrying dependency and affected-area metadata, and computes **conflict-free parallel work assignment** so multiple workers — primarily AI coding agents, humans secondarily — can be dispatched onto disjoint areas of a codebase without merge collisions. No server, no database: GitHub stays git-only.\n\nIt's driven from the command line and built to be scripted: every command speaks JSON, exit codes are the API, and output is deterministic.\n\n## The two commands that matter\n\n```sh\ntkt tracks --format json          # conflict-free parallel batches of ready tickets\ntkt guard \u003cbranch\u003e --format json  # exit 6 iff a branch's actual diff escapes its\n                                  # ticket's declared scope (an overlap with an open\n                                  # ticket is a non-failing WARN by default)\n```\n\n`init` / `create` / `set` / `link` / `show` / `list` / `ready` / `next` / `lint` / `delete` / `rename` are the convenience surface around those two; `list --where` filters with boolean expressions and `view` saves them as named views; `rollup` aggregates an initiative (counts, % done, ready frontier, blocked set) and `graph` / `path` export the dependency DAG (Graphviz DOT, critical path); `tracks --max-overlap` / `lanes` tune how parallel you go (tolerate benign overlap, or sequence conflicts onto per-worker lanes instead of idling); `claim` / `release` / `claims` / `next --claim` provide race-safe pull-based dispatch; `status --all-branches`, `reconcile`, `watch`, `comment`, and `events --watch` give an orchestrator a live view of — and an append-only, conflict-free annotation channel for — workers running on their own branches (`reconcile` flags where the board has drifted from the actual branches/worktrees); and `run` executes named **recipes** — typed, parameterized procedures over these same commands (e.g. `run supersede --arg id=X --arg with=A,B` re-points X's dependents onto A,B and closes X), defined in `[recipe.\u003cname\u003e]` or `.ticketsplease/recipes/` and invoked explicitly (not a `[workflow]` state machine, and no event triggers). New to the model? `tkt guide` prints it in one screen, and `tkt doctor` verifies setup.\n\n## Install\n\n**From source (works today):**\n\n```sh\ncargo install --git https://github.com/moderately-ai/ticketsplease --locked ticketsplease-cli\n```\n\nThis installs the `ticketsplease` binary. Symlink a short alias if you like: `ln -s \"$(command -v ticketsplease)\" ~/.local/bin/tkt`.\n\n**Prebuilt binary via the installer** (once a release is tagged):\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/moderately-ai/ticketsplease/main/install.sh | sh\n```\n\nThe installer detects your platform, downloads the matching static binary, verifies its SHA256, installs to `~/.local/bin`, and symlinks `tkt`. Pin a version with `TICKETSPLEASE_VERSION=v0.1.0`, or change the directory with `BIN_DIR=…`.\n\n## Quickstart\n\n```sh\ntkt init                              # scaffold tickets/ + ticketsplease.toml + the Claude skill\ntkt guide                             # the conceptual model in one screen\n# edit ticketsplease.toml to define your scopes (see below)\ntkt create --title \"Add vector index\" --priority p1 --scope query/planner\ntkt create --id build-index-trait --title \"Build the index trait\" --scope core\ntkt link add-vector-index --depends-on build-index-trait\ntkt ready                             # what's dispatchable now\ntkt list --where 'priority:p0 AND NOT status:done'   # boolean filter (AND/OR/NOT, parens)\ntkt view save epic 'tag:epic AND NOT status:done'    # save a reusable named view, then: tkt list --view epic\ntkt tracks                            # conflict-free parallel batches\ntkt next --parallel 4                 # four disjoint picks for four agents\ntkt guard my-branch                   # gate a branch before merge (exit 6 = conflict)\ntkt status --all-branches             # each worker's tip status across tkt/* branches\ntkt watch add-vector-index --until review --timeout 600  # block until a worker is ready (exit 7 on timeout)\ntkt lint                              # validate schema, links, and cycles\n```\n\n## The model\n\nA **ticket** is a markdown file under `tickets/` with YAML frontmatter:\n\n```yaml\n---\nid: add-vector-index          # slug; equals the filename\ntitle: Add vector index\nstatus: todo                  # built-in: todo|ready|in-progress|blocked|review|done|closed (or your [workflow.states])\npriority: p1                  # p0 (highest) .. p3\ndependencies: [build-index-trait]   # hard, scheduling-blocking; cycle-checked\nrelated: []                   # soft \"see also\"; ignored by scheduling\nscopes: [query/planner]       # exclusive (rewrite) area claims\nshared_scopes: []             # additive (append) claims — co-edit freely\npaths: []                     # extra explicit globs\ntags: []\n# any custom keys you add are preserved verbatim\n---\nFree-form markdown body.\n```\n\nEdits are **round-trip-safe**: ticketsplease rewrites only the field it changes and leaves unknown keys, key order, comments, and the body byte-for-byte. Hand-editing is fully supported.\n\n**Terminal states — `done` vs `closed`.** Both take a ticket out of scheduling, but they mean different things. `done` = completed: it *satisfies* dependents, so they become ready. `closed` = terminated without completing (won't-do, duplicate, obsolete, superseded, cancelled): it does **not** satisfy dependents — instead they surface as **orphaned** (`rollup` lists them, `lint` fails on them, and `claim` refuses with a pointed message) so you re-point, waive, or close them rather than silently building on abandoned work. `tkt close \u003cid\u003e --reason \u003cduplicate|wontdo|obsolete|superseded|cancelled\u003e --note \u003ctext\u003e` records an optional resolution; `tkt reopen \u003cid\u003e` returns it to an active status and clears the reason in the same write. The reason is queryable (`list --where 'reason:duplicate'`).\n\nA **scope** is a stable abstract name for an area of the codebase. Tickets reference scopes; `ticketsplease.toml` maps them to file globs (and, for Rust repos, to crates). Two tickets that share a scope never land in the same parallel batch, and the guard fails a branch that touches a scope its ticket didn't declare.\n\n**Access intent.** A scope can be claimed *exclusively* (`scopes` — a rewrite) or *shared/additively* (`shared_scopes` — append/extend). Two shared claims on a scope are compatible and run in parallel; a shared claim still conflicts with an exclusive one. On top of that, `tracks`/`next`/`lanes` take `--max-overlap K`, a per-pair tolerance budget (`0` strict … `any`), so you fill N workers least-riskily instead of single-threading on benign clashes — `tracks --width` tells you how many fit, and `lanes` plans ordered per-worker queues that *sequence* conflicts instead of dropping them. `[scope_policy]` weights a scope's clash cost (`0` = free hub). The guard honours all of this: a shared-by-both collision is reported but non-gating.\n\n## Configuration — `ticketsplease.toml`\n\n```toml\nschema_version = 1\ntickets_dir = \"tickets\"\ndefault_base = \"main\"          # base ref for `guard`\n\n[language]\nbackend = \"rust\"               # \"none\" (path globs only) or \"rust\" (also use the cargo crate graph)\n\n[scopes]                       # scope name -\u003e path globs\n\"query/planner\" = [\"crates/query/src/planner/**\"]\n\"core\"          = [\"crates/core/**\"]\n\n[scope_crates]                 # scope -\u003e owning crate, so the guard expands reverse-dependents\n\"core\" = \"my-core-crate\"\n\n[external_scopes]              # name a forked dep (pinned via git=…rev=…) as a scope\n\"sqlparser-fork\" = { repo = \"tomsanbear/sqlparser\", paths = [] }\n\n[scope_policy]                 # per-scope clash cost for tracks/next --max-overlap\n\"core\" = { weight = 0 }        # weight 0 = a free-to-co-edit hub; higher = riskier (default 1)\n\n[workflow]                     # custom lifecycle states (optional; omit for the built-in set)\nenforce_transitions = false    # opt-in state machine; default off (any state -\u003e any state)\n[workflow.states.todo]\ncategory = \"dispatchable\"       # dispatchable | open | parked | terminal — the engine contract\n[workflow.states.qa]\ncategory = \"open\"              # occupies its scopes for the guard; excluded from `ready`\n[workflow.states.shipped]\ncategory = \"terminal\"\nsatisfies_dependents = true    # a completed terminal state — unblocks dependents\n[workflow.states.wontfix]\ncategory = \"terminal\"\nsatisfies_dependents = false   # a dropped/cancelled terminal state — orphans dependents\n[workflow.transitions]         # only consulted when enforce_transitions = true\ntodo = [\"qa\"]\nqa   = [\"shipped\", \"wontfix\"]\n\"*\"  = [\"wontfix\"]            # escape hatch: cancel from any state\n```\n\nWhen `backend = \"rust\"`, the guard maps a branch's changed files to crates and walks the cargo **reverse-dependency** graph: a change to a leaf crate is flagged against every crate that depends on it. This needs `cargo` on `PATH` (always true inside a Rust repo). Each collision is tagged `cause: \"direct\"` (a real file/crate overlap) or `\"transitive\"` (reached only via the reverse-dep walk — safe for an additive change), and a per-scope `affected_causes` map lets a consumer triage which under-declarations and collisions are real rather than hand-diffing. Pass `guard --direct-only` (alias `--no-reverse-deps`) to gate on direct overlap only and skip the expansion entirely.\n\n`[external_scopes]` extends the guard beyond this repo: a branch that bumps a pinned `git = … rev = …` dependency (matched by `repo` against the changed manifest lines) — or edits an in-tree fork `paths` glob — is flagged against tickets declaring that external scope. Because external scopes are ordinary scope names, `tracks` already keeps two tickets touching the same fork in separate batches.\n\n**Custom workflow states.** With no `[workflow]` table a repo uses the built-in states (`todo`, `ready`, `in-progress`, `blocked`, `review`, `done`, `closed`). Define `[workflow.states]` to declare your own — each state's **name** is yours to choose, but it must be pinned to one engine **category** the scheduler/guard/rollup reason about: `dispatchable` (pickable), `open` (occupies its scopes for the guard, blocks conflicting parallel work), `parked` (held but not finished, like `blocked`), or `terminal` (finished, excluded from scheduling). A terminal state's `satisfies_dependents` bit *is* the done-vs-closed distinction — `true` unblocks dependents, `false` orphans them. `tkt states` lists the effective registry; `tkt lint` rejects a config with no dispatchable or terminal state; `tkt migrate --remap old=new` moves tickets stranded by a renamed/removed state. Because the engine keys on the category (never the name), renaming a state while keeping its category never breaks scheduling.\n\n**Enforced transitions (opt-in).** Set `enforce_transitions = true` and declare a `[workflow.transitions]` adjacency map (`from = [to, …]`) to make `set`/`close`/`reopen` reject any move that isn't a listed edge — for a QA gate or a no-skip-review rule. It is **off by default** (any-to-any): the engine's real invariants ride on categories, not edges, so the transition graph is a convenience constraint, not a requirement — add it only when you have evidence you need it. A `\"*\"` source is an escape hatch (e.g. close-from-anywhere), `--force` overrides a single move (recorded as `forced` on the event), and `claim`/`release` are engine transitions exempt from the graph. Bulk `set --where` advances the legal matches and reports the rest as skipped rather than aborting. `tkt lint` flags transition edges that name undefined states and non-terminal dead-ends (a state with no way out).\n\n**Maintenance advisories (interactive only).** In an interactive human session the CLI may print a stderr hint after a command — an update is available, the repo has drifted (`run tkt migrate`), or the board has lint findings. They are gated off for every non-interactive caller (`--format json`, no TTY, or `CI` set), so agents and pipelines never see them and stdout stays a clean data channel. `[maintenance]` tunes them: `update_check` (default true; probes the latest release at most once per `check_interval_hours`, default 24) and `auto_migrate` (default false — when true, an interactive session auto-applies the drift repair instead of only nudging; the `--auto-doctor` flag does the same for one command). `TICKETSPLEASE_NO_ADVISORIES=1` disables all of them.\n\n## Tool-managed state — `.ticketsplease/`\n\nSaved views (and bundled body templates) live under `.ticketsplease/` at the repo root. Unlike most tool dot-dirs this is **meant to be committed** — a saved view like \"the open p0/p1 epic\" or a shared ticket-body template is a project artifact, not local state. Don't add it to `.gitignore`.\n\n## The contract\n\n- **`--format json`** on every command yields a stable, versioned payload (`schema_version: 1`), deterministically ordered (sorted keys, no timestamps) — safe to diff and cache.\n- **Exit codes are the API:** `0` ok · `2` usage · `3` invalid/dirty · `4` not found · `5` dependency cycle · `6` conflict · `7` watch timeout.\n- Every command takes `--repo \u003cpath\u003e`; everything is offline and atomic.\n\n## The agent skill\n\n`tkt init` (and `tkt skill install`) wire the bundled skill into your agent harness's skills directory — Claude Code by default (`.claude/skills/ticketsplease/`). It teaches an agent the orchestration loop — `tracks` to fan out disjoint work, `guard` to gate each branch before merge.\n\nThe skill is a single `SKILL.md` + `references/` layout that the major coding agents all consume, so `--harness` selects where it installs: `claude` → `.claude/skills`, `codex` → `.agents/skills` (the cross-tool Agent Skills standard directory, also read by opencode and Pi), `opencode` → `.opencode/skills`, `pi-agent` → `.pi/skills`. `--global` installs into the harness's user-global dir (available in every project) instead of the repo.\n\nThe skill is embedded in the binary, but instead of a frozen per-repo copy it lives once at a canonical per-user path (`~/.local/share/ticketsplease/skill`) and each install is a **symlink** to it. The installer runs `tkt skill sync` after every install/`self-update`, so the canonical copy — and therefore every linked project — always matches your binary; `tkt doctor` warns if it drifts and `tkt migrate` repairs a stale link. A project link is local, so `init` gitignores it; use `tkt skill install --copy` if you'd rather commit a real copy.\n\n## Dogfooding\n\nticketsplease tracks its own development. See `tickets/` and `ticketsplease.toml` in this repo: `tkt tracks` reports which of the remaining work items can proceed in parallel.\n\n## Status\n\nv0.1, pre-release. The core is in place and tested: round-trip frontmatter editing, scheduling (`ready`/`tracks`/`next`), the conflict guard (path-glob + cargo crate-graph), and the bundled skill. Release binaries, self-update, and the migration engine are in progress (tracked as tickets in this repo).\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoderately-ai%2Fticketsplease","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoderately-ai%2Fticketsplease","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoderately-ai%2Fticketsplease/lists"}