ticketwright 4.0.0__tar.gz → 4.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/stack.schema.md +1 -1
  2. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/settings.json.tmpl +1 -1
  3. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/setup/SKILL.md +42 -3
  4. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/setup/interview.md +6 -0
  5. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/setup/scaffold.md +3 -2
  6. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/setup/teammate.md +25 -2
  7. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/ticket/SKILL.md +29 -17
  8. {ticketwright-4.0.0 → ticketwright-4.0.2}/CHANGELOG.md +73 -0
  9. {ticketwright-4.0.0 → ticketwright-4.0.2}/PKG-INFO +119 -13
  10. {ticketwright-4.0.0 → ticketwright-4.0.2}/README.md +118 -12
  11. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/effective_config.py +57 -0
  12. ticketwright-4.0.2/bin/plugin_doctor.py +669 -0
  13. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/selftest.sh +936 -9
  14. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/update_notice.py +147 -21
  15. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/verify_stack.sh +13 -1
  16. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/AGENTS.md.tmpl +86 -6
  17. {ticketwright-4.0.0 → ticketwright-4.0.2}/ticketwright/__init__.py +1 -1
  18. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/agents/qc-reviewer.md +0 -0
  19. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/commands/.gitkeep +0 -0
  20. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/connections.example.yaml +0 -0
  21. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/stack.example.asana-bq.yaml +0 -0
  22. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/stack.example.azure.yaml +0 -0
  23. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/stack.example.multi-audience.yaml +0 -0
  24. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/stack.example.multi-warehouse.yaml +0 -0
  25. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/stack.example.no-warehouse.yaml +0 -0
  26. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/stack.example.solo.yaml +0 -0
  27. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/stack.yaml +0 -0
  28. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/config/viewer.example.yaml +0 -0
  29. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/hooks/_stack.py +0 -0
  30. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/hooks/db_write_guard.py +0 -0
  31. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/hooks/regenerate_ticket_index.py +0 -0
  32. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/hooks/session_context.py +0 -0
  33. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/hooks/source_material_guard.py +0 -0
  34. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/hooks/ticket_index_context.py +0 -0
  35. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/refresh/SKILL.md +0 -0
  36. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/refresh/context-pack.md +0 -0
  37. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/refresh/index.md +0 -0
  38. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/review/SKILL.md +0 -0
  39. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/setup/adopt.md +0 -0
  40. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/setup/voice.md +0 -0
  41. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/ship/SKILL.md +0 -0
  42. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/skillify/SKILL.md +0 -0
  43. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/skillify/authoring.md +0 -0
  44. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/spec-and-build/SKILL.md +0 -0
  45. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/skills/ticket/priming.md +0 -0
  46. {ticketwright-4.0.0 → ticketwright-4.0.2}/.claude/statusline.sh +0 -0
  47. {ticketwright-4.0.0 → ticketwright-4.0.2}/.gitignore +0 -0
  48. {ticketwright-4.0.0 → ticketwright-4.0.2}/LICENSE +0 -0
  49. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/README.md +0 -0
  50. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/chat/gmail.md +0 -0
  51. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/chat/outlook.md +0 -0
  52. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/chat/slack.md +0 -0
  53. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/chat/teams.md +0 -0
  54. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/docstore/gdrive.md +0 -0
  55. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/docstore/rclone.md +0 -0
  56. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/docstore/sharepoint.md +0 -0
  57. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/meetings/fireflies.md +0 -0
  58. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/meetings/granola.md +0 -0
  59. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/meetings/notion.md +0 -0
  60. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/meetings/teams.md +0 -0
  61. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/meetings/zoom.md +0 -0
  62. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/runtime/antigravity.md +0 -0
  63. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/runtime/claude-code.md +0 -0
  64. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/runtime/cline.md +0 -0
  65. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/runtime/codex-cli.md +0 -0
  66. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/runtime/cursor.md +0 -0
  67. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/runtime/devin.md +0 -0
  68. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/runtime/opencode.md +0 -0
  69. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/tracker/asana.md +0 -0
  70. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/tracker/azure-devops.md +0 -0
  71. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/tracker/github-issues.md +0 -0
  72. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/tracker/jira.md +0 -0
  73. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/tracker/linear.md +0 -0
  74. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/tracker/local.md +0 -0
  75. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/tracker/monday.md +0 -0
  76. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/vcs/azure-repos.md +0 -0
  77. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/vcs/github.md +0 -0
  78. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/vcs/gitlab.md +0 -0
  79. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/viewer/macos-open.md +0 -0
  80. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/viewer/windows-start.md +0 -0
  81. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/viewer/xdg-open.md +0 -0
  82. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/warehouse/bigquery.md +0 -0
  83. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/warehouse/databricks.md +0 -0
  84. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/warehouse/postgres.md +0 -0
  85. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/warehouse/redshift.md +0 -0
  86. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/warehouse/snowflake.md +0 -0
  87. {ticketwright-4.0.0 → ticketwright-4.0.2}/adapters/warehouse/synapse.md +0 -0
  88. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/_yamlite.py +0 -0
  89. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/build_ticket_index.py +0 -0
  90. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/bump_version.sh +0 -0
  91. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/delivery_plan.py +0 -0
  92. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/emit_runtime.py +0 -0
  93. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/enrich_ticket.py +0 -0
  94. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/handoff.sh +0 -0
  95. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/hook_shim.py +0 -0
  96. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/ingest_index_records.py +0 -0
  97. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/install.sh +0 -0
  98. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/kit_paths.py +0 -0
  99. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/meeting_refs.py +0 -0
  100. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/opencode_tool_gate.js +0 -0
  101. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/recall.py +0 -0
  102. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/render.sh +0 -0
  103. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/render_and_validate.sh +0 -0
  104. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/resolve_user.py +0 -0
  105. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/scan_source_materials.py +0 -0
  106. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/split_and_export.sh +0 -0
  107. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/sql_scan.py +0 -0
  108. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/tw +0 -0
  109. {ticketwright-4.0.0 → ticketwright-4.0.2}/bin/whoami.py +0 -0
  110. {ticketwright-4.0.0 → ticketwright-4.0.2}/pyproject.toml +0 -0
  111. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/CLAUDE.md.tmpl +0 -0
  112. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/generated-skill/SKILL.md.tmpl +0 -0
  113. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/generated-skill/bin/drift_check.sh +0 -0
  114. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/generated-skill/golden/example.json +0 -0
  115. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/generated-skill/sql/qc.sql.tmpl +0 -0
  116. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/generated-skill/sql/step.sql.tmpl +0 -0
  117. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/generated-skill/templates/README.md.tmpl +0 -0
  118. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/generated-skill/templates/tracker_comment.txt.tmpl +0 -0
  119. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/gitignore.tmpl +0 -0
  120. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/person.yaml.tmpl +0 -0
  121. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/plan.md.tmpl +0 -0
  122. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/project-README.md.tmpl +0 -0
  123. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/roles/analyst.md +0 -0
  124. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/roles/engineer.md +0 -0
  125. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/roles/generalist.md +0 -0
  126. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/roles/scientist.md +0 -0
  127. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/spec.md.tmpl +0 -0
  128. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/ticket-README.md.tmpl +0 -0
  129. {ticketwright-4.0.0 → ticketwright-4.0.2}/templates/voice-profile.md.tmpl +0 -0
  130. {ticketwright-4.0.0 → ticketwright-4.0.2}/tests/emit/README.md +0 -0
  131. {ticketwright-4.0.0 → ticketwright-4.0.2}/tests/meeting_refs/README.md +0 -0
  132. {ticketwright-4.0.0 → ticketwright-4.0.2}/tests/source_materials/README.md +0 -0
  133. {ticketwright-4.0.0 → ticketwright-4.0.2}/ticketwright/cli.py +0 -0
@@ -114,7 +114,7 @@ policies: # behavioral rules every skill inherits (the kit's "global rules
114
114
  | `key_prefix` | string | `ENG` | Ticket-ID prefix. Branch names = `{key_prefix}-NNNN`. Omit it when `id_mode: slug` — there are no keys to prefix. |
115
115
  | `id_mode` | enum | `keyed` | `keyed` (default) = folder names carry a tracker key; `slug` = the folder name **is** the id. See "Trackerless work". |
116
116
  | `key_prefixes` | list | `[ENG]` | Prefixes the ticket index recognizes in folder names. Optional; defaults to `[key_prefix]`. Use when one repo holds tickets from several trackers (e.g. `[ENG, OPS]`). |
117
- | `assignee_dir` | string | `alice` | Last-resort owner subdir under `tickets/`, used only when no people map exists (`bin/whoami.py` returns `miss` and there are no `people/*.yaml`). With a people map, the whoami-resolved person is the owner. |
117
+ | `assignee_dir` | string | `alice` | Last-resort owner subdir under `tickets/`, used only when **no people map exists at all** (`bin/whoami.py` returns `miss` and there are no `people/*.yaml`). With a people map, the whoami-resolved person is the owner, and an unresolved person is a **hard stop** — never this key, which would file a new teammate's work in a colleague's folder. `effective_config.py` reports which case applies as `owner_source` (`resolved` / `unbound` / `assignee_dir_fallback` / `none`) and the answer as `owner`. |
118
118
  | `ticket_path` | template | `tickets/{assignee}/{id}` | Where a ticket folder lives. `{assignee}` `{id}` tokens; `{assignee}` is the ticket's **owner** — the whoami-resolved person for new work, or the locator's owner (`owner/id`) when named explicitly. |
119
119
  | `ticket_subdirs` | list | `[source_materials, final_deliverables, qc_queries, exploratory_analysis]` | Scaffolded per ticket. |
120
120
  | `default_epic` | string \| null | `ENG-100` | Parent epic for newly created tickets (null if tracker has no epics). |
@@ -1,5 +1,5 @@
1
1
  {
2
- "_README": "Rendered to .claude/settings.json by /setup. The hooks block below is for a VENDORED (cp -r) install, where nothing else wires the kit's hooks. On a PLUGIN install (CLAUDE_PLUGIN_ROOT set), .claude-plugin/plugin.json already wires these same hooks from the plugin dir, so /setup OMITS this hooks block to avoid double-firing (double db-write prompts, double index regen) AND MERGES an 'extraKnownMarketplaces' (ticketwright source {source:'git', url:'https://github.com/kyle-chalmers/ticketwright.git'} — the discriminator `claude plugin marketplace add` writes itself; 'git' and 'url' are different source types — plus autoUpdate:true) + 'enabledPlugins' (ticketwright@ticketwright:true) block so the REPO opts every user into the plugin at PROJECT scope (a plugin cannot set its own install scope — the repo does; teammates are prompted to install), committed with the repo, and self-updates on formal releases only (version-string change; never un-released main commits). The documented install already writes most of that block via `--scope project` on both `marketplace add` and `plugin install` (everything except autoUpdate), so setup MERGES into those two maps — preserving unrelated entries, an existing ticketwright source (a fork edits that URL), and any explicit autoUpdate:false — rather than overwriting. Do NOT add those two keys on a vendored install — there is no marketplace to enable from. permissions + statusLine are written in both modes (setup appends stack-specific CLI allows); on a plugin install setup copies statusline.sh into .claude/ so the relative command resolves. The db_write_guard hook turns 'db_write_requires_approval' into a mechanical ask before any destructive warehouse statement; the source_material_guard hook turns 'source_material_guard' into a mechanical ask before a raw meeting transcript is staged for commit or copied into a docstore backup.",
2
+ "_README": "Rendered to .claude/settings.json by /setup. The hooks block below is for a VENDORED (cp -r) install, where nothing else wires the kit's hooks. On a PLUGIN install (CLAUDE_PLUGIN_ROOT set), .claude-plugin/plugin.json already wires these same hooks from the plugin dir, so /setup OMITS this hooks block to avoid double-firing (double db-write prompts, double index regen) AND MERGES an 'extraKnownMarketplaces' (ticketwright source {source:'git', url:'https://github.com/kyle-chalmers/ticketwright.git'} — the discriminator `claude plugin marketplace add` writes itself; 'git' and 'url' are different source types — plus autoUpdate:true) + 'enabledPlugins' (ticketwright@ticketwright:true) block so the REPO opts every user into the plugin at PROJECT scope (a plugin cannot set its own install scope — the repo does; registering the marketplace is not installing the plugin, so teammates must still run the two --scope project install commands themselves, README Track 2), committed with the repo, and self-updates on formal releases only (version-string change; never un-released main commits). The documented install already writes most of that block via `--scope project` on both `marketplace add` and `plugin install` (everything except autoUpdate), so setup MERGES into those two maps — preserving unrelated entries, an existing ticketwright source (a fork edits that URL), and any explicit autoUpdate:false — rather than overwriting. Do NOT add those two keys on a vendored install — there is no marketplace to enable from. permissions + statusLine are written in both modes (setup appends stack-specific CLI allows); on a plugin install setup copies statusline.sh into .claude/ so the relative command resolves. The db_write_guard hook turns 'db_write_requires_approval' into a mechanical ask before any destructive warehouse statement; the source_material_guard hook turns 'source_material_guard' into a mechanical ask before a raw meeting transcript is staged for commit or copied into a docstore backup.",
3
3
  "hooks": {
4
4
  "PreToolUse": [
5
5
  {
@@ -130,6 +130,34 @@ which prints the resolved commands without launching anything. Never commit this
130
130
  ## Default mode — configure the repo
131
131
 
132
132
  ### Phase 1 — Detect (no questions yet)
133
+ 0. **Preflight — the ways a run is doomed before it starts.** Both probes are read-only and cheap;
134
+ run them before anything else, because neither failure is fixable by anything this skill writes.
135
+ - **Is this a clone?** `!git rev-parse --show-toplevel` — a non-zero exit means there is no
136
+ repository here. **Halt**, and say why: "There is no `.git` in this folder. If it came from a
137
+ Download-ZIP button (the folder name usually ends in `-main`), Ticketwright cannot branch,
138
+ commit, or open a PR from it, and no config would fix that. Clone the repo instead —
139
+ `git clone <the repository's URL>` — then re-run `/setup` inside the clone."
140
+ - **Is the kit installed and usable here?**
141
+ `!python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/plugin_doctor.py" --json` — one
142
+ read-only pass over the install prerequisites (it never installs anything, and makes no
143
+ network call). Report **every** finding that is not `ok`, each with its `fix` lines
144
+ **verbatim**: they are exact commands, and a paraphrased command is one nobody can run.
145
+ A `fail` on any of these three is a **halt**, because nothing setup writes reaches the cause:
146
+ - `scope_supported` — the agent CLI is too old to install a plugin for one repo; the fix names
147
+ both the in-session install route and the update command for how it was installed;
148
+ - `repo_install` — nothing is installed for this repository;
149
+ - `install_payload` — the install record points at a directory that does not exist.
150
+ Everything else (`warn`, `unknown`, a global-only install, a stale catalog) is reported and
151
+ the run continues. On a non-plugin install — a vendored kit, a pip install — the install
152
+ checks answer `unknown`: that is the expected answer there, not a failure. If the script is
153
+ absent (an older kit copy), say so in one line and carry on.
154
+ - **`yq`** — when `yq_present` is not `ok`, Phase 4's kit-integrity check cannot run at all.
155
+ Print the platform install command the doctor gives, and **offer to run it now**, while it is
156
+ still cheap — not at the moment the check fails. Say in the same breath what that check costs:
157
+ it runs 1,200+ assertions over several minutes, longer than a typical tool timeout, so give it
158
+ the background or a raised timeout and judge it by its **exit code**, never by whether the
159
+ tail of its output looks green.
160
+ Identity routing is unchanged: it happens at step 4 below.
133
161
  1. **CLIs:** `!for c in snow acli gh glab bq databricks yq jq git rclone; do command -v $c >/dev/null && echo "✓ $c" || echo "– $c"; done`
134
162
  2. **MCP servers** connected this session (tracker / chat / warehouse servers).
135
163
  3. **Repo facts — probes, not questions.**
@@ -140,7 +168,9 @@ which prints the resolved commands without launching anything. Never commit this
140
168
  confirms instead of asking. The default branch comes from
141
169
  `git symbolic-ref refs/remotes/origin/HEAD` — **not** `git symbolic-ref HEAD`, which reports
142
170
  whatever branch happens to be checked out and would misconfigure any setup run from a
143
- feature branch.
171
+ feature branch. On a repo nothing has been pushed to yet that probe exits 128 ("not a
172
+ symbolic ref") — fall back to the current local branch and confirm it in prose; never leave the
173
+ agent to improvise on a failed probe.
144
174
  - **Obsidian:** installed or not (e.g. `command -v obsidian`, or the OS application folder).
145
175
  Never a question — `graph_notes`/`graph_config` already default correctly; this only decides
146
176
  which one-liner the Phase-4 report prints (step 8).
@@ -258,7 +288,9 @@ Only on explicit confirmation, execute — **source of truth first, derived file
258
288
  7. **Two distinct checks — keep them labeled as such in the report:**
259
289
  - `!bash "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/selftest.sh"` — **kit integrity**. It
260
290
  validates the plugin's *own bundled example* stacks, **not** your repo's config. A failure here
261
- is fatal.
291
+ is fatal. It runs 1,200+ assertions and takes several minutes — longer than a typical
292
+ tool timeout, so run it in the background or with a raised timeout, and judge it by its exit
293
+ code (non-zero on any failure), not by whether the tail of its output looks green.
262
294
  - `!bash "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/verify_stack.sh" .claude/config/stack.yaml`
263
295
  — **your repo's stack** reachability (pass the repo stack path explicitly so it's unambiguous
264
296
  which config was checked). An unreachable tool slot is **not** fatal at setup time; print its
@@ -278,6 +310,11 @@ Only on explicit confirmation, execute — **source of truth first, derived file
278
310
  - **The punch list** — one entry per skipped round, each naming its re-entry command
279
311
  (`/setup role` for round 5, `/setup policies` for round 6). Deferring must be trackable;
280
312
  "took defaults" must never be indistinguishable from "chose".
313
+ - **`project.assignee_dir`, in one line — what the key is for, not a warning.** It is the
314
+ last-resort owner subdir under `tickets/` for a repo with no `people/` roster. Once a roster
315
+ exists, the owner of new work is whoever `whoami` resolves, and a person it does not
316
+ recognize is a stop (`/setup --teammate`) rather than a fall-back to this key — so say that,
317
+ and never describe it as something setup is refusing to touch.
281
318
  - **Email, when round 4 recorded delivery:** say plainly that email is **configured but not
282
319
  yet activated** — the commented target block holds the answers, and nothing will send until
283
320
  someone deliberately activates it: convert the chat slot to `targets:` form with a declared
@@ -296,4 +333,6 @@ Only on explicit confirmation, execute — **source of truth first, derived file
296
333
  ticket PR references rules/adapters absent from the repo's history. Offer a commit (e.g.
297
334
  `chore: initialize ticketwright workspace`). First flag that `stack.yaml` may hold internal
298
335
  identifiers (tracker site, warehouse project/dataset) — config, not secrets, but worth a glance
299
- before committing to a public repo.
336
+ before committing to a public repo. On a remote created empty (no default branch yet), this
337
+ first push seeds `main` directly — there is nothing to open a PR against — and the PR flow
338
+ starts with the next change; say so rather than colliding with a never-push-to-main rule.
@@ -21,6 +21,12 @@ it in later" is always a valid answer: write the key as a `# TODO` and keep goin
21
21
  `verify_stack.sh` names every unset required key as a warning on every run, so a deferred key is a
22
22
  tracked choice, not a silent hole.
23
23
 
24
+ One key deserves a precision note wherever it is asked: a slot's **`mcp:` value must be the
25
+ runtime's exact tool prefix** — the `<name>` in `mcp__<name>__*` — because adapters render tool
26
+ names from it. Tell the person to copy it from a tool name they can see, not from a UI label; a
27
+ friendly name that matches no connected server makes every rendered tool name point at nothing,
28
+ and nothing errors until a skill tries to call one.
29
+
24
30
  Never promise a question count, in this file or in any report — a stated number becomes the
25
31
  binding constraint on correctness, which is exactly the defect this round structure replaced.
26
32
 
@@ -79,8 +79,9 @@ Render `${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/.claude/settings.json.tmpl`
79
79
  **On a plugin install, also commit a project-scoped enablement.** A plugin can't set its own install
80
80
  scope — the *repo* opts in. Merge these two keys
81
81
  into the rendered `.claude/settings.json` so the plugin is enabled *for this repo* (committed →
82
- travels with the repo; teammates who open and trust it are prompted to install it; it survives the
83
- original author leaving) and refreshes
82
+ travels with the repo; it survives the original author leaving). Registering the marketplace is not
83
+ installing the plugin — a teammate who opens and trusts the repo gets the marketplace clone, then
84
+ runs the install themselves (Track 2). It also refreshes
84
85
  itself:
85
86
 
86
87
  ```json
@@ -38,11 +38,34 @@ commits `/ship` makes. If either is unset, have the person set them now — repo
38
38
  re-run step 0): the identity recorded is now one this machine actually resolves by.
39
39
 
40
40
  ## 1 · Install prerequisites
41
- Check what's present: `for c in <the CLIs named by the configured tool slots> yq jq git; do
41
+ **First, the two that make everything after them pointless.** Both probes are read-only:
42
+
43
+ - **Is this a clone?** `!git rev-parse --show-toplevel` — a non-zero exit means there is no
44
+ repository here. **Halt**: "There is no `.git` in this folder. If it came from a Download-ZIP
45
+ button (the folder name usually ends in `-main`), Ticketwright cannot branch, commit, or open a
46
+ PR from it. Clone the repo instead — `git clone <the repository's URL>` — then re-run this
47
+ inside the clone."
48
+ - **Is the kit installed and usable here?**
49
+ `!python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/plugin_doctor.py" --json` — a
50
+ read-only pass over the install prerequisites; it installs nothing and makes no network call.
51
+ Report every finding that is not `ok` with its `fix` lines **verbatim** — they are exact
52
+ commands. A `fail` on `scope_supported` (the agent CLI is too old to install a plugin for one
53
+ repo), `repo_install` (nothing is installed for this repository) or `install_payload` (the
54
+ install record points at a directory that does not exist) is a **halt**: none of it is fixable
55
+ by anything this flow writes, and the fix text names the route out — including the in-session
56
+ install and the update command for how the CLI was installed. `warn`/`unknown` findings are
57
+ reported and the flow continues; on a vendored or pip install the install checks answer
58
+ `unknown`, which is the expected answer there. If the script is absent (an older kit copy), say
59
+ so in one line and carry on.
60
+
61
+ Then check the rest: `for c in <the CLIs named by the configured tool slots> yq jq git; do
42
62
  command -v $c >/dev/null && echo "✓ $c" || echo "✗ $c (install)"; done`. For each missing tool give
43
63
  the install command (Homebrew / winget / apt as appropriate). Scope note: `yq` is needed only by
44
64
  the kit's own `selftest.sh` (step 5 runs it once, as a kit-integrity check) — no skill in a
45
- configured repo invokes it day to day, so a missing `yq` blocks that one check, nothing else.
65
+ configured repo invokes it day to day, so a missing `yq` blocks that one check, nothing else. When
66
+ `yq_present` is not `ok`, print the doctor's platform install command and **offer to run it now**,
67
+ before step 5 needs it — and say what step 5 costs: several minutes, longer than a typical tool
68
+ timeout, judged by its **exit code**, not by how green the tail of its output looks.
46
69
 
47
70
  ## 2 · Authenticate each configured tool
48
71
  For every configured tool slot, open its adapter's `auth:` notes and walk the person through
@@ -18,19 +18,30 @@ brief, nothing else. Follow [priming.md](priming.md) § Recall. Useful mid-sessi
18
18
  this before?", "which tickets touched VW_X?").
19
19
 
20
20
  ## Phase 0 — Resolve & preflight (halt-on-fail)
21
- 0. **Resolve WHO first** — `bash "${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/bin/tw" whoami.py`
22
- and show its one-line "Working as …" display. This person is the default owner for everything
23
- below. By status:
24
- - `resolved` — proceed; new work belongs to this person.
25
- - `miss` with a people map present (any `people/*.yaml`) — ask once ("I don't recognize
26
- `<identity>`. Who are you?"), then `whoami.py --bind <id>`; never infer from a name.
27
- - `miss` with **no people map at all** — fall back to `project.assignee_dir`, the documented
28
- last resort for repos that predate owner routing.
29
- - `ambiguous` — ask which person it is; never rank or pick.
30
- - `conflict` — proceed as the machine-pinned person and surface the warning line verbatim.
31
- 1. Read the merged config — `bash "${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/bin/tw" effective_config.py --json`,
32
- never raw `stack.yaml`: `project.*`, `seams.tracker`, `seams.vcs`. If the team config is
33
- missing, say so and offer `/setup` — don't scaffold blind.
21
+ 0. **Read the merged config first — it is what decides WHO** — `bash "${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/bin/tw" effective_config.py --json`,
22
+ never raw `stack.yaml`. It carries `project.*`, `seams.tracker`, `seams.vcs` — and it settles the
23
+ owner **mechanically**, in one field, so no skill re-derives the rule. If the team config is
24
+ missing, say so and offer `/setup` — don't scaffold blind. Branch on **`owner_source`**, never on
25
+ a `people/` listing of your own: a roster can live entirely in
26
+ `$XDG_CONFIG_HOME/ticketwright/people/` and never appear in this repo, and only the resolver
27
+ looks in both homes — "no `people/*.yaml` here" is not the same question.
28
+ - `resolved` — proceed; `owner` is this person and owns everything below.
29
+ - `unbound` — **HARD STOP here, before any ticket path is rendered.** Say:
30
+ "I can't tell who you are: `people/<id>.yaml` is a placeholder (or no `people/` entry matches
31
+ this machine's identity). Run `/setup --teammate` first — it asks who you are, binds it, and
32
+ sets up your machine." Do **not** fall back to `project.assignee_dir`: with a roster present
33
+ that key names a COLLEAGUE, and their `tickets/<owner>/` folder is exactly where an unbound
34
+ teammate's work silently lands. Never infer an owner from a name.
35
+ - `assignee_dir_fallback` — proceed with `owner`: there is no roster in either home, so
36
+ `project.assignee_dir` is the documented last resort for repos that predate owner routing.
37
+ - `none` — stop: no roster and no `assignee_dir`. Offer `/setup`.
38
+ Read `owner`, never `project.assignee_dir` directly.
39
+ 1. **Then show who that is** — `bash "${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}/bin/tw" whoami.py`,
40
+ and show its one-line "Working as …" display. This is the display-and-binding step; the routing
41
+ decision was already made in step 0, and whoami never overrides it. Two statuses still change
42
+ what you do next: `ambiguous` — ask which person it is; never rank or pick, and step 0's hard
43
+ stop stands until they answer. `conflict` — proceed as the machine-pinned person and surface the
44
+ warning line verbatim.
34
45
  2. **Verify** the tracker + vcs seams (run their `verify` commands). If one is unreachable, print
35
46
  that adapter's `auth` notes and offer to continue **local-only** (workspace + context still work;
36
47
  tracker fetch is skipped) — degrade, don't die.
@@ -44,10 +55,11 @@ this before?", "which tickets touched VW_X?").
44
55
  ## Phase 1 — Resume, don't restart
45
56
  4. Render `project.ticket_path` → the ticket dir, filling `{assignee}` with the **locator's owner**
46
57
  (the step-0 resolved person for new or bare-id work — never the static `project.assignee_dir`,
47
- except through step 0's no-people-map last resort). If the dir exists: read its `README.md`, list
48
- `final_deliverables/`, check `git log --oneline -10` + `git status`, summarize what's done and
49
- what remains, re-fetch the ticket for new comments, then **skip to Phase 3**. Resuming another
50
- person's ticket (locator owner ≠ resolved person) is fine — say so out loud.
58
+ except through step 0's `assignee_dir_fallback` last resort). If the dir exists: read its
59
+ `README.md`, list `final_deliverables/`, check `git log --oneline -10` + `git status`, and
60
+ summarize what's done and what remains, re-fetch the ticket for new comments, then **skip to
61
+ Phase 3**. Resuming another person's ticket (locator owner ≠ resolved person) is fine — say so
62
+ out loud.
51
63
 
52
64
  ## Phase 2 — Workspace (new ticket)
53
65
  5. **Branch** via the vcs adapter, named `<id>`, off `seams.vcs.default_branch` — or a **worktree**
@@ -4,6 +4,79 @@ All notable changes to this project are documented here. Format loosely follows
4
4
  [Keep a Changelog](https://keepachangelog.com/); this project uses semantic-ish versioning.
5
5
 
6
6
 
7
+ ## [4.0.2] — 2026-09-03
8
+
9
+ ### Added
10
+ - **`bin/plugin_doctor.py` — every install state gets a name and a fix.** Onboarding onto a repo that
11
+ already carries the committed enablement failed in ways nothing in the kit could describe: a CLI too
12
+ old to accept `--scope` (Claude Code 2.0.x has neither `--scope` nor `plugin list`), a marketplace
13
+ registered with nothing installed, a global install nobody meant, an install record whose
14
+ `installPath` was never created (the install reports success, re-running it is a no-op), and a
15
+ "Download ZIP" folder with no `.git`. The doctor checks all of that in one command — plus `yq`, git
16
+ identity, the update channel to update through, and whether the installed version is behind the
17
+ marketplace catalog — and prints the fix for each, human-readable or `--json`. It runs from the
18
+ marketplace clone before anything is installed
19
+ (`python3 ~/.claude/plugins/marketplaces/ticketwright/bin/plugin_doctor.py`, a fork substitutes its
20
+ own marketplace name) and through `bin/tw` afterwards. Stdlib only, no network, no writes; it never
21
+ runs an install, and it reuses `update_notice.py` so its upgrade fix is the same string the session
22
+ banner prints. `/setup` runs it at preflight and halts on the states nothing setup writes can fix.
23
+ - **Install prerequisites an agent can read before the plugin loads.** README Track 2 and
24
+ `templates/AGENTS.md.tmpl` carry the same fourteen checks in the same order as the doctor, each
25
+ tagged with a `doctor-check` marker; selftest pins the set and order against the doctor's own check
26
+ list, so the three surfaces cannot drift.
27
+
28
+ ### Fixed
29
+ - **The joiner install no longer lands at user scope.** README Track 2, `docs/troubleshooting.md` and
30
+ the rendered `AGENTS.md` all gave the bare `claude plugin install ticketwright@ticketwright`, which
31
+ defaults to `--scope user` — installing Ticketwright for every repo on the machine while the repo
32
+ itself got nothing. All three now give the `--scope project` pair, run from the repository root
33
+ (a project-scope install keys to the session's directory, claude-code#82830), with the in-app
34
+ `/plugin install` → Project-scope route as the fallback for a CLI too old for the flag.
35
+ - **The "teammates are prompted to install" claim is gone.** It was in the README, `setup/scaffold.md`,
36
+ `.claude/settings.json.tmpl` and `templates/AGENTS.md.tmpl`, and it is not what happens: registering
37
+ the marketplace is not installing the plugin, and the documented behavior (Claude Code 2.1.195 and
38
+ later report the plugin as not installed and show the install command; older builds say nothing) is
39
+ now what those four files say.
40
+ - **"Restart" says what it means.** Every restart instruction now names `/reload-plugins` or a new
41
+ session first, then a full quit of the Claude app (Cmd+Q, or File → Exit) if the skills are still
42
+ absent — a new chat inside the running app is not a restart.
43
+ - **`bin/update_notice.py` recognises a `local` install row** (the desktop app writes one) and emits
44
+ the uninstall/reinstall pair at the scope the record actually carries, instead of assuming
45
+ `--scope project`.
46
+ - **An unbound teammate no longer writes into someone else's folder.** A `people/<id>.yaml` placeholder
47
+ with no identities resolved to the project's `assignee_dir`, so a first ticket opened before
48
+ `/setup --teammate` landed under another person's directory. The ticket front door now halts and
49
+ names the fix.
50
+
51
+ ### Changed
52
+ - **`/setup` preflight order:** git repo → the doctor's findings → `yq` (with the platform install
53
+ command offered before the self-test needs it) → identity. A ZIP folder or a broken install is
54
+ reported before setup writes anything, since nothing setup writes would repair it.
55
+
56
+ ### Docs
57
+ - `docs/troubleshooting.md`: cause 2 is now "Your Claude Code CLI is too old" with per-channel update
58
+ commands; a new cause covers an install record with no payload, including the safety conditions on
59
+ the copy-the-clone recovery; the upgrade table gains a `local`-scope row; a new block names the three
60
+ ways to run the doctor.
61
+
62
+ ## [4.0.1] — 2026-09-02
63
+
64
+ ### Fixed
65
+ - **The kit-integrity selftest passes on a plugin install.** Section 31 ran `bin/tw` from inside the
66
+ kit's own tree and expected the kit root — right only when the kit is a git repo, so on a plugin
67
+ install (a cache, not a repo) two assertions failed for a reason no user ever meets. The test now
68
+ builds a git project and runs from a ticket subdirectory, which is the real scenario; `bin/tw`
69
+ itself was already correct there.
70
+ - **A verify can no longer hang setup.** `verify_stack.sh` runs each slot's verify under a wall-clock
71
+ cap (`VERIFY_TIMEOUT`, default 30s) and reports a timeout as "appears to need interactive auth"
72
+ — a browser-SSO warehouse connection used to block the whole check with no output.
73
+
74
+ ### Docs
75
+ - `/setup`: fall back to the current local branch when `refs/remotes/origin/HEAD` is unset (an
76
+ unpushed repo); expect `selftest.sh` to take several minutes and judge it by exit code; on a
77
+ remote created empty, the scaffold's first push seeds `main` and the PR flow starts with the next
78
+ change; a slot's `mcp:` value must be the runtime's exact tool prefix (`mcp__<name>__*`).
79
+
7
80
  ## [4.0.0] — 2026-09-02
8
81
 
9
82
  Breaking: `/productize` is now `/skillify`, and `templates/productized-skill/` is now
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ticketwright
3
- Version: 4.0.0
3
+ Version: 4.0.2
4
4
  Summary: Tool-agnostic AI layer for ticket-driven work repos: prior-art recall, object reverse-index, deep QC, and a self-maintaining ticket index. Stdlib-only, no vector store.
5
5
  Project-URL: Homepage, https://github.com/kyle-chalmers/ticketwright
6
6
  Project-URL: Repository, https://github.com/kyle-chalmers/ticketwright
@@ -169,9 +169,10 @@ personal install, or another repo's), `marketplace add … --scope project` prin
169
169
  declared in project settings" — expected, not an error: the cached marketplace is reused and the
170
170
  project-scoped declaration still lands in this repo's settings.
171
171
 
172
- **2 · Fully restart Claude Code** — quit and relaunch, not just a new prompt. Plugin skills load
173
- at session start, so installing and running `/setup` in the same session silently fails: the
174
- command simply doesn't exist yet.
172
+ **2 · Reload the plugin, or restart.** Plugin skills load at session start, so installing and
173
+ running `/setup` in the same session silently fails: the command simply doesn't exist yet. Run
174
+ `/reload-plugins` or start a new session. If the commands still do not appear, fully quit the Claude
175
+ app (Cmd+Q on macOS, File → Exit on Windows/Linux) and relaunch — a new chat is not a restart.
175
176
 
176
177
  *Check:* `/ticketwright:setup` shows up in the new session's command list.
177
178
 
@@ -253,20 +254,124 @@ start, but does NOT install the plugin.** Verified live: a teammate's `installed
253
254
  stayed `{}` across restarts until the manual install in step 2. Skip step 2 and
254
255
  `/ticketwright:setup` is not a command that exists.
255
256
 
256
- **1 · Clone the repo and open it in Claude Code** (trust the workspace when prompted).
257
+ #### If you are the agent helping someone install — read this first
258
+
259
+ One command answers most of what follows. The marketplace clone lands on disk as soon as the person
260
+ opens and trusts the repo — before anything is installed — so this runs at the moment nothing else
261
+ does:
262
+
263
+ ```bash
264
+ python3 ~/.claude/plugins/marketplaces/ticketwright/bin/plugin_doctor.py
265
+ ```
266
+
267
+ That path names the `ticketwright` marketplace; a fork substitutes its own marketplace name. The
268
+ doctor prints one line per check below, with the fix for each, and takes `--json` for a
269
+ machine-readable report. Once the plugin is installed the same checks run as
270
+ `bash "${CLAUDE_PLUGIN_ROOT:-.}/bin/tw" plugin_doctor.py`.
271
+
272
+ The checks, in the order the doctor runs them:
273
+
274
+ 1. **A clone, not a downloaded ZIP.** <!-- doctor-check: git_clone -->
275
+ `ls -d .git`. A "Download ZIP" folder (named `<repo>-main`, no `.git`) cannot branch, commit, or
276
+ open a PR. Fix: `git clone <repo url>` and work in the clone.
277
+ 2. **You are at the repository root.** <!-- doctor-check: cwd_is_root -->
278
+ `git rev-parse --show-toplevel` matches your working directory. A project-scope install keys to
279
+ the session's directory rather than the repo root
280
+ ([claude-code#82830](https://github.com/anthropics/claude-code/issues/82830)), so run both
281
+ install commands from the root.
282
+ 3. **The `claude` CLI is on PATH.** <!-- doctor-check: claude_on_path -->
283
+ `command -v claude`. Missing: install Claude Code and reopen the terminal. The in-app route
284
+ below needs no terminal at all.
285
+ 4. **Which version that CLI is.** <!-- doctor-check: claude_version -->
286
+ `claude --version`. Reported, not judged: from Claude Code 2.1.195 the app itself reports a
287
+ repo's plugin as not installed and shows the install command; older builds say nothing, which is
288
+ why this checklist exists.
289
+ 5. **That CLI understands `--scope`.** <!-- doctor-check: scope_supported -->
290
+ `claude plugin install --help` mentions `--scope`. If it does not, the CLI is too old for either
291
+ install command — use the in-app route below, then update it.
292
+ 6. **How that CLI was installed.** <!-- doctor-check: install_channel -->
293
+ `readlink -f "$(command -v claude)"` says which channel to update through: native installer →
294
+ `claude update`; npm or nvm → `npm install -g @anthropic-ai/claude-code@latest`; Homebrew →
295
+ `brew upgrade claude-code`. An nvm `claude` earlier on PATH shadows a native one; `claude doctor`
296
+ lists conflicting installs.
297
+ 7. **The marketplace is registered.** <!-- doctor-check: marketplace_registered -->
298
+ A `ticketwright` entry in `~/.claude/plugins/known_marketplaces.json` whose `installLocation`
299
+ exists on disk. Missing: the first command of the pair below.
300
+ 8. **This repo has an install record.** <!-- doctor-check: repo_install -->
301
+ A row in `~/.claude/plugins/installed_plugins.json` whose `projectPath` is this repo.
302
+ Registration never creates one. Fix: the pair below, from the repository root, then restart.
303
+ 9. **The install record points at files that exist.** <!-- doctor-check: install_payload -->
304
+ The recorded `installPath` holds a `.claude-plugin/plugin.json`. Seen on Claude Code 2.0.22:
305
+ install prints "Successfully installed" and the directory is never created, so re-running install
306
+ is a no-op. Fix: update the CLI, then
307
+ `claude plugin uninstall ticketwright@ticketwright --scope <the recorded scope>` and install
308
+ again; if uninstall answers "not found", copy the marketplace clone into the recorded path —
309
+ recipe in [`docs/troubleshooting.md`](docs/troubleshooting.md).
310
+ 10. **No machine-wide install nobody meant.** <!-- doctor-check: user_install -->
311
+ A row with `scope: "user"` turns Ticketwright on for every repo on the machine. If you meant
312
+ this repo only: `claude plugin uninstall ticketwright@ticketwright --scope user`, then the pair
313
+ below.
314
+ 11. **The installed version matches the marketplace catalog.** <!-- doctor-check: catalog_current -->
315
+ Behind means a tagged release has not been picked up; the fix is the uninstall-and-install pair
316
+ the session banner names, at the scope the install record carries.
317
+ 12. **`yq` is installed.** <!-- doctor-check: yq_present -->
318
+ `command -v yq`. Needed only by `bin/selftest.sh`, where its absence fails a dozen-plus checks
319
+ from one cause. macOS: `brew install yq`. Linux: your distribution's package. Windows:
320
+ `winget install MikeFarah.yq`.
321
+ 13. **Git identity is set.** <!-- doctor-check: git_identity -->
322
+ `git config --get user.name` and `git config --get user.email`. Unset, the first commit fails:
323
+ `git config user.name "…"` and `git config user.email "…"`.
324
+ 14. **Restart the right way.** <!-- doctor-check: restart -->
325
+ Printed whenever an install check above is not clean. Run `/reload-plugins` or start a new
326
+ session. If the skills still do not appear, fully quit the Claude app (Cmd+Q on macOS,
327
+ File → Exit on Windows/Linux) and relaunch. A new chat inside the running app is not a restart.
328
+
329
+ The install itself is two commands, run from the repository root:
330
+
331
+ ```bash
332
+ claude plugin marketplace add https://github.com/kyle-chalmers/ticketwright.git --scope project
333
+ claude plugin install ticketwright@ticketwright --scope project
334
+ ```
335
+
336
+ When the repo's committed settings already registered the marketplace, the first command prints
337
+ "already on disk — declared in project settings" — expected, not an error. **No terminal, or a CLI
338
+ that rejects `--scope`?** Run `/plugin install ticketwright@ticketwright` in the session and choose
339
+ **Project** scope, or use **+ → Plugins → Add plugin** in the desktop app; both run in the app's own
340
+ engine. Then update the CLI (check 6).
341
+
342
+ *Verify:* `claude plugin list` shows `ticketwright@ticketwright … enabled`, and
343
+ `/ticketwright:setup` appears in the next session's command list. `bin/selftest.sh` takes several
344
+ minutes — judge it by its exit code, not by how long it has been running. Anything still
345
+ unexplained: [`docs/troubleshooting.md`](docs/troubleshooting.md).
346
+
347
+ **1 · `git clone` the repo and open it in Claude Code** (trust the workspace when prompted). Use a
348
+ clone, not GitHub's "Download ZIP" — a `<repo>-main` folder with no `.git` cannot branch, commit, or
349
+ open a PR.
257
350
 
258
351
  *Check:* `.claude/config/stack.yaml` exists — that's the team config Track 1 committed.
259
352
 
260
- **2 · Install the plugin explicitly:**
353
+ **2 · Install the plugin explicitly**, from the repository root:
261
354
 
262
355
  ```bash
263
- claude plugin install ticketwright@ticketwright
356
+ claude plugin marketplace add https://github.com/kyle-chalmers/ticketwright.git --scope project
357
+ claude plugin install ticketwright@ticketwright --scope project
264
358
  ```
265
359
 
266
- *Check:* `claude plugin list` shows `ticketwright@ticketwright … enabled`.
360
+ Both commands default to `--scope user`, which would install Ticketwright for every repo on your
361
+ machine instead of this one. The marketplace is usually registered already from the committed
362
+ settings, so the first command prints "already on disk — declared in project settings" — expected,
363
+ not an error. If your CLI rejects `--scope`, it is too old: run `/plugin install
364
+ ticketwright@ticketwright` in the session and choose **Project** scope (or use **+ → Plugins → Add
365
+ plugin** in the desktop app), then update the CLI — see the checklist above.
366
+
367
+ *Check:* `claude plugin list` shows `ticketwright@ticketwright … enabled`. If your CLI answers
368
+ `unknown command 'list'` or `unknown option '--scope'`, it is too old — Claude Code 2.0.x has
369
+ neither; see the checklist above.
267
370
 
268
- **3 · Fully restart Claude Code** — quit and relaunch, not just a new prompt. Plugin skills load
269
- at session start; installing and running `/setup` in the same session silently fails.
371
+ **3 · Reload the plugin, or restart.** Plugin skills load at session start; installing and running
372
+ `/setup` in the same session silently fails. Run `/reload-plugins` or start a new session. If the
373
+ commands still do not appear, fully quit the Claude app (Cmd+Q on macOS, File → Exit on
374
+ Windows/Linux) and relaunch — a new chat is not a restart.
270
375
 
271
376
  *Check:* `/ticketwright:setup` shows up in the new session's command list.
272
377
 
@@ -373,9 +478,10 @@ means later ticket PRs reference rules that aren't in the repo's history.
373
478
  ### Project-scoped by default
374
479
 
375
480
  A plugin can't set its own install scope — the **repo** does. `--scope project` writes the enablement
376
- into the repo's `.claude/settings.json`, so it travels *with the repo*: every teammate who opens (and
377
- trusts) it is prompted to install Ticketwright (no marketplace to add, no config to write), and it keeps
378
- working after the person who set it up moves on. Commit the file. This is what the two Track 1
481
+ into the repo's `.claude/settings.json`, so it travels *with the repo*, and it keeps working after
482
+ the person who set it up moves on. Registering the marketplace is not installing the plugin — a
483
+ teammate who opens and trusts the repo gets the marketplace clone, then runs the install themselves
484
+ (Track 2). Commit the file. This is what the two Track 1
379
485
  install commands produce, plus the one key they don't write:
380
486
 
381
487
  ```json