setup
Skill4 starsChanged 18 days agoMITView on GitHub
Set up Ticketwright in a repo. It detects your tools, interviews you in rounds (you can skip the last two, and each skip says what it costs), writes the config, and scaffolds folders. Team modes configure the repo as a full setup, one tool slot (tracker, warehouse, chat, docstore, meetings or vcs), roles, the team, or policies. Person modes configure one person as a teammate, their voice, or their viewer. It also adopts existing repos.
- Installs packages
- Commits and pushes
191 other projects use a version of this.
Adopted by
Most recently taken up by
- hydra-db/hydradb-claude-codetheir own version, first seen Nov 8, 2026
- WALKERKILLER/Look-Tongji-Notestheir own version, first seen Nov 8, 2026
- steerlabs/opensteertheir own version, first seen Nov 8, 2026
- ansvisor/ansvisortheir own version, first seen Nov 8, 2026
- dsifry/metaswarmtheir own version, first seen Nov 8, 2026
- anombyte93/prd-taskmastertheir own version, first seen Nov 8, 2026
Used with
Projects with it also use add-voice-transcription, add-discord and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
juancruzmunozalbeloSkilladd-voice-transcription
Add voice message transcription to NanoClaw using OpenAI's Whisper API. Automatically transcribes WhatsApp voice notes so the agent can read and respond to them.
Also in 5 projects that use this
MicrosoftSkilladd-discord
Add Discord bot channel integration via Chat SDK.
Also in 11 projects that use this
MicrosoftSkilladd-slack
Add Slack channel integration via Chat SDK.
Also in 11 projects that use this
juancruzmunozalbeloSkilladd-telegram-swarm
Add Agent Swarm (Teams) support to Telegram. Each subagent gets its own bot identity in the group. Requires Telegram channel to be set up first (use /add-telegram). Triggers on "agent swarm", "agent teams telegram", "telegram swarm", "bot pool".
Also in 7 projects that use this
- +8 more from microsoft
Other versions191 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 191
- anthropics/knowledge-work-plugins★ 35ksame name
- garrytan/gbrain★ 32ksame name
- gavrielc/nanoclaw★ 31ksame name
- nanocoai/nanoclaw★ 31ksame name
- qwibitai/nanoclaw★ 31ksame name
- Q00/ouroboros★ 6.3ksame name
- pytorch/executorch★ 5.2ksame name
- agenticnotetaking/arscontexta★ 3.5ksame name
- aws/agent-toolkit-for-aws★ 3.4ksame name
- spotify/portal-ai-plugins★ 2.8ksame name
181 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
The file
Show its rules and text
We split this skill into the rules it gives an agent
23 rules. 0 are common, 7 are distinctive, and 16 are very specific to this project.
0
Common
Most of the 170 edited versions we read keep these.
7
Distinctive
This author's own choices. Worth a look.
16
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 32
Every mode sits on one scope axis.
- Distinctive: line 114
Use multi-select whenever more than one answer can be true, and add "All of these" when a multi-select has three or more options.
- Distinctive: line 129
When a kit limit shapes the answer, configure around it and say in one line of the plan what was set up and why, never hand the person the …
- Distinctive: line 136
When a connector's tools are named differently from what its setup notes expect (another agent's version of the same connector, say), do …
- Very specific: line 153
Read capabilities from this skill's kit_paths.py --json probe the first time a question comes up, and keep the result.
- Very specific: line 192
Every reference file (scaffold.md, teammate.md, voice.md) and every rendered artifact invokes kit scripts as bash "$(git rev-parse …
- Very specific: line 228
Follow teammate.md: resolve WHO this is (whoami, binding on a miss) → install checklist → per-tool auth walk-through → their tracker …
- Very specific: line 238
Follow voice.md: identify the person → wire their people/<id>.yaml → render the seed from templates/voice-profile.md.tmpl → interview (≤5 …
- Very specific: line 259
Finishing a slot chosen before it was connected: When the slot holds a value still written TODO (most often mcp: TODO.
- Very specific: line 264
Write its exact tool prefix to mcp: (read from the tool list, never typed by the person), the same rule as interview.md § the mcp: value.
- Very specific: line 266
Run the adapter's read-only probe: print it with bash "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" adapter_surface.py …
- Distinctive: line 271
Ask only the questions that slot skipped because the tool was not there (a channel, a project, a folder), then verify and re-render as …
- Very specific: line 330
Keep the ZIP case visible: if the folder name ends in -main or holds files but no .git (files other than the kit's own install, which …
- Very specific: line 367
No git name or email?: When the probe reports name_set or email_set false, ask before round 1 (they are what identity and every commit …
- Very specific: line 380
Obsidian: never a question; graph_notes/graph_config already default correctly.
- Very specific: line 493
every file this phase will create or change — .claude/config/stack.yaml, AGENTS.md, CLAUDE.md, the human-facing README.md (or …
- Distinctive: line 501
Only on explicit confirmation, execute — source of truth first, derived files after:
- Very specific: line 502
Write .claude/config/stack.yaml per stack.schema.md (chosen tool slots live.
- Very specific: line 522
If the write is refused or blocked, do not retry it and do not reach the file another way (a shell redirect, cp, a script, …
- Very specific: line 586
The state, then the next command: When every tool connected or was deliberately left for later: <!-- ticketwright:user-facing:begin --> …
- Distinctive: line 591
When something the first ticket needs is not working yet (a tool that did not connect, a sign-in still pending), lead with "Almost ready …
- Very specific: line 611
When the .claude/statusline.sh copy was refused too, add one line: run cp "$(bash bin/tw --kit)/.claude/statusline.sh" …
- Distinctive: line 674
the launcher pair is not optional: every command in AGENTS.md and every reference file routes through it, so a clone without those two …
“Common” is measured: kept by at least 6 in 10 of the 170 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
/setup
Why this skill spells out a kit path while everything else calls
bin/tw:/setupis the bootstrapper. On a plugin install the project has nobin/until step 5b of Phase 3 puts one there, so it cannot resolve assets through the launcher it is about to install.This exemption covers this file's body and nothing else. The runtime substitutes the exact token
${CLAUDE_PLUGIN_ROOT}into a SKILL.md body at skill launch, so the formTW_KIT="${CLAUDE_PLUGIN_ROOT}"; <cmd> "${TW_KIT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/..."works here. It does not work inscaffold.md,teammate.mdorvoice.md: those are opened with Read, which returns raw bytes, so the token arrives verbatim, expands to empty, and silently points every path at/…. Those files use the project launcher like the rest of the kit.Never write the one-expansion
${CLAUDE_PLUGIN_ROOT:-...}anywhere. It matches no token, so it reaches the shell with the variable unset on every install shape — the bug that broke every documentedbin/command for months.
One skill, three jobs: configure a repo (run once), add a tool later (/setup tool chat),
and onboard a person (/setup --teammate). Detect first, ask last — detection produces the
facts a question depends on before that question is asked. What earns a question is decided by one
rule, not a count: ask when a wrong or absent value would still yield a confident-looking
output; leave a commented default when it fails loudly at verify_stack.sh or on first use
(see interview.md). Never promise a question count.
Every mode sits on one scope axis. TEAM-scoped modes write the team's committed config: the
default repo-configuration mode, tool <tracker|warehouse|chat|docstore|meetings|vcs>, and the round re-runs
role / team / policies. PERSON-scoped modes write
one person's own config: --teammate (the per-person flow), --voice, and viewer. The axis is
who the config is about, not committed-vs-local — --voice is person-scoped yet writes a
committed file.
THE SCOPE INVARIANT — a team mode may declare that a person EXISTS. Only that person's own flow may declare who they are or how their machine connects.
Concretely: a team mode may create an identity-free tier-2 placeholder for a teammate — the file
people/<id>.yaml(the filename is the id) holding adisplay_name:and nothing more. It must NEVER write another person'sidentities:list (the routing-critical fieldwhoamimatches on), never a voice-profile reference (what makes/shipspeak as them), and never any tier-3 value (their machine — which the person running team setup is not sitting at). Where a placeholder and that person's own flow disagree about them, the person's own flow wins. And be honest about what a placeholder does NOT buy: an identity-free stub still returnsmissfromwhoami, because resolution requires an identity-map hit. Its value is narrower — a team-visible roster, a way to tell "this repo just upgraded" from "this person is new", and named candidates for the bind interview to offer on a miss.The same line protects the team file:
.claude/config/stack.yamlis COMMITTED and SHARED, so it may only ever receive TIER-1 values — which tool fills each slot, which data the team reads, the policies, the ticket conventions. A detected MACHINE-LOCAL value — a named profile or connection from a tool's own local config file, a home-directory mount path — must NEVER be written here: it would hand every teammate one person's machine. Those belong in.claude/config/connections.local.yaml(tier 3, gitignored); a person's portable settings belong inpeople/<id>.yaml(tier 2). Where a verify command needs a personal value, write the{token}and leave the value to tier 3 — each adapter declares which of its keys are personal in itsuser_keys:frontmatter.bash tools/verify_stack.shwarns when a machine-local value is found in committed config.Machine-local files when the session runs in a worktree. Many agent apps start every session in a linked git worktree, and gitignored files are not shared between worktrees. So never write a machine-local file (
connections.local.yaml,viewer.local.yaml) by its relative path: write it wherebash "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" kit_paths.py --local-config <file name>points. In a worktree that is the main checkout's copy, which every later session on this machine reads. The resolver reads the same place, so a value set here is not lost when the next session starts in a fresh worktree.
THE MODEL-INVOCATION CONFIRM GATE —
/setupis model-invocable, in every mode. When the model reaches for/setupon its own initiative (the user did not explicitly invoke it), it must, in EVERY mode — default,tool,role,team,policies,--teammate,--voice,viewer, adoption, and Bootstrap — print what it is about to write and stop for the user's explicit confirmation before writing ANY file. And regardless of who invoked it, confirm before writing or changing committed team config (.claude/config/stack.yaml,AGENTS.md,CLAUDE.md,.claude/settings.json,MIGRATION.md) or seeding another person'speople/<id>.yaml. An explicit user invocation is its own authorization for that mode's own scoped writes (aviewerrun the user asked for may write its gitignored file; a person's own--teammaterun may write their own tier-2/tier-3 files) — a run the model started is not. This gate is an instruction the agent follows, not a mechanical block: no runtime enforces it, which is why it is written here where the agent reads it, and why the default path's Phase 3 prints the specifics.
How setup talks to the person: every mode
The person may know nothing about Ticketwright, and may not be a programmer. Trust comes from saying plainly what is about to happen and why, before it happens.
- Show the map first (default mode), before the first probe. Use this block, in these words:
<!-- ticketwright:user-facing:begin -->Setup has four steps:- Check this machine. I look at what's installed. No questions, unless this folder isn't a git repository yet (then I ask before starting one).
- Your work. A few questions about how work reaches your team, where your data lives, and how you share results. Each one says why it's asked.
- Review the plan. I show every file I'd create. Nothing is written until you say yes.
- Write and check. I create the files, check that each tool connects, and tell you anything left to finish. You can stop at any point. Your team's settings aren't written until you say yes in step 3.
<!-- ticketwright:user-facing:end -->The other modes open with one line instead: what this run changes, and whether it asks anything. - Open and close every step with one line each. Open: what this step does and whether it asks anything. Close: what got done, and what comes next.
- Every question explains itself. One line on what it decides and why it matters; each option says what choosing it changes. A detected value is the recommended option (first, marked recommended, with where it came from), never the answer: the person still picks.
- Ask by the question rule in "How this skill asks the person something" below. This skill's
probe is
!TW_KIT="${CLAUDE_PLUGIN_ROOT}"; python3 "${TW_KIT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/tools/kit_paths.py" --json. In prose the interview goes one question at a time, waiting for an explicit answer before the next, and an interview never runs in a subagent. - Option rules (both presentations):
- Options come from detection, from the person's own words, or from the fixed choices a question defines. Never invent a value (a project key, a folder, a dataset, a channel) and offer it as if it existed. A typed value that detection can check is checked before it is written.
- Offer "None / we don't have one" whenever absence is a real answer.
- Use multi-select whenever more than one answer can be true, and add "All of these" when a multi-select has three or more options.
- More real options than fit: show the most likely first plus a "Show more" option that pages through the rest: when it is picked, ask a follow-up question listing the next options (the same multi-select, with what was already picked carried over), never "type them in Other". "None / we don't have one" stays on the first page, never behind "Show more". Never move a detected option into the question's wording instead ("Postgres: type it in Other"); that hides it. Free text is always possible for a value not listed (the question tool's own "Other", or "or type your own" in prose).
- A question about which tool the team uses offers every tool the kit has setup notes for in
that slot, detected or not. Detected ones come first with what was found; an undetected one
is labeled "not connected here yet", and choosing it adds a connect-it item to the list of
things to finish. Say on that choice that a connector added in the app reaches this session
only after
/reload-pluginsor a new session. Never make the person type the name of a common tool the kit supports.
- Talk about the work, not the kit. Questions use the person's words: tickets, data, results, files, the team. "Tool slot", "target", "tier", "seam" and "adapter" do not appear in a question. When a kit limit shapes the answer, configure around it and say in one line of the plan what was set up and why, never hand the person the limit as a choice to make.
- When the runtime refuses a command (a sandbox that blocks creating
.git, a credential store it cannot write), do not route around it: give the person the exact command to run in their own terminal, say what it does in one line, and wait for them to say it is done. And never edit the kit's own files (its setup notes, templates or scripts) during setup: everything this repository needs, including a connector's exact name, goes instack.yamlor the machine file. When a connector's tools are named differently from what its setup notes expect (another agent's version of the same connector, say), do not rewrite the notes to match: record that tool as a thing to finish, naming the tool names you can see, so the mismatch is fixed once in the kit instead of silently in one repository. - Privacy while detecting. Setup never reads message bodies, meeting notes or file contents. Listing names inside someone's own space (their drive's folders, their projects) is fine after asking, names only. Suggestions from past usage are opt-in and counts-only (interview.md § Suggestions from past usage).
- Lead the ending with success (Phase 4, step 8): "You're ready for your first ticket" and the one command that starts it, or "Almost ready for your first ticket: N things to finish first" with each item and the command that finishes it. Optional follow-ups come after, each with why it matters and the command that finishes it.
How this skill asks the person something
<!-- ticketwright:ask-rule:begin -->The kit's one question rule, the same in every skill that asks:
- Read
capabilitiesfrom this skill'skit_paths.py --jsonprobe the first time a question comes up, and keep the result. capabilities.structured_questionsisyesandcapabilities.question_toolis notnone→ ask with that tool, withincapabilities.question_limits(when that saysunknown: one question per call, at most four options). Group up to the limit only questions that do not depend on each other's answers; ask a dependent question after the one it depends on.- Otherwise (
no/unknown, or the probe failed) → ask in chat as a short numbered list, one question at a time unless this skill says a set goes in one round, and wait for an explicit answer. - The question content is the same either way; only how it is shown changes. No skill names a
runtime's tool: the runtime's own adapter (
adapters/runtime/<name>.md) does. A question tool is not available inside a subagent, so the main session asks. - Never invent an option. Options come from what this run found or the fixed choices a step names. A recommended option goes first, marked recommended, with where it came from; the person still picks. Each option says what choosing it changes, and free text is always possible (the tool's own "Other", or "or tell me something else" in prose).
- Whatever the person needs to answer is already in chat before the question, so a picker never hides it.
- A reply the kit asks for word for word (
ship unreviewed, theyesbefore destructive SQL) stays typed: an unreviewed ship or an irreversible write is never one click. The external-send gate (go ahead) is asked like any other question once the full delivery plan is in chat: its first option is "Go ahead", which delivers exactly the printed plan, and with no question tool the person typesgo ahead.
<!-- ticketwright:ask-rule:end -->In this skill the questions are the interview's, the plan review's yes before anything is written, and each mode's own confirmations. Rules 3 to 5 above add setup's own wording and option rules on top of this one.
Start here, in the default mode: show the map before anything else
The very first thing a default-mode run outputs is the four-step map in "How setup talks to the person" above, word for word, before the launcher command below, before the repository check and before any question. A run that opens with a command or a question has skipped the one thing that tells the person what setup is about to do. (Other modes open with their one line instead.)
Ensure the launcher — run this FIRST in every mode, before opening any reference file
(After the map, in the default mode. When the folder is not a git repository yet, ask Phase 1's
repository question first and run this block once the repository exists, so a person who stops
there is left with nothing copied.)
Every reference file (scaffold.md, teammate.md, voice.md) and every rendered artifact invokes
kit scripts as bash "$(git rev-parse --show-toplevel)/bin/tw" <script>. That resolves only if the
project has the launcher. This file's body is the one place the plugin token is substituted, so
this is the one place that can install it. Idempotent — run it every time:
TW_KIT="${CLAUDE_PLUGIN_ROOT}"; K="${TW_KIT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}"
if [ -f "$K/tools/kit_paths.py" ]; then KS="$K/tools"; else KS="$K/bin"; fi
if [ -f "$KS/refresh_launcher.py" ]; then python3 "$KS/refresh_launcher.py" --write
elif [ -f "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" ]; then
bash "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" refresh_launcher.py --write
fi
bash "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" --kitThe first lines install the launcher when it is missing and replace a copy older than this kit's
(an older copy cannot find a kit whose scripts moved from bin/ to tools/). On an agent that does
not fill in the plugin path, the project's own launcher runs the refresh instead. They never touch a
copy that is current or newer, a symlink, or a file with the launcher's name that is not
Ticketwright's. When the output says Updated the launcher, tell the person in one line and
list both files in what to commit.
Why this is not only a Track-1 concern: a teammate joining a repo configured by an earlier
Ticketwright finds no committed launcher, so without this step their very first --teammate command
fails — the same "No such file or directory" this release exists to end, arriving through a
different door. Mention the two new files when you list what to commit.
The final line must print a kit path. If it instead says cannot locate the ticketwright kit, the
copy succeeded and the RESOLUTION failed — do not read it as a bad copy. On a plugin install
kit_paths.py finds the kit through this repo's row in ~/.claude/plugins/installed_plugins.json,
so the usual cause is that the plugin is not installed for this repo at project scope: run the
doctor's repo_install check (bash "$(git rev-parse --show-toplevel)/bin/tw" plugin_doctor.py) and
follow its fix, then re-run the line above. Stop either way rather than continuing into step 6,
whose commands all route through this launcher.
Mode: --teammate — the per-person flow (person-scoped)
Follow teammate.md: resolve WHO this is (whoami, binding on a miss) → install
checklist → per-tool auth walk-through → their tracker account (what /ticket assigns on a yes) →
detect their machine and write their machine file →
verify, bound to the team's expected target (each MCP-transport slot also gets its permission
posture probed, compared, and recorded) → read-the-map → guided first-ticket dry run. Requires
an existing stack.yaml. Entered automatically when the repo is configured but the person is
unrecognized (Phase 1 routing below); --teammate stays the explicit re-run. Offer the --voice
step at the end so the new person's comms sound like them from their first ship.
Mode: --voice — build or refine a person's comms voice profile (person-scoped)
Follow voice.md: identify the person → wire their people/<id>.yaml → render the seed
from templates/voice-profile.md.tmpl → interview (≤5 questions) → optionally learn from short
approved exemplars → save. /ship then drafts in that voice, within the hard comms rails. Requires
an existing stack.yaml. (This is a first-class mode, not a tool slot — voice is never a
seams.* entry.)
Mode: tool <tracker|warehouse|chat|docstore|meetings|vcs> — add one tool slot to the team config (team-wide)
E.g. /setup tool chat. Detect candidates for just that tool slot, then run only that slot's
interview round from interview.md (round 2 for tracker, round 3 for warehouse,
round 4 for chat/docstore, round 2's meetings follow-up for meetings, round 4's code-host question for vcs, including the slot's
adapter-required keys), add the block to committed stack.yaml, verify it, and re-render
AGENTS.md. Nothing else changes.
tracker and vcs are re-entry slots too, and they are the ones people need most. The
interview always fills them, so they are never absent — but they are routinely filled with the
WRONG transport detail, and until this mode accepted them there was no documented command that
repaired one. The case that forced this: a person on an MCP-only tracker whose verify names a CLI
they do not have, so verify_stack reports a healthy tracker as unreachable. /setup tool tracker
re-runs that slot's questions — transport, mcp:, cli:, and the read-only verify — against the
existing config, edit-never-overwrite. Same for vcs. Where the adapter declares its CLI optional
(requires_by_transport:), the transport comes from detection exactly as in
interview.md round 2, and cli: is asked only when that CLI was detected.
Finishing a slot chosen before it was connected. When the slot holds a value still written
TODO (most often mcp: TODO; the session banner and verify_stack.sh both name these), this
mode finishes it instead of asking everything again:
- Find the connector in your own tool list. Not there yet: say it needs connecting and a reload
(
/reload-pluginsor a new session), and stop; the slot staysTODO. - Write its exact tool prefix to
mcp:(read from the tool list, never typed by the person), the same rule as interview.md § themcp:value. - Run the adapter's read-only probe: print it with
bash "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" adapter_surface.py --adapter <path> --probeand call it once. - Record the result in
.claude/config/posture.local.yaml, as teammate.md describes for an MCP slot. - Ask only the questions that slot skipped because the tool was not there (a channel, a project,
a folder), then verify and re-render as above.
The same steps apply to every tool slot with an MCP connection: tracker, warehouse, chat, docstore
and meetings.
Deprecated spellings: the old
/setup chat//setup docstore//setup warehouse(withouttool) keep working for one release — accept them, print "Note:/setup chatis now/setup tool chat; the old spelling goes away in the next release." (substituting the slot named), and continue as normal. The old spelling collided with person-scopedviewerin one syntax while meaning the opposite scope, which is why the split exists.
Modes: role · team · policies — re-run one interview round (team-wide)
The re-entry commands for what the repo interview covers outside the tool slots — each re-runs exactly one round of interview.md against the existing config, edit-never-overwrite:
/setup rolesetsproject.role, the one thing the default interview never asks (question 13a in interview.md), then re-runs round 5 (project.domainandproject.team_docs) and re-rendersAGENTS.md. Role is opt-in: a repo that never runs this keepsgeneralist./setup teamre-runs round 1's roster question: who else is on the team, writing one identity-freepeople/<id>.yamlplaceholder per new person named (files, never folders), under the scope invariant above./setup policiesre-runs round 6:db_write_requires_approval,human_review_handoffandship_merges_pr. Each also clears its round's# TODO(setup)line fromstack.yamlonce the answers are written. These exist so a skipped round is a deferral, not a dead end — every skip's punch-list entry names the command that finishes it. Requires an existingstack.yaml.
Mode: viewer — which apps open your deliverables (person-scoped)
The re-run entry point for the viewer interview in viewer.md: follow it in full. The
same interview runs after round 6 of the default path, in --teammate, and at the /review
hand-off the first time it finds no usable viewer, so a skipped answer is asked again where it is
needed. Which app opens a .sql is a personal choice, so it lives in a gitignored per-user file and
each teammate answers for themselves; this mode never touches stack.yaml. It opens with the one
sentence on what the review stage is for, then asks where the choices apply, which kinds of files
the person checks at review, and which app opens each kind, and confirms with handoff.sh
--dry-run, which prints the resolved commands without launching anything. Never commit this file.
Default mode — configure the repo
(The map was already printed, per "Start here" above. Then the launcher step, then Phase 1.)
Phase 1: Check this machine (no questions unless the folder is not a repository yet)
Open with the map from "How setup talks to the person" above, then say this step's one line: "First I'll look at what's installed on this machine. This step asks nothing unless this folder isn't a git repository yet."
- Preflight — the ways a run is doomed before it starts. Both probes are read-only and cheap;
run them before anything else.
- Is this a repository?
!git rev-parse --show-toplevel: a non-zero exit means there is no.githere. Ticketwright branches, commits and opens pull requests, so it needs one. Do not halt: ask, with these three options (the first is the recommended one only when the folder is empty apart from the plugin's own.claude/settings.json):- Start a new repository here: "I'll run
git initin this folder. Nothing is shared anywhere until you add a remote." - I'll clone my team's existing repository: "Stop here; clone it with
git clone <the repository's URL>, then run/setupinside the clone." - Stop: nothing is changed.
On option 1: run
git init, then ask once whether to connect a remote now. Creating a new remote repository (for example on a code host) is an outward-facing action: only on its own explicit yes, private unless the person says otherwise, and never as part of another answer. "Not now" is a fine answer; the first push can come later. Keep the ZIP case visible: if the folder name ends in-mainor holds files but no.git(files other than the kit's own install, whichticketwright initor the plugin put there), say "If this folder came from a Download-ZIP button, it has no history to branch from, clone the repository instead (git clone <the repository's URL>) and run/setupthere," and offer option 2 first.
- Start a new repository here: "I'll run
- Is the kit installed and usable here?
!TW_KIT="${CLAUDE_PLUGIN_ROOT}"; python3 "${TW_KIT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/tools/plugin_doctor.py" --json— one read-only pass over the install prerequisites (it never installs anything, and makes no network call). Report every finding that is notok, each with itsfixlines verbatim: they are exact commands, and a paraphrased command is one nobody can run. Afailon any of these three is a halt, because nothing setup writes reaches the cause:scope_supported— the agent CLI is too old to install a plugin for one repo; the fix names both the in-session install route and the update command for how it was installed;repo_install— nothing is installed for this repository;install_payload: the install record points at a directory that does not exist, or the payload is missing files setup needs (this is the install-completeness check Phase 4 relies on). Everything else (warn,unknown, a global-only install, a stale catalog) is reported and the run continues. On a non-plugin install (a vendored kit, a pip install) the install checks answerunknown: that is the expected answer there, not a failure, so leave those out of what you tell the person instead of listing them. If the script is absent (an older kit copy), say so in one line and carry on.
yq: needed only by the kit's full self-check (tools/selftest.sh), which setup no longer runs by default (Phase 4). Whenyq_presentis notokon a vendored or edited kit, print the platform install command the doctor gives and offer to run it now; on a plugin install, mention it once and move on. Identity routing is unchanged: it happens at step 4 below.
- Is this a repository?
- CLIs and machine facts, in one probe:
!TW_KIT="${CLAUDE_PLUGIN_ROOT}"; python3 "${TW_KIT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/tools/setup_detect.py" --jsonreports the command-line tools on PATH, the git facts (repository, branch, origin host only, default branch and where that answer came from, whether a git name and email are set), the synced-drive folders (names and paths of the mount roots, never their contents), Obsidian, and the analysis apps installed here. Use its answers; do not re-probe by hand, and never state a fact it did not report (a "no synced drive" claim needs this probe's emptymountslist behind it). If the script is absent (an older kit), fall back to the CLI probe below and say the other facts are unknown:!for c in snow acli gh glab bq databricks yq jq git rclone; do command -v $c >/dev/null && echo "✓ $c" || echo "– $c"; doneNo git name or email? When the probe reportsname_setoremail_setfalse, ask before round 1 (they are what identity and every commit use): "Git doesn't know your name or email on this machine yet. What should your commits show?", offering the name and address detection saw elsewhere plus free text. Set them for this repository only (git config user.name "<name>",git config user.email "<email>"), the same fix teammate.md step 0.5 uses. - MCP servers connected this session (tracker / chat / warehouse / docs / meeting servers): the one thing a shell probe cannot see. List them by the name the person would recognize.
- What the facts mean (probes, not questions).
- Origin:
origin_public_hostdecides the roster privacy warning (phrased "if this repo is public", never "it is", visibility cannot be checked offline), and the origin host is the VCS answer round 4 confirms instead of asking. The default branch comes fromrefs/remotes/origin/HEADwhen there is one; on a repo nothing has been pushed to yet the probe reports the current branch and says so, confirm it rather than stating it. - Obsidian: never a question;
graph_notes/graph_configalready default correctly; this only decides which one-liner the Phase-4 report prints (step 8). - Docstore mount roots: REPORT-ONLY. A cloud-storage mount root is a machine-local
(tier-3) value: display what was found and route it to the person flow
(
.claude/config/connections.local.yaml, teammate.md steps 3–4). Writing it into committedstack.yamlis exactly the leak the tier split exists to prevent. Found no mount and the configured docstore expects one? Do not ask — print the guide as its full GitHub URL, https://github.com/kyle-chalmers/ticketwright/blob/main/docs/drive-mount.md, which covers installing the mount per OS and the mountlessrcloneadapter. Never a baredocs/path:docs/does not ship in the PyPI package. - Names only, here too: report profile/connection/mount names and paths — never echo a tool config file's contents anywhere; those files can hold plaintext secrets. After step 4 below, close this step with this list: print it as a bulleted list, one line per area, filled in with what the probes found. Not a one-sentence summary ("I've mapped out your tools: …"), and not a raw tool inventory:
Here's what I found on this machine: - Work comes from: <the trackers and request sources found> - Data lives in: <the warehouses found, and whether each is signed in> - Results go out through: <the chat and email connections found> - Files are kept in: <synced drive folders found, or none> Next, a few questions about how your team works. - Origin:
- Existing state — five routes, checked in order. First write the boundary down: the ADOPT
triggers are evidence of prior ticket work — ticket-looking folders, an existing index, or
custom
.claude/commands/.claude/skills. A repo whose only Ticketwright trace is.claude/settings.json(plugin enablement) is fresh — enablement is how the kit arrives, not evidence of prior work.stack.yamlexists but there is noAGENTS.md→ a Phase-3 halt left half-done (the config was written or hand-created; the scaffold never ran). Checked FIRST, before the Bootstrap route, because a hand-created recovery has nopeople/yet either. Write whatever of step 5 is still missing (.claude/settings.json, and the round-1 machine pin if it was refused), then resume at step 6.stack.yamlexists but there is nopeople/directory at all → this repo predates per-person config: run the Bootstrap below, then return to what the person asked for.stack.yamlexists (andpeople/too) → resolve who is at the keyboard:!TW_KIT="${CLAUDE_PLUGIN_ROOT}"; python3 "${TW_KIT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/tools/whoami.py" --root . --jsonmiss→ this is a teammate new to this repo: switch to teammate.md automatically. Editing the team's shared config must never be a new cloner's first offered action. (--teammatestays the explicit re-run.)ambiguous→ ask which of the named candidates they are (per the question rules above), bind withwhoami.py --bind <id>, then re-run the check.conflict→ surface whoami's warning verbatim and resolve the identity first — fix the stale side (the machine pin, or this repo's git config; step 0 of teammate.md walks it) before offering any team-config edit.resolved→ offer to edit the existing config; never overwrite it (the half-done case is route 1 above, so it never reaches this branch).
- No
stack.yaml, and the adopt triggers above are present → an existing repo: switch to adopt.md (map onto what's there; write MIGRATION.md). - None of the above → a fresh repo: continue to Phase 2.
Bootstrap — a configured repo that predates people/
Immediately after an upgrade, people/<id>.yaml exists for nobody, so whoami returns miss for
the whole team — including whoever configured the repo — and the teammate route above would treat
every existing contributor as brand new. Seed the roster from what the repo already knows, and
confirm rather than onboard:
- Collect candidates from
project.assignee_dir, any legacyproject.voice_profiles.mapentries, existingtickets/<owner>/folders, and recentgit logauthors. Author names and emails are DISPLAY HINTS for the confirmation only — choosing an id is each person's own job. - Confirm with the person: "This repo has work by
<display hints>. Which of these are current teammates — and which one is you?" An id must be a plain identifier (letters, digits, dot, dash, underscore): it namespeople/<id>.yamlandtickets/<id>/. - For each confirmed teammate who is not present, write the identity-free placeholder the
scope invariant allows:
people/<id>.yamlcontainingdisplay_name:only — never theiridentities:, voice, or any machine value. Say plainly that a placeholder still resolves asmissuntil that person's own first run binds their identities. - The person at the keyboard is the one answer available now: run
whoami.py --bind <their id>(their own answer is authority) and show the one-line "Working as …" confirmation. This is what distinguishes "this repo just upgraded" (seed, confirm, carry on with what they asked for) from "this person is new" (the teammate route above).
Phase 2: Your work (questions; detected answers recommended, defaults visible)
Open with one line: "Now a few questions about how your team works. Each says why it's asked, and
nothing is written yet." Then run the interview in interview.md, following the
question rules in "How setup talks to the person" above. Six rounds, cut by whether skipping is
survivable: rounds 1–4 always run (who · how work reaches you, where the one intake question
covers the tracker, email, meeting notes and chat, and writes project.intake · where the data
lives · how results are shared and where files are kept), rounds 5–6 are individually skippable (how you work · house rules), the skip
offered at that round's header and labeled with its cost — never as a global "take defaults for
the rest". What earns a question is the rule at the top of this file, not a count. Every chosen
adapter's required keys are asked; "I'll fill it in later" writes the key as a # TODO and keeps
going — verify_stack.sh names any unset required key on every run (a warning, not a failure),
so a deferred key is reported rather than lost. A skipped round is written down twice — a
# TODO(setup) line in stack.yaml and a punch-list entry in the Phase-4 report — each naming
its re-entry command (/setup role for round 5, /setup policies for round 6; later,
/setup team adds teammates and /setup tool <tracker|warehouse|chat|docstore|meetings|vcs> adds a declined slot).
Everything the interview does not ask ships as a commented default the user can edit later:
default_epic, word limits, the other eight policies — each with its "when to change this" note.
Check the draft before showing the plan. Compose the draft stack.yaml and the draft machine
file (connections.local.yaml: the person pin plus any profile, connection or mount values) in the
session's scratch space, never in the repo, and run:
!TW_KIT="${CLAUDE_PLUGIN_ROOT}"; bash "${TW_KIT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/tools/setup_draft_check.sh" <draft stack.yaml> <draft connections.local.yaml>
It copies both into a throwaway repository and runs the tool check there without contacting any
tool, so a refused character in a folder name, a missing destination or an unset required key shows
up now, while it is still a question, instead of after the files are written. For each problem it
reports, go back to the question that produced the value and ask again in plain words (for example:
"Folder names can't contain &. Use 'Sales and Ops Tickets' instead?"). Unreachable tools are not
problems here: nothing is contacted yet. Phase 3 starts only when the draft check has no ✗ lines
other than ones the person chose to leave for later.
Phase 3 — Write & scaffold (HARD HALT → confirm the plan before the first write)
This is the default path's instance of the model-invocation confirm gate above — the fresh-repo path writes the most (committed team config + scaffold), so its plan is the most detailed. Print the resolved setup plan, then stop and wait for the user — nothing is written until they confirm. The human authorizes THE PLAN, not merely the idea of setup. Open with one line: "Here's everything I'd set up. Nothing is written until you say yes." The plan names:
- what they told you → what gets configured, as a short table in their words (for example "You share results by email and Slack → both are set up; you pick who gets each result when you ship it"), with any tool left for later and the command that adds it;
- the result of the draft check (clean, or the items the person chose to finish later);
- the person's own review apps from the viewer interview, when it was answered: "your review apps (this machine only, not committed)" and the file it goes to (see viewer.md);
- every file this phase will create or change —
.claude/config/stack.yaml,AGENTS.md,CLAUDE.md, the human-facingREADME.md(orREADME.ticketwright.mdif a README already exists — never overwritten),.claude/settings.json,.gitignore, the AI-layer index, the seeded ticket index; - on a repo that already has config, exactly what would change —
role/team/policiesare edit-never-overwrite (see above), so name the specific keys being edited and never present a whole-file overwrite as the plan.
Only on explicit confirmation, execute — source of truth first, derived files after:
5. Write .claude/config/stack.yaml per stack.schema.md (chosen tool slots live; optional ones as
commented blocks; the 11 policies with a one-line "when to change this" comment each) as the
FIRST write. Step 6 renders FROM stack.yaml, and some runtimes
refuse every write under .claude/ in a non-interactive run — a categorical path guard (the
refusal calls the file "sensitive") that no allow rule lifts. If the stack.yaml write is
refused, STOP: scaffold nothing else — an AGENTS.md describing a stack that does not exist on
disk is worse than an empty repo. Report the refusal text verbatim; that the runtime's .claude/
guard is the cause, not a Ticketwright error; the two ways forward — approve the prompt in an
interactive session, or create the .claude/ files by hand from the printed plan (print their
full contents on request); and that everything else is written on the next confirm. Never route
around the guard.
5a. Write .claude/settings.json as its own, announced step. Its contents come from
scaffold.md: the hooks (omitted on a plugin install), the read-only command
allows, the status line, and on a plugin install the plugin enablement with autoUpdate. Claude
Code's Auto mode treats a session writing its own project's .claude/settings.json as
self-modification and blocks it, so most installers meet a prompt or a refusal here. Never let
that arrive unexplained: say this first, in these words, then make the one write:
<!-- ticketwright:user-facing:begin -->Next I'll update .claude/settings.json. It lets read-only commands run without asking you each time, turns on automatic Ticketwright updates for this repo, and adds a status line. Claude Code may ask you to approve this change, or block it in Auto mode. Either answer is fine: nothing breaks without it, and I'll give you the exact text to paste if it's blocked.
<!-- ticketwright:user-facing:end -->If the write is refused or blocked, do not retry it and do not reach the file another way (a
shell redirect, cp, a script, settings.local.json). Carry on with step 5b; the scaffold does
not depend on this file. Keep the full merged contents you meant to write, because the closing
report (step 8) prints them as the paste-in fallback. The same applies to the
.claude/statusline.sh copy scaffold.md asks for on a plugin install: if it was
refused too, the fallback adds the copy command.
5b. Confirm the launcher is in the project, and add it to what gets committed. The
"Ensure the launcher" preflight above already installed bin/tw + bin/kit_paths.py (it runs
first in every mode and is idempotent — do NOT re-run a bare cp here: on a vendored or init
install the kit root IS the project root, so cp "$K/tools/tw" bin/ is a file onto itself, which
exits 1 and would halt the scaffold on the one install shape that already worked). Just verify
and carry both files into step 9's commit list:
bash "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" --kitCheck: that prints a kit path. If it errors, stop — step 6 renders commands that all route
through this launcher.
5c. Write the person's review apps, when the viewer interview after round 6 was answered: step 5
of viewer.md (it reads the current plan, writes the gitignored file, and checks it).
It runs after 5b because the check goes through the launcher. A skipped interview writes nothing
here; the punch list in step 8 names /setup viewer.
6. Scaffold the rest per scaffold.md: render AGENTS.md (+ role focus) and a one-line
CLAUDE.md (@AGENTS.md, so Claude Code auto-loads the rules), the human-facing README.md
(rendered to README.ticketwright.md instead if a README already exists — never overwritten),
folders, .gitignore (deliverable CSVs committed by default; PII opts out via *.private.csv / a
private/ subfolder), the AI-layer index, and the seeded ticket index.
Note that the round-1 whoami --bind machine pin (.claude/config/connections.local.yaml) sits
under the same guard: in a headless run that refusal lands during the interview, before this
halt — record it and list the pin with the deferred .claude/ files rather than stopping there.
Phase 4 — Verify & hand off
- Check that each tool connects, and say which check is which. Open with one line: "Now I'll
check that each of your tools answers."
- The kit itself was already checked in Phase 1: the doctor's
install_payloadconfirmed the installed plugin has every file setup and the first ticket open. That is a completeness check, not a content check; the released kit is tested before it ships. The kit's full self-check,!TW_KIT="${CLAUDE_PLUGIN_ROOT}"; bash "${TW_KIT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/tools/selftest.sh"(1,400+ assertions, several minutes, needsyq), is not run by default. Offer it only on a vendored or edited kit; when it runs, run it in the background or with a raised timeout and judge it by its exit code, never by whether the tail of its output looks green. !TW_KIT="${CLAUDE_PLUGIN_ROOT}"; bash "${TW_KIT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/tools/verify_stack.sh" .claude/config/stack.yaml— your repo's stack reachability (pass the repo stack path explicitly so it's unambiguous which config was checked). An unreachable tool slot is not fatal at setup time; print its adapter's auth notes as the fix.- …and the posture pointers that check prints (advisory, never blocking): verify_stack
emits a
▸ <slot> ... connects through MCPline (also connectswhen the slot has a terminal route too) for every tool slot whose resolved transport includes MCP, naming that adapter's "Permission posture (MCP)" section. For each pointer: run the read-only probe thatbash "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" adapter_surface.py --adapter <path> --probeprints (the probe written for this agent's tool names), apply the adapter's comparison rule, and record the outcome in the report AND in the gitignored record file.claude/config/posture.local.yaml(teammate.md step 5 defines the three outcome words and the record shape;matchesis writable only when the adapter's comparison rule held). Chat/tracker slots always record the introspection cap — a connector's grant set cannot be read from inside a session — with a pointer to where a human confirms the grant. A posture the policy does not expect is reported and recorded, not a stop. The outcome words and the record file are for the record, not for the person: never print the outcome words (the three status names teammate.md defines) or the file's name in the report. Say what they mean instead, as step 8 shows.
- The kit itself was already checked in Phase 1: the doctor's
- Report: lead with success, then the one next command, then optional follow-ups. The person
reading this may have no context on Ticketwright, so the report opens with what worked and what
to type next, and nothing technical comes before that. Use the plugin's command names on a
plugin install (
/ticketwright:ticket,/ticketwright:setup role) and the bare names (/ticket,/setup role) on a vendored orticketwright initinstall. The shape, in order:- The state, then the next command. When every tool connected or was deliberately left for
later:
<!-- ticketwright:user-facing:begin -->You're ready for your first ticket. Start one with/ticketwright:ticket <ID>, using a ticket from your tracker (for exampleTEST-14).<!-- ticketwright:user-facing:end -->When something the first ticket needs is not working yet (a tool that did not connect, a sign-in still pending), lead with "Almost ready for your first ticket: N things to finish first", then those items, each with the exact command or step that finishes it, then the same next command. - Optional follow-ups, under the heading "Optional, when you have a minute". One entry per
item that applies, each saying in plain words what it is, why it matters, and the command or
step that finishes it. Never an internal name (a round number, a policy key, a status word,
a file the person never opens) without saying what it means. These are the entries, in
these words:
- The settings file, when step 5a was blocked. Say the write was blocked by Claude Code
and nothing breaks without it, say in one line what each addition does, then give the file
path and the exact JSON, as one fenced block holding the WHOLE file setup meant to write
(the merged result, so pasting it replaces the file cleanly):
<!-- ticketwright:user-facing:begin -->Claude Code blocked me from changing its own settings file, which it does in Auto mode. Nothing breaks without it. Adding it gives you:- Fewer approval prompts: read-only commands (like
git statusand your tools' listing commands) run without asking you each time. - Automatic updates: everyone on this repo gets new Ticketwright releases when they come out.
- A status line: the bottom of Claude Code shows your ticket prefix, your branch and your tools.
To add it, open
.claude/settings.jsonin this repo and replace everything in it with the text below.
<!-- ticketwright:user-facing:end -->When the.claude/statusline.shcopy was refused too, add one line: runcp "$(bash bin/tw --kit)/.claude/statusline.sh" .claude/statusline.shin a terminal at the repo root, so the status line has its script. - Fewer approval prompts: read-only commands (like
- Skipped domain and standards (round 5):
<!-- ticketwright:user-facing:begin -->You skipped describing your team's domain and standards, so the rules file uses a generic description ("data analysis"). Run/ticketwright:setup roleto add them; it takes about two minutes.<!-- ticketwright:user-facing:end --> - Skipped house rules (round 6): "You kept the default house rules: which database
changes ask you first, when results open for your sign-off, and whether shipping merges the
pull request. Run
/ticketwright:setup policiesto choose your own." - Skipped review apps: "You didn't pick which apps open your results for review, so
Ticketwright will print file paths instead. Run
/ticketwright:setup viewerto choose." - A key left for later (
# TODO). Say what the value is and when a team needs it before naming it, then the step that sets it. The sensitive-data question (interview question 7a) is always asked, so the report never asks the person whether they have personal data: whenproject.sensitive_data.statusisnoneorunsureit raises no PII follow-up at all. Only when the answer was "Yes" and the approved role was left for later:<!-- ticketwright:user-facing:begin -->You said your data includes personal information, but the role approved for reading it isn't set yet. Queries switch to that role before they run. To set it, edit thepii_role:line in.claude/config/stack.yaml, or run/ticketwright:setup tool warehouse.<!-- ticketwright:user-facing:end -->A repo configured before the question existed (nosensitive_data:at all) gets one entry instead: "Setup hasn't asked whether your data includes personal information. Run/ticketwright:setup tool warehouseto answer it." - Connector permissions setup can't read (any tool step 7 recorded as not checkable):
<!-- ticketwright:user-facing:begin -->Setup can't see a connector's permission grant from inside a session. To check what your Slack or Jira connector is allowed to do, open the connector in the Claude app.<!-- ticketwright:user-facing:end -->Name the real tools for this repo. When a probe found more access than the house rules expect, say what it found and where that permission is changed, in one line.
- The settings file, when step 5a was blocked. Say the write was blocked by Claude Code
and nothing breaks without it, say in one line what each addition does, then give the file
path and the exact JSON, as one fenced block holding the WHOLE file setup meant to write
(the merged result, so pasting it replaces the file cleanly):
- Good to know, one line each, only when they apply:
- Email, when round 4 set it up: email goes out as drafts only, from the sending
address chosen in round 4, to whoever is chosen for each result when the work is shipped;
nothing sends until the person says send. A repo still carrying an older commented
seams.chat.targets.emailblock (configured but not yet activated) gets interview.md's offer to activate it as a per-ticket target; the standing-destination alternative isstack.example.multi-audience.yaml. project.assignee_dir, in one line: what the key is for, not a warning. It is the last-resort owner folder undertickets/for a repo with nopeople/roster. Once a roster exists, the owner of new work is whoeverwhoamiresolves, and a person it does not recognize is a stop (/setup --teammate) rather than a fall-back to this key. Say that, and never describe it as something setup is refusing to touch.- Obsidian, never a question. Detected: "this repo already opens as an Obsidian vault;
the ticket graph is in
tickets/graph/andtickets/objects/." Not detected: the graph files still get written; print the guide as its full GitHub URL, https://github.com/kyle-chalmers/ticketwright/blob/main/docs/obsidian.md, never as a baredocs/obsidian.mdpath, becausedocs/does not ship in the PyPI package. - Personal data, when question 7a was "Yes": results that hold it go in a
*.private.csvfile or aprivate/folder so git skips them, and messages carry a summary and a link rather than the data. - A new teammate starts with
/ticketwright:setup --teammatein their own clone.
- Email, when round 4 set it up: email goes out as drafts only, from the sending
address chosen in round 4, to whoever is chosen for each result when the work is shipped;
nothing sends until the person says send. A repo still carrying an older commented
- What was set up, last and short: the tools chosen for each job in the person's words, the
files written, and which check was which ("the install check confirmed Ticketwright's own
files are all there; the tool check confirmed your tools answer"). Never the words "seam",
"adapter", "draft check" or "connector id"; say "tool", "the tool's setup notes", "the
settings check before writing" and "the connector's name in Claude Code".
Deferring must stay trackable: every skipped round (
/setup rolefor round 5,/setup policiesfor round 6,/setup viewerfor the review apps) and every# TODOkey appears in step 2's list, so "took defaults" is never indistinguishable from "chose" (this list is the punch list the interview's skip rule promises).
- The state, then the next command. When every tool connected or was deliberately left for
later:
- Offer to commit the scaffold, even when some items are still to finish (a pending login or a
tracker project someone still has to create is not a reason to leave the scaffold uncommitted). What setup just wrote (
.claude/config/stack.yaml,AGENTS.md,CLAUDE.md,.claude/settings.json,.gitignore,documentation/AI_LAYER_INDEX.md, the seededtickets/index,bin/tw+bin/kit_paths.py— plus, on a vendored install, the kit itself) is untracked; the launcher pair is not optional: every command inAGENTS.mdand every reference file routes through it, so a clone without those two files gives the next teammate "No such file or directory" on their first command; if it isn't committed, a later ticket PR references rules/adapters absent from the repo's history. Offer a commit (e.g.chore: initialize ticketwright workspace). First say, in plain words, what a commit shares:stack.yamlnames things like your tracker's site address, the mailbox results are drafted from, and your warehouse project. None of it is a password, but on a public remote anyone can read it, so glance at it before pushing there. On a remote created empty (no default branch yet), this first push seedsmaindirectly — there is nothing to open a PR against — and the PR flow starts with the next change; say so rather than colliding with a never-push-to-main rule. When this session runs in a linked worktree (kit_paths.py --jsonreportsworktree.linked: true; agent apps often start sessions in one), the commit lands on the session's branch, not on<default branch>. Say so in plain words: the main checkout, other sessions and teammates see the setup only once that branch is merged, so offer to push it and open a pull request. On a repository with no commits yet, the first push seeds the default branch from the session branch (git push -u origin HEAD:<default branch>), so the remote's first branch is the default one and not the session's name.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

