Ticketwright
Mission
Ticketwright empowers a team to do a high volume of analysis without letting quality slide, on whatever tools they already use.
Vision
Any new or experienced member can pick up any analysis and be productive the same day, because the team's past work is written down and organized, and AI can trace it.
Ticketwright is built for the broad group of people who touch and interact with data — analysts, BI, ops, research, reporting — and it works for any team storing ticket- or task-driven analysis work in a repo, database or not.
This is for you if:
- ✅ your team's work product is an answer, not a feature
- ✅ you ship more analyses than anyone can carefully review by hand
- ✅ you want past work written down, findable, and reusable — by people and by AI
- ❌ you need a project tracker, an ETL scheduler, or a BI dashboard — Ticketwright sits beside those; it does not replace them
What it builds: a team brain
A Ticketwright repo is a shared corpus of tickets that makes your team's past work available to both people and AI. Every analysis lands in one place with its business context, its assumptions, its QC verdict and its deliverables, so any teammate - or any agent - can find prior work, judge whether it applies, and reuse it instead of rebuilding it. You can cite my analysis, I can cite yours, and neither of us has to interrupt the other to do it. Because the corpus is machine-readable, the assistant gets better at your team's domain the longer the team uses it.
What that buys, and the mechanism behind each piece:
- Prior-art recall compounds.
tickets/INDEX.mdis an auto-maintained catalog of every ticket, surfaced at the start of every session, and/ticketopens with a reuse brief - prior work ranked by shared objects, tags and keywords (deterministic, stdlib, no vector store), with what to copy and which gotchas carry over. The engine isbin/recall.py, and what it ranks against istickets/index_data.json- the curated summaries written at ticket close (/shiproutes this through/refresh index;bin/enrich_ticket.pyis the headless-model path,bin/ingest_index_records.pythe agent-neutral one). Every shipped ticket that gets curated makes the next search better. The failure mode, plainly: curation can fail or be skipped, and a skipped ticket falls back to its bare README title (▱in the catalog) - keep skipping and the corpus rots toward a folder of SQL nobody can find. Details: docs/ticket-index.md. - Object-level memory.
tickets/OBJECTS.mdmaps every warehouse object to the tickets that touched it, so "has anyone used this view, and what did they learn about it?" is a lookup. This is the institutional knowledge hardest to keep in people's heads. - Assumptions make prior work citable. The
reduce_assumptionspolicy requires assumptions in the ticket README - a workflow policy the skills honor, not a mechanically enforced one. Without written assumptions an old analysis is unciteable: you cannot tell whether its numbers apply to your question. This is what separates a knowledge base from a file dump. - QC verdicts are a quality signal on the corpus.
/review's APPROVE / REQUEST-CHANGES verdict sits next to the deliverable, so a reader can tell validated work from exploratory work before reusing either. - Old work re-runs. The
deterministic_outputspolicy (explicitORDER BYon exports, golden-replay diffs on productized skills) keeps a prior analysis executable where its queries and inputs still exist - a stronger claim than a document makes. - Continuity. When someone leaves, their reasoning survives with their SQL: the context, assumptions and verdicts stay in the repo, readable by the next person and traceable by the next agent.
The lifecycle is the map
Every ticket moves through the same five phases, whatever tools sit underneath. The tool slots exist to serve the phases, and one slot can serve more than one phase:
| Phase | Tool slots it can use |
|---|---|
| 1 · Open the work | tracker + vcs |
| 2 · Do the work | warehouse + local tools |
| 3 · Quality-check it | no slot of its own - /review plus human sign-off |
| 4 · Deliver | vcs + docstore |
| 5 · Announce and share | tracker + chat |
Phase 4 is the one slot with a machine-local prerequisite: the gdrive and sharepoint
adapters write into a desktop sync mount. docs/drive-mount.md covers
installing that mount per OS, which half of the path is a team decision and which is yours - or
how to skip the mount entirely with the rclone adapter, which needs only the binary.
"More tools" means named targets inside a slot - two warehouses, a team chat and a client chat -
never more slots. And phase 3 is worth a second look: quality checking has no tool slot of its
own. Every other phase has a dedicated external system available to it (available, not always
present - trackerless, warehouse-less and docstore-less setups are all supported), but there is
no QC service to plug in. /review and the qc-reviewer agent borrow the warehouse slot to
re-run the deliverable queries, and under the default human_review_handoff policy the final
gate is a person reading the output - the deliverables open in each reviewer's own applications,
a per-user choice whose portable half lives in committed people/<id>.yaml and whose machine
wiring stays local.
The commands that drive these phases are in How work flows below.
It works with your tools, through one config file:
| Tool slot | Works with |
|---|---|
| Tracker | Jira · Azure DevOps · Linear · Asana · Monday · GitHub Issues · etc. — or none at all |
| Warehouse | Snowflake · BigQuery · Databricks · Postgres · Redshift · Synapse · Supabase · DuckDB · etc. — or none at all |
| Chat | Slack · Teams · email (Gmail · Outlook) · etc. |
| Docs | Google Drive · SharePoint · Dropbox · S3 · Box · etc. — mounted, or mountless via rclone |
| Git | GitHub · GitLab · Azure Repos · Bitbucket · etc. |
- The lists are examples, not a whitelist — any tool that fills a slot works. The first six
trackers, six warehouses, and the Slack/Teams/Gmail/Outlook/Drive/SharePoint/GitHub/GitLab/Azure-Repos
set ship as adapters today; wiring up another (Supabase, DuckDB, Bitbucket, …) is
a single adapter file — the skills never change. For document stores the
shipped
rcloneadapter already covers Dropbox, S3 and Box without a desktop sync mount. - More than one warehouse is fine — name the targets.
- No warehouse is fine too — a team whose deliverables are documents, models, or reports just omits the tool slot (worked example).
- No ticketing system is fine too — set
id_mode: slugand a folder you name becomes the ticket.
Getting started
Two tracks — pick yours and follow it end to end. Track 1 is for the first person bringing Ticketwright into a repo; Track 2 is for everyone who clones that repo afterwards. Every step carries its own one-line check, so a stall is diagnosable instead of mysterious.
Track 1 — Setting up a repo
1 · Install the plugin at project scope, from inside the repo you want to work tickets in:
claude plugin marketplace add https://github.com/kyle-chalmers/ticketwright.git --scope project
claude plugin install ticketwright@ticketwright --scope project
Check: claude plugin list shows ticketwright@ticketwright … enabled.
That writes the repo's own .claude/settings.json. Both commands default to --scope user, so
omit --scope project only if you want Ticketwright for yourself across every repo rather
than for this repo's team.
2 · Fully restart Claude Code — quit and relaunch, not just a new prompt. Plugin skills load
at session start, so installing and running /setup in the same session silently fails: the
command simply doesn't exist yet.
Check: /ticketwright:setup shows up in the new session's command list.
3 · Run setup:
/ticketwright:setup # detects your tools, interviews you in rounds, writes the config — once per repo
/ticketwright:ticket ENG-123 # start working
setup also handles repos that already have ticket history — it maps onto your existing
layout instead of scaffolding, and writes a MIGRATION.md checklist (see
Adopting an existing repo).
Check: .claude/config/stack.yaml exists, and the tool-slot verifier names each slot — ask your
agent to "run verify_stack" (on a plugin install the script lives in the plugin, and the skills
resolve it; in a vendored or pip repo you can run bash bin/verify_stack.sh directly).
4 · Turn on release pick-up. Add one key by hand to the "ticketwright" marketplace entry in
.claude/settings.json — no CLI flag sets this one — so teammates pick up tagged releases:
"autoUpdate": true
/ticketwright:setup adds that key for you if you'd rather not hand-edit; see
Project-scoped by default for the finished file.
One honest caveat while the gap reported in
claude-code#61854 persists (verified live
2026-08-23): autoUpdate refreshes the marketplace CATALOG on session start, but Claude Code does not
yet re-install a project-scoped plugin from it - so a new release reaches every teammate's machine
without being swapped in. Sessions announce it rather than leaving it silent: when the catalog is
ahead of what this repo is running, the session-start banner ends with one line naming both versions
and the command pair below. Until the gap lands upstream, picking up a release is that one command
pair, run from the repo:
claude plugin uninstall ticketwright@ticketwright --scope project && claude plugin install ticketwright@ticketwright --scope project
(It may reorder keys in .claude/settings.json; the content is identical - git checkout the file
if you want zero diff.)
5 · Commit the scaffold (/setup offers to), and Ticketwright travels with the repo.
Check: the committed files include .claude/settings.json, .claude/config/stack.yaml, and
AGENTS.md.
What teammates will then see: opening (and trusting) the repo registers the marketplace from
the committed .claude/settings.json and primes the session banner — and then they follow
Track 2, because registration is not installation (the fact Track 2 opens with).
Track 2 — Joining a configured repo
Someone already ran Track 1 and committed the result; you just cloned. One fact up front, because
it is the step people lose an afternoon to: the repo's committed .claude/settings.json
(enabledPlugins + extraKnownMarketplaces) registers and clones the marketplace on session
start, but does NOT install the plugin. Verified live: a teammate's installed_plugins.json
stayed {} across restarts until the manual install in step 2. Skip step 2 and
/ticketwright:setup is not a command that exists.
1 · Clone the repo and open it in Claude Code (trust the workspace when prompted).
Check: .claude/config/stack.yaml exists — that's the team config Track 1 committed.
2 · Install the plugin explicitly:
claude plugin install ticketwright@ticketwright
Check: claude plugin list shows ticketwright@ticketwright … enabled.
3 · Fully restart Claude Code — quit and relaunch, not just a new prompt. Plugin skills load
at session start; installing and running /setup in the same session silently fails.
Check: /ticketwright:setup shows up in the new session's command list.
4 · Onboard yourself:
/ticketwright:setup --teammate
It walks you through your people/<id>.yaml, your machine-local
.claude/config/connections.local.yaml, and auth for each tool the team's config actually uses.
Check: ask your agent to "run verify_stack". Slots with a shell verify should report reachable
(an unreachable one prints its auth fix; finishing onboarding first and authing later is fine).
MCP-only slots — a chat tool connected through a desktop connector, say — cannot be checked from
the shell and show as unverified; /ticketwright:setup --teammate probes those in-session instead.
What you need installed (derived from the stack, not a fixed list)
The CLIs a teammate needs depend on which tool slots the team's stack.yaml fills — there is no
universal list. snow matters only if the warehouse is Snowflake, gh only if vcs is GitHub, and
so on. The verifier run above names anything missing, and each tool's install and auth notes
live in its adapter (adapters/<seam>/<tool>.md). On macOS the common ones are a Homebrew line
each:
brew install yq jq # every stack: the kit's own tooling
brew install gh # only if vcs is GitHub
brew install glab # only if vcs is GitLab
brew install snowflake-cli # only if the warehouse is Snowflake (the `snow` CLI)
Windows equivalents exist (winget install … covers most of these), but Windows onboarding is
untested — expect to translate paths and shell syntax yourself rather than assume parity.
What setup actually does
It runs once per repo, and detects before it asks — detection produces the facts each question depends on, and a question exists only where a wrong or missing value would fail silently (a dead catalog link, a generic persona, a message with no stakeholders). Anything that fails loudly at verification or first use ships as a commented default instead.
What it looks at first, before asking you anything:
- Which CLIs are on your PATH —
snow,acli,gh,glab,bq,databricks,yq,jq,git— to pre-select the tools you already have. - Which MCP servers are connected in the session (tracker / chat / warehouse).
- What's already in the repo — an existing
.claude/config/stack.yaml(it offers to edit and never overwrites), or existing ticket folders and indexes, which switch it into adopt mode. - Who you are. On a repo that's already configured, an unrecognized person is routed straight into teammate onboarding — a new cloner is never offered the team's shared config as their first action.
Config is three tiers: .claude/config/stack.yaml is the team's committed answer,
people/<id>.yaml holds each person's portable settings, and .claude/config/connections.local.yaml
holds the per-machine ones and is gitignored. bin/effective_config.py merges them — read that, not
the raw file. The machine tier can supply credentials and local paths; it can never change which data
gets read, and never a policy.
Work that arrives from a meeting. project.intake names where work comes from — tracker,
email, chat, meetings. Meeting notes arrive as a file in the ticket's source_materials/,
named YYYY-MM-DD-<slug>-meeting.md: the committed, curated form, trimmed to decisions and
action items. Raw full transcripts are a different matter — they are the most PII-dense thing a
ticket folder holds, so they stay out of git by default, and a guard asks before one is committed
or copied into a docstore backup. It reads filenames and document shape, not meaning, so it
catches the bulk artifact and does not pretend to be a confidentiality review.
What it then asks — in rounds, detected answers pre-selected. Four rounds always run: who
(you, confirmed from identity resolution, and who else is on the team), where work comes from
(tracker or none, key prefix, the tracker's "done" state, how catalog rows link back), where
the data lives (warehouse or none, its required keys, a dev target), and where work goes
(git host confirmed from origin, docstore, chat and its stakeholder include-list, and one
question covering email intake and delivery plus whether an AI notetaker carries work in). Two
more are individually skippable, each skip labeled with its cost — how you work (role,
domain, analysis tools) and house rules (the two
policies whose defaults most often differ by team). A skipped round becomes a # TODO in the
config plus a punch-list entry naming the command that finishes it later (/setup role,
/setup policies). The other eight policies ship as commented defaults you can edit any time.
What it writes:
.claude/config/stack.yaml— your chosen tool slots live (the config key isseams:— "tool slot" is the same thing, internally called a seam), optional ones as commented blocks, each policy with a one-line "when to change this" note.autoUpdate: trueon the marketplace entry — the one key no CLI flag can set, so runningsetupis how auto-update gets turned on at all. It merges: an existing entry keeps thesourceyou have (forks edit that URL), and a deliberatefalseis left alone.AGENTS.md(rules, tuned to your role) and a one-lineCLAUDE.mdthat imports it..claude/settings.json— read-only CLI allows, plus the hooks on a vendored install (omitted on a plugin install, whereplugin.jsonalready wires them).- Folders +
.gitignore—tickets/<you>/,documentation/,resources/,specs/; deliverable CSVs committed by default, PII opting out via*.private.csvor aprivate/folder. - The AI-layer index and a seeded ticket index.
Then it verifies and hands off: two clearly-labelled checks — selftest.sh for kit integrity and
verify_stack.sh for whether your tools are actually reachable (an unreachable tool isn't fatal at
setup time; it prints the auth fix) — then offers to commit the scaffold, since an uncommitted setup
means later ticket PRs reference rules that aren't in the repo's history.
Project-scoped by default
A plugin can't set its own install scope — the repo does. --scope project writes the enablement
into the repo's .claude/settings.json, so it travels with the repo: every teammate who opens (and
trusts) it is prompted to install Ticketwright (no marketplace to add, no config to write), and it keeps
working after the person who set it up moves on. Commit the file. This is what the two Track 1
install commands produce, plus the one key they don't write:
{
"extraKnownMarketplaces": {
"ticketwright": {
"source": { "source": "git", "url": "https://github.com/kyle-chalmers/ticketwright.git" },
"autoUpdate": true
}
},
"enabledPlugins": { "ticketwright@ticketwright": true }
}
Three details in that block are deliberate:
- The source is an explicit
https://…gitURL, not theowner/reposhorthand. The shorthand can resolve to SSH and fail for anyone without GitHub SSH keys; the URL clones over HTTPS through your existing git credential helper (keychain /gh auth login). A fork edits just this one URL. source: "git"is the discriminatorclaude plugin marketplace addwrites for anhttps://…gitURL — thatsourceobject is copied from the CLI's own output rather than hand-authored. (gitandurlare different marketplace source types; don't swap one for the other.)autoUpdateis scoped to formal releases. The version only moves in a tagged release commit — so day-to-day commits tomainnever put teammates onto un-released work. Neither install command writes this key (no flag sets it);/ticketwright:setupadds it, or add it by hand. What it does today: it refreshes the marketplace catalog; Claude Code does not yet swap the installed project-scoped plugin to the new version (the release pick-up caveat in Track 1 has the command pair, andclaude plugin marketplace update ticketwrightrefreshes the catalog by hand).
Installing without --scope project puts Ticketwright in your own ~/.claude/settings.json instead —
right for personal, cross-repo use, but your teammates get nothing. Use the committed block when you
want the whole team on it.
How work flows
Four steps — plan → build → check → ship — and one command to remember. Skills are shown by
their short names here and below; on a plugin install, use the namespaced form
(/ticketwright:ticket, /ticketwright:review, …). The short names work when the kit's skill
files live in the repo itself — vendored, or installed via pip (ticketwright init copies
.claude/skills/ into the repo). A plugin install exposes only the namespaced form, however
fully the repo is configured — /setup writes config, never skill folders.
/ticket <id> opens or resumes the ticket, auto-loads its context + closest prior work,
and routes you to the right next step ↓
/spec-and-build spec mode writes the blueprint (committed first); build mode executes it
/review [--deep] independent QC pass: re-runs queries, walks the validation pyramid → APPROVE / REQUEST-CHANGES
…and at the top of that pyramid, opens the deliverables in YOUR apps and waits
/ship [--go] backup → tracker comment → chat draft → commit + PR — HARD HALT before anything external
Three supporting skills you'll reach for occasionally:
| Skill | What it does |
|---|---|
/setup |
Configure the repo (once) · add a tool later (/setup tool chat) · pick which apps open your deliverables (/setup viewer) · onboard a person (/setup --teammate, entered automatically for an unrecognized person) |
/refresh |
Rebuild the ticket catalog (index) or the domain knowledge pack (context) — day-to-day, hooks keep these fresh automatically |
/productize |
Turn a recurring workflow (quarterly pull, monthly report) into its own parameterized, golden-tested skill |
(The v1 command names — /start-ticket, /qc-review, … — were retired in v3; see the rename map
in docs/troubleshooting.md.)
See it as a graph (Obsidian)
Ticketwright writes a small, auto-maintained graph layer under tickets/ — graph/<owner>.<id>.md
(a node per ticket, keyed by owner + id so two people's same-named tickets never merge) and
objects/<object>.md (a node per data object) — so you can open the repo as an
Obsidian vault and browse your work.
The graph and the catalog are two renderings of one relationship model. The same cross-reference
resolution in bin/build_ticket_index.py writes both in a single pass: tickets/INDEX.md and
tickets/OBJECTS.md are how an agent queries the relationships, and the graph is how a person
sees the shape of the corpus at a glance - which analyses cluster, which objects are load-bearing
across many tickets, where the orphans are. Generated together, they cannot drift apart.
- Open a table like
ANALYTICS.VW_ORDERSand its local graph is every ticket that touched it. - Open a ticket and you see the objects it touched, plus the tickets it built on.
- Zero manual setup. It also writes
.obsidian/graph.json, so the Graph view opens already focused on the tickets↔objects web — READMEs filtered out, ticket and object nodes color-coded. - Your tweaks survive. Forces, zoom, and custom filters/groups are never clobbered.
- Plain markdown — no plugins, no wikilinks. It renders on GitHub too.
/ship stages the graph layer with the catalog — tickets/graph/ and tickets/objects/ alongside
tickets/INDEX.md, tickets/OBJECTS.md and tickets/index_data.json — so the graph a teammate
opens is the graph you see. Nothing ignores the nodes, and the same --check gate covers them, so
a node left out surfaces as index drift in CI instead of a graph only your clone has.
On by default. Set project.graph_notes: false to turn off the whole layer, or
project.graph_config: false to keep the nodes but stop managing .obsidian/graph.json.
Don't have Obsidian? The layer still renders (plain markdown, browsable on GitHub), and /setup
prints one pointer instead of asking anything — install steps, opening the repo as a vault, and
what the node types mean are in docs/obsidian.md.
Sound like you (voice profiles)
Every ticket ends with /ship drafting the tracker comment, chat message, and PR body — and you
almost always edit that draft before it goes out. Voice profiles capture how you write so the
draft arrives already sounding like you, and then learn from the edits you still make.
- Opt-in. Off until a person has a
voice:block inpeople/<id>.yaml. Build a profile with/setup --voice: a short interview (and, if you want, a few of your own already-sent lines). - Per person.
bin/whoami.pyresolves who is working (offline, via the identities each person enumerates inpeople/<id>.yaml— never a fuzzy guess; on a miss it asks who you are and remembers the answer with--bind)./shipmaps that person to their voice profile (bin/resolve_user.py, a thin shim over it) and loads theirvoices/<id>.md. - Within the rails, always. Voice shapes phrasing only.
/shipruns a comms-lint step first (word limits, hyperlinks, include-list) and only then applies voice, so the profile can never breach a word limit, drop a hyperlink, or skip the stakeholder include-list. - It combs itself.
/shipdiffs what it drafted against what you approved and proposes profile updates from the delta — you approve each one; nothing is learned silently. - Personal data. A profile is your writing fingerprint. Committed by default (so a team shares
them like the ticket index); to keep yours private, point your
voice.pathoutside the repo — or gitignore it before its first commit, since gitignoring a file git already tracks does nothing. It stores short approved exemplars — never full confidential threads.
Safety rails (on by default)
- High-risk DB writes ask first — a hook inspects every warehouse command and prompts before
anything irreversible (
DROP/DELETE/UPDATE/TRUNCATE/CREATE OR REPLACE/…), even SQL hidden in a-ffile. Additive work (plainCREATE,INSERT INTO,ALTER … ADD) runs without a prompt. Tune withpolicies.db_write_requires_approval:off|high_risk(default) |all. - External posts hard-halt —
/shipprints exactly what it's about to post (tracker comment, chat message, PR) and waits for your explicit go. - Chat defaults to draft — you click send.
- Deliverables commit with the ticket, PII opts out — exports are committed by default so results
show in the PR; keep customer data out of git by naming it
*.private.csvor dropping it in aprivate/subfolder, and/shiplists what it's about to commit so nothing sensitive slips in. - Every assumption is written down — the ticket README template enumerates them by category.
Hooks, in full
Trust demands transparency: this plugin runs hooks, so here is every one of them. All are Python stdlib-only, make no network calls, never write outside the repo, and fail open — a hook error never blocks your session.
Two places where the DB guard removes a prompt rather than adding one, stated plainly:
- Verifiably read-only SQL is auto-approved — a single simple command, every referenced file
read, and every statement a
SELECT/SHOW/DESCRIBE/EXPLAIN. - Under
bypassPermissionsit prints asystemMessageinstead of asking, because you already opted out of prompting for that session.
Neither can loosen a deny rule in your settings — hooks can tighten permissions, never widen them
past what your own rules allow.
| Event | Script | What it does |
|---|---|---|
| PreToolUse (Bash) | .claude/hooks/db_write_guard.py |
Pauses for confirmation before a warehouse CLI command carrying high-risk SQL (including SQL hidden in -f files / stdin redirects); auto-approves verifiably read-only SQL |
| PostToolUse (Write|Edit) | .claude/hooks/regenerate_ticket_index.py |
Regenerates tickets/INDEX.md / OBJECTS.md when the curated store changes |
| SessionStart | .claude/hooks/session_context.py, ticket_index_context.py |
Emits a short repo/catalog banner inside a ticketwright repo; silent elsewhere |
Every hook is repo-gated: zero cost (and zero output) in repos that aren't set up for
ticketwright. Explicit timeouts are declared so a hung hook can never stall a session. To turn
them all off, disable the plugin (claude plugin disable ticketwright); the skills can still
be vendored without hooks via the kit install.
Adopting an existing repo
Already have years of ticket folders and your own conventions? Run /setup. It:
- detects your existing layout and maps onto it rather than scaffolding over it
- infers the config from evidence — folders, CI, installed CLIs, MCP servers
- classifies your custom commands against the plugin's skills as shadows / extends / unrelated
- writes a
MIGRATION.mdchecklist instead of overwriting anything
Adoption is incremental: run one real ticket through /ticket → /review → /ship before you delete
anything custom.
Installing without the plugin
The plugin above is the primary channel and the one to use with Claude Code. The pip package covers the two cases it can't: vendoring the kit's files into a repo, and running the deterministic engines from a shell or CI.
pip install ticketwright # zero runtime dependencies; stdlib only
ticketwright init # vendor the kit into a repo (no plugin required)
ticketwright install --runtime codex-cli # translate the skills for a non-Claude runtime
ticketwright recall --for ENG-123 # prior-art ranking — no Claude Code needed
ticketwright index --stats # catalog coverage — no Claude Code needed
ticketwright enrich ENG-123 # curated index summary — needs a model CLI on PATH
recallandindexare pure stdlib and run anywhere.enrichcalls a model headlessly. Which command it runs is resolved per runtime fromadapters/runtime/<name>.md(--model-cmdoverrides it), falling back toclaude -p. A runtime that documents no headless command says so and points at the agent-neutral ingest path instead.initcopies the kit's files — skills, agents, hooks, adapters, templates,bin/— and preserves your edits on re-runs (--forceto overwrite).install --runtime <name>is the compatibility layer between the canonical.claude/skills/source and each runtime's own layout (bin/install.shis the same command for a vendored install), covering all seven runtimes and driven by each runtime adapter's declared capabilities, never a name baked into code. Where the runtime already reads the canonical copy it VERIFIES and emits no skills —--runtime claude-codenatively (the Claude Code path is unchanged), and cursor/opencode/cline/devin because they read.claude/skills/directly; the printed report states what that shared file cannot carry for a foreign reader (allowed-toolsanddisable-model-invocationare Claude-specific keys those runtimes ignore, warned per affected skill). Where the runtime cannot see the canonical copy it EMITS a translated copy: codex-cli and antigravity share one.agents/skills/<name>/SKILL.mdemission, each file stamped with a provenance header — hand-copying skill files between layouts is unsupported, because a stale duplicate silently winning over the canonical copy is the failure mode the installer exists to prevent; re-run it to update (a file the installer did not emit is never overwritten — the install fails loudly instead). Skills marked user-invocable-only (disable-model-invocation: true—/setup,/ship,/productize) are emitted with a topmost warning block stating that nothing mechanical prevents model invocation there; every other metadata loss is recorded per runtime inadapters/runtime/<name>.md§ Metadata mapping. Theqc-revieweragent definition is emitted wherever subagents are user-definable (.codex/agents/*.toml; markdown for cursor/devin/antigravity); where they are not (cline) or the definition path is undocumented (opencode), the report says so.--globalemits into the runtime's declared per-user skills root and REFUSES where that root is unknown (antigravity — its documented sources disagree) rather than guessing a path. The install also wires the DB-write guard where the runtime documents a home for it —.cursor/hooks.json(withfailClosed: true, required configuration),.agents/hooks.jsonfor antigravity, a throw-to-deny plugin under.opencode/plugins/— all fronting one scanner (bin/sql_scan.py) throughbin/hook_shim.py. Runtimes with noasktier get thehigh_riskpolicy as deny-with-escape (the deny names a one-shot re-approval), and the report says so at install time; where even the hooks-config location is undocumented (codex-cli, devin) the installer prints the manual wiring line instead of guessing. What is ENFORCEMENT (proven — the native Claude hooks) vs WIRED (emitted, live confirmation owed) vs GUIDANCE vs UNKNOWN per runtime × per hook is stated in the renderedAGENTS.mdenforcement table (and emitted into.clinerules/for cline).
init is a file copy, not a working setup. It deliberately writes no stack.yaml and no
AGENTS.md — /setup renders both from evidence in your repo, and /setup runs in Claude Code.
On another harness you get the skill files (translated by install where needed) but still have to
render the config yourself. A harness-agnostic setup path is on the roadmap, not
shipped — don't read this section as "Ticketwright runs anywhere today."
Learn more
- docs/architecture.md — how it's built: the AI-layer model, tool slots, adapters and the verb contract, and how to add a new tool.
- docs/troubleshooting.md — a skill failed mid-way, a tool is unreachable, the index looks stale, upgrade paths.
- docs/ticket-index.md — the ticket catalog + recall engine in depth.
- docs/obsidian.md — browse your tickets as a graph: install Obsidian, open the repo as a vault, the two node types, and the opt-outs.
- docs/drive-mount.md — backing up to a cloud drive: install the Drive
or OneDrive mount per OS, the
mount_roottier split, or go mountless with rclone. - CONTRIBUTING.md · ROADMAP.md · CHANGELOG.md
CI runs the full self-test on every push; PyPI publishing is OIDC Trusted Publishing (no stored tokens) — see docs/pypi-setup.md.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ticketwright-3.7.1.tar.gz.
File metadata
- Download URL: ticketwright-3.7.1.tar.gz
- Upload date:
- Size: 545.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d68a7d5883b685c246a33ec4cb8b95627e9d6335f92a4a5ec565532e01729303
|
|
| MD5 |
17309c3b4f787508117a86b39594cd99
|
|
| BLAKE2b-256 |
49de4403dacbe3dc45ba255842654c94a9207da6ee91e7268561df9f60eafc19
|
Provenance
The following attestation bundles were made for ticketwright-3.7.1.tar.gz:
Publisher:
publish.yml on kyle-chalmers/ticketwright
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ticketwright-3.7.1.tar.gz -
Subject digest:
d68a7d5883b685c246a33ec4cb8b95627e9d6335f92a4a5ec565532e01729303 - Sigstore transparency entry: 2586694501
- Sigstore integration time:
-
Permalink:
kyle-chalmers/ticketwright@6c73c3e910788211cc6c2b67d80f90ba7b6b2d15 -
Branch / Tag:
refs/tags/v3.7.1 - Owner: https://github.com/kyle-chalmers
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6c73c3e910788211cc6c2b67d80f90ba7b6b2d15 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ticketwright-3.7.1-py3-none-any.whl.
File metadata
- Download URL: ticketwright-3.7.1-py3-none-any.whl
- Upload date:
- Size: 576.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
588141e1fabce19ef5476e1509949794dddc8506df72a5422494e90fb9cb4e8d
|
|
| MD5 |
b7e22ea63aeb403d299e8c29c5c13cda
|
|
| BLAKE2b-256 |
ebaff40b1e62bd41edcd974ed7dd53b1dfa07c2159fec557dfe70b7f10ae2ad6
|
Provenance
The following attestation bundles were made for ticketwright-3.7.1-py3-none-any.whl:
Publisher:
publish.yml on kyle-chalmers/ticketwright
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ticketwright-3.7.1-py3-none-any.whl -
Subject digest:
588141e1fabce19ef5476e1509949794dddc8506df72a5422494e90fb9cb4e8d - Sigstore transparency entry: 2586695183
- Sigstore integration time:
-
Permalink:
kyle-chalmers/ticketwright@6c73c3e910788211cc6c2b67d80f90ba7b6b2d15 -
Branch / Tag:
refs/tags/v3.7.1 - Owner: https://github.com/kyle-chalmers
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6c73c3e910788211cc6c2b67d80f90ba7b6b2d15 -
Trigger Event:
push
-
Statement type: