ticketwright 4.1.0__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.azure.yaml +2 -2
  2. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.multi-audience.yaml +3 -3
  3. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.multi-warehouse.yaml +4 -4
  4. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.no-warehouse.yaml +3 -3
  5. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.solo.yaml +2 -2
  6. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.schema.md +5 -5
  7. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.yaml +3 -3
  8. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/ticket_index_context.py +23 -1
  9. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/settings.json.tmpl +1 -1
  10. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/build/SKILL.md +7 -3
  11. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/review/SKILL.md +6 -3
  12. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/interview.md +1 -1
  13. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/scaffold.md +2 -1
  14. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/ticket/SKILL.md +3 -2
  15. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/ticket/priming.md +5 -0
  16. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/ticket/spec.md +4 -2
  17. {ticketwright-4.1.0 → ticketwright-4.2.0}/CHANGELOG.md +100 -0
  18. ticketwright-4.2.0/PKG-INFO +192 -0
  19. ticketwright-4.2.0/README.md +168 -0
  20. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/README.md +2 -2
  21. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/chat/gmail.md +3 -3
  22. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/chat/outlook.md +2 -2
  23. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/chat/slack.md +1 -1
  24. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/github-issues.md +1 -1
  25. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/jira.md +2 -2
  26. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/linear.md +3 -3
  27. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/build_ticket_index.py +57 -13
  28. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/delivery_plan.py +56 -11
  29. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/effective_config.py +6 -0
  30. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/emit_runtime.py +7 -22
  31. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/enrich_ticket.py +7 -7
  32. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/handoff.sh +10 -7
  33. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/plugin_doctor.py +104 -11
  34. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/recall.py +2 -2
  35. ticketwright-4.2.0/bin/retired_skills.py +169 -0
  36. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/selftest.sh +459 -67
  37. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/AGENTS.md.tmpl +15 -11
  38. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/golden/example.json +2 -2
  39. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/plan.md.tmpl +7 -1
  40. {ticketwright-4.1.0 → ticketwright-4.2.0}/ticketwright/__init__.py +1 -1
  41. {ticketwright-4.1.0 → ticketwright-4.2.0}/ticketwright/cli.py +10 -11
  42. ticketwright-4.1.0/PKG-INFO +0 -781
  43. ticketwright-4.1.0/README.md +0 -757
  44. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/agents/qc-reviewer.md +0 -0
  45. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/commands/.gitkeep +0 -0
  46. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/connections.example.yaml +0 -0
  47. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.asana-bq.yaml +0 -0
  48. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/viewer.example.yaml +0 -0
  49. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/_stack.py +0 -0
  50. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/db_write_guard.py +0 -0
  51. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/regenerate_ticket_index.py +0 -0
  52. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/review_verdict_guard.py +0 -0
  53. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/session_context.py +0 -0
  54. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/source_material_guard.py +0 -0
  55. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/refresh/SKILL.md +0 -0
  56. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/refresh/context-pack.md +0 -0
  57. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/refresh/index.md +0 -0
  58. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/SKILL.md +0 -0
  59. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/adopt.md +0 -0
  60. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/teammate.md +0 -0
  61. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/voice.md +0 -0
  62. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/ship/SKILL.md +0 -0
  63. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/skillify/SKILL.md +0 -0
  64. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/skillify/authoring.md +0 -0
  65. {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/statusline.sh +0 -0
  66. {ticketwright-4.1.0 → ticketwright-4.2.0}/.gitignore +0 -0
  67. {ticketwright-4.1.0 → ticketwright-4.2.0}/LICENSE +0 -0
  68. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/chat/teams.md +0 -0
  69. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/docstore/gdrive.md +0 -0
  70. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/docstore/rclone.md +0 -0
  71. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/docstore/sharepoint.md +0 -0
  72. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/fireflies.md +0 -0
  73. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/granola.md +0 -0
  74. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/notion.md +0 -0
  75. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/teams.md +0 -0
  76. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/zoom.md +0 -0
  77. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/antigravity.md +0 -0
  78. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/claude-code.md +0 -0
  79. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/cline.md +0 -0
  80. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/codex-cli.md +0 -0
  81. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/cursor.md +0 -0
  82. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/devin.md +0 -0
  83. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/opencode.md +0 -0
  84. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/asana.md +0 -0
  85. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/azure-devops.md +0 -0
  86. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/local.md +0 -0
  87. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/monday.md +0 -0
  88. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/vcs/azure-repos.md +0 -0
  89. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/vcs/github.md +0 -0
  90. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/vcs/gitlab.md +0 -0
  91. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/viewer/macos-open.md +0 -0
  92. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/viewer/windows-start.md +0 -0
  93. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/viewer/xdg-open.md +0 -0
  94. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/bigquery.md +0 -0
  95. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/databricks.md +0 -0
  96. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/postgres.md +0 -0
  97. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/redshift.md +0 -0
  98. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/snowflake.md +0 -0
  99. {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/synapse.md +0 -0
  100. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/_yamlite.py +0 -0
  101. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/bump_version.sh +0 -0
  102. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/hook_shim.py +0 -0
  103. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/ingest_index_records.py +0 -0
  104. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/install.sh +0 -0
  105. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/kit_paths.py +0 -0
  106. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/meeting_refs.py +0 -0
  107. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/opencode_tool_gate.js +0 -0
  108. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/render.sh +0 -0
  109. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/render_and_validate.sh +0 -0
  110. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/resolve_user.py +0 -0
  111. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/review_verdict.py +0 -0
  112. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/scan_source_materials.py +0 -0
  113. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/split_and_export.sh +0 -0
  114. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/sql_scan.py +0 -0
  115. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/tw +0 -0
  116. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/update_notice.py +0 -0
  117. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/verify_stack.sh +0 -0
  118. {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/whoami.py +0 -0
  119. {ticketwright-4.1.0 → ticketwright-4.2.0}/pyproject.toml +0 -0
  120. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/CLAUDE.md.tmpl +0 -0
  121. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/SKILL.md.tmpl +0 -0
  122. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/bin/drift_check.sh +0 -0
  123. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/sql/qc.sql.tmpl +0 -0
  124. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/sql/step.sql.tmpl +0 -0
  125. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/templates/README.md.tmpl +0 -0
  126. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/templates/tracker_comment.txt.tmpl +0 -0
  127. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/gitignore.tmpl +0 -0
  128. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/person.yaml.tmpl +0 -0
  129. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/project-README.md.tmpl +0 -0
  130. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/roles/analyst.md +0 -0
  131. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/roles/engineer.md +0 -0
  132. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/roles/generalist.md +0 -0
  133. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/roles/scientist.md +0 -0
  134. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/spec.md.tmpl +0 -0
  135. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/ticket-README.md.tmpl +0 -0
  136. {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/voice-profile.md.tmpl +0 -0
  137. {ticketwright-4.1.0 → ticketwright-4.2.0}/tests/emit/README.md +0 -0
  138. {ticketwright-4.1.0 → ticketwright-4.2.0}/tests/meeting_refs/README.md +0 -0
  139. {ticketwright-4.1.0 → ticketwright-4.2.0}/tests/source_materials/README.md +0 -0
@@ -4,8 +4,8 @@
4
4
  # Try it: bash bin/verify_stack.sh .claude/config/stack.example.azure.yaml --dry-run
5
5
 
6
6
  project:
7
- key_prefix: ENG # branch/index display convention (ADO ids are bare integers)
8
- key_prefixes: [ENG]
7
+ key_prefix: TEST # branch/index display convention (ADO ids are bare integers)
8
+ key_prefixes: [TEST]
9
9
  assignee_dir: dana
10
10
  ticket_path: "tickets/{assignee}/{id}"
11
11
  ticket_subdirs: [source_materials, final_deliverables, qc_queries, exploratory_analysis]
@@ -19,12 +19,12 @@
19
19
  # Field docs: stack.schema.md · Validate: bash bin/verify_stack.sh .claude/config/stack.example.multi-audience.yaml
20
20
 
21
21
  project:
22
- key_prefix: ENG
23
- key_prefixes: [ENG]
22
+ key_prefix: TEST
23
+ key_prefixes: [TEST]
24
24
  assignee_dir: alice
25
25
  ticket_path: "tickets/{assignee}/{id}"
26
26
  ticket_subdirs: [source_materials, final_deliverables, qc_queries, exploratory_analysis]
27
- default_epic: ENG-100
27
+ default_epic: TEST-100
28
28
  terminal_status: Done
29
29
  ticket_url_template: "https://acme.atlassian.net/browse/{id}"
30
30
  word_limits: {tracker_comment: 100, chat: 100, pr: 200, ticket: 200}
@@ -9,12 +9,12 @@
9
9
  # Field docs: stack.schema.md · Validate: bash bin/verify_stack.sh .claude/config/stack.example.multi-warehouse.yaml
10
10
 
11
11
  project:
12
- key_prefix: ENG
13
- key_prefixes: [ENG]
12
+ key_prefix: TEST
13
+ key_prefixes: [TEST]
14
14
  assignee_dir: alice
15
15
  ticket_path: "tickets/{assignee}/{id}"
16
16
  ticket_subdirs: [source_materials, final_deliverables, qc_queries, exploratory_analysis]
17
- default_epic: ENG-100
17
+ default_epic: TEST-100
18
18
  terminal_status: Done
19
19
  ticket_url_template: "https://acme.atlassian.net/browse/{id}"
20
20
  word_limits: {tracker_comment: 100, chat: 100, pr: 200, ticket: 200}
@@ -96,7 +96,7 @@ seams:
96
96
 
97
97
  policies:
98
98
  hard_halt_before_external_posts: true
99
- db_write_requires_approval: true
99
+ db_write_requires_approval: high_risk # off | high_risk | all (see stack.schema.md)
100
100
  chat_default_draft: true
101
101
  hyperlink_everything: true
102
102
  skillify_everything: true
@@ -13,8 +13,8 @@
13
13
  # Field docs: stack.schema.md · Validate: bash bin/verify_stack.sh .claude/config/stack.example.no-warehouse.yaml
14
14
 
15
15
  project:
16
- key_prefix: ENG # display/branch convention; GitHub issue ids are bare numbers
17
- key_prefixes: [ENG]
16
+ key_prefix: TEST # display/branch convention; GitHub issue ids are bare numbers
17
+ key_prefixes: [TEST]
18
18
  assignee_dir: ana
19
19
  ticket_path: "tickets/{assignee}/{id}"
20
20
  ticket_subdirs: [source_materials, final_deliverables, qc_queries, working_notes]
@@ -34,7 +34,7 @@ seams:
34
34
  transport: cli
35
35
  repo: "acme/analysis-repo"
36
36
  verify: "gh auth status"
37
- # Folder/branch ids read ENG-<number> locally; adapter verbs take the bare issue number —
37
+ # Folder/branch ids read TEST-<number> locally; adapter verbs take the bare issue number —
38
38
  # see the adapter's note (key_prefix is a display/branch convention for GitHub Issues).
39
39
 
40
40
  # warehouse is deliberately omitted — this team's analysis work doesn't run through a database.
@@ -5,7 +5,7 @@
5
5
  # the unit of work, and its README.md is the ticket.
6
6
  #
7
7
  # Two settings carry that: `tracker: local` (the tracker verbs read and write the folder itself) and
8
- # `id_mode: slug` (the folder name is the id, instead of a tracker key like ENG-1234). Chat and
8
+ # `id_mode: slug` (the folder name is the id, instead of a tracker key like TEST-1234). Chat and
9
9
  # docstore are simply absent — /ship skips those artifacts and names the /setup command that would
10
10
  # enable them, rather than blocking.
11
11
  #
@@ -59,7 +59,7 @@ seams:
59
59
 
60
60
  policies:
61
61
  hard_halt_before_external_posts: true
62
- db_write_requires_approval: true
62
+ db_write_requires_approval: high_risk # off | high_risk | all (see stack.schema.md)
63
63
  chat_default_draft: true
64
64
  hyperlink_everything: true
65
65
  skillify_everything: true
@@ -111,13 +111,13 @@ policies: # behavioral rules every skill inherits (the kit's "global rules
111
111
 
112
112
  | Field | Type | Example | Meaning |
113
113
  |---|---|---|---|
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. |
114
+ | `key_prefix` | string | `TEST` | 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
- | `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]`). |
116
+ | `key_prefixes` | list | `[TEST]` | Prefixes the ticket index recognizes in folder names. Optional; defaults to `[key_prefix]`. Use when one repo holds tickets from several trackers (e.g. `[TEST, OPS]`). |
117
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
- | `default_epic` | string \| null | `ENG-100` | Parent epic for newly created tickets (null if tracker has no epics). |
120
+ | `default_epic` | string \| null | `TEST-100` | Parent epic for newly created tickets (null if tracker has no epics). |
121
121
  | `terminal_status` | string | `Done` | The "done" workflow state (not always "Done"). |
122
122
  | `ticket_url_template` | template \| null | `https://acme.atlassian.net/browse/{id}` | How `tickets/INDEX.md` links each ticket (`{id}` token). Null/omitted → the index renders no per-ticket link. |
123
123
  | `intake` | list | `[tracker, email]` | Where work arrives; any of `tracker`, `email`, `chat`, `meetings`. Optional; **defaults to `[tracker]`** — the example shows email intake *added* (a "email carries work in" answer in the setup interview), not the default. Consumer: when `email`/`chat`/`meetings` is listed, `/ticket`'s priming step reads `source_materials/` for material a human dropped in — intake beyond the tracker arrives as files, not API calls. **`meetings`** names the AI-notetaker channel: export the notes or a curated transcript excerpt as `source_materials/YYYY-MM-DD-<slug>-meeting.md`. That name is the **committed, curated form** — trimmed to decisions and action items. Raw full transcripts stay out of git by default (`templates/gitignore.tmpl`) and are gated before any commit or docstore copy by `source_material_guard`, whose classifier (`bin/scan_source_materials.py`) matches filenames and document shape, **never meaning** — a curated summary quoting confidential material verbatim passes, so this is a gate against the bulk artifact, not a confidentiality review. |
@@ -386,10 +386,10 @@ What changes:
386
386
 
387
387
  | | `keyed` (default) | `slug` |
388
388
  |---|---|---|
389
- | A folder is a ticket when | its name contains a tracker key (`ENG-12`) | its whole name, after an optional leading status emoji, matches `[a-z0-9][a-z0-9_-]*` |
389
+ | A folder is a ticket when | its name contains a tracker key (`TEST-12`) | its whole name, after an optional leading status emoji, matches `[a-z0-9][a-z0-9_-]*` |
390
390
  | The id is | the matched key | the whole folder name |
391
391
  | `key_prefix` | conventional (absent, the index falls back to matching any `LETTERS-digits`) | omit it — the banner and statusline label the repo by its directory |
392
- | Cross-references come from | any `ENG-1234` in the README prose | **only** `[[wiki-links]]` |
392
+ | Cross-references come from | any `TEST-1234` in the README prose | **only** `[[wiki-links]]` |
393
393
  | Ordering | date, then ticket number, then id | date, then id (every slug scores 0 on the number) |
394
394
 
395
395
  Two consequences worth knowing before you adopt it:
@@ -13,12 +13,12 @@
13
13
  # stack.example.no-warehouse.yaml (no warehouse seam — document/report deliverables).
14
14
 
15
15
  project:
16
- key_prefix: ENG # ticket-ID prefix; branch names = {key_prefix}-NNNN
17
- key_prefixes: [ENG] # prefixes the ticket index recognizes (defaults to [key_prefix])
16
+ key_prefix: TEST # ticket-ID prefix; branch names = {key_prefix}-NNNN
17
+ key_prefixes: [TEST] # prefixes the ticket index recognizes (defaults to [key_prefix])
18
18
  assignee_dir: alice # default owner subdir under tickets/
19
19
  ticket_path: "tickets/{assignee}/{id}"
20
20
  ticket_subdirs: [source_materials, final_deliverables, qc_queries, exploratory_analysis]
21
- default_epic: ENG-100 # parent epic for new tickets (or null if the tracker has no epics)
21
+ default_epic: TEST-100 # parent epic for new tickets (or null if the tracker has no epics)
22
22
  terminal_status: Done # the workflow's "done" state (not always "Done")
23
23
  ticket_url_template: "https://acme.atlassian.net/browse/{id}" # how INDEX.md links a ticket ({id} token); omit/null = no link
24
24
  # intake: [tracker] # where work arrives (tracker | email | chat); email/chat intake = forwarded threads in source_materials/
@@ -60,8 +60,23 @@ def discovered_total(root: Path) -> int | None:
60
60
  return None
61
61
 
62
62
 
63
+ def hidden_keyed(root: Path) -> list[str]:
64
+ """Keyed folders slug mode is hiding (see build_ticket_index.hidden_keyed_folders). Fails open."""
65
+ try:
66
+ import sys
67
+ kit = os.environ.get("CLAUDE_PLUGIN_ROOT")
68
+ bindir = (Path(kit).resolve() if kit else Path(__file__).resolve().parent.parent.parent) / "bin"
69
+ sys.path.insert(0, str(bindir))
70
+ from build_ticket_index import hidden_keyed_folders, hidden_keyed_warning # type: ignore
71
+ hidden = hidden_keyed_folders(root)
72
+ return [hidden_keyed_warning(hidden)] if hidden else []
73
+ except Exception:
74
+ return []
75
+
76
+
63
77
  def main() -> int:
64
78
  root = project_root()
79
+ warn = hidden_keyed(root)
65
80
  data_file = root / "tickets" / "index_data.json"
66
81
  index_file = root / "tickets" / "INDEX.md"
67
82
 
@@ -79,6 +94,10 @@ def main() -> int:
79
94
  if index_file.is_file():
80
95
  print("## Ticket index\nCatalog of prior ticket work: `tickets/INDEX.md` — grep it before "
81
96
  "starting related work (same object / stakeholder / report).")
97
+ if warn:
98
+ print(warn[0])
99
+ elif warn:
100
+ print("## Ticket index\n" + warn[0])
82
101
  return 0
83
102
 
84
103
  total = discovered_total(root) or len(tickets)
@@ -106,8 +125,11 @@ def main() -> int:
106
125
  lines.append(f"- {t.get('owner')}/{t.get('id')} ({d}) — {title}")
107
126
  if total > len(tickets):
108
127
  lines.append(f"({total - len(tickets)} newer ticket(s) on disk not yet enriched — run the index workflow.)")
109
- elif len(tickets) > total:
128
+ elif len(tickets) > total and not warn:
129
+ # Not while slug mode is hiding keyed folders: those records DO have folders, and pruning
130
+ # would delete them. The warning below says what to do instead.
110
131
  lines.append(f"({len(tickets) - total} record(s) have no folder on disk — run /refresh index --prune.)")
132
+ lines += warn
111
133
  print("\n".join(lines))
112
134
  return 0
113
135
 
@@ -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; 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; the review_verdict_guard hook turns 'hard_halt_before_external_posts' into a mechanical ask before a git push / PR of a ticket with deliverables and no APPROVE verdict on file.",
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, docs/getting-started.md 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; the review_verdict_guard hook turns 'hard_halt_before_external_posts' into a mechanical ask before a git push / PR of a ticket with deliverables and no APPROVE verdict on file.",
3
3
  "hooks": {
4
4
  "PreToolUse": [
5
5
  {
@@ -33,7 +33,9 @@ foreign owners share is a hard stop listing the `owner/id` choices, never a pick
33
33
  first; every ticket is scoped before it is built." (A spec passed as an argument does not
34
34
  substitute: the plan is the approval record.)
35
35
  - **`spec:` names a path** → that spec is the executable detail; load it (the argument path wins
36
- if one was given). If the named file does not exist → stop: "`plan.md` for `<owner>/<id>` names
36
+ if one was given). The path is repo-root-relative (`<ticket-dir>/specs/<id>-<slug>.md`); a bare
37
+ `specs/<id>-<slug>.md` from an older plan resolves inside `<ticket-dir>`, never at the repo
38
+ root. If the named file does not exist → stop: "`plan.md` for `<owner>/<id>` names
37
39
  a spec that is not on file — run `/ticket <owner>/<id>` to finish scoping."
38
40
  - **`spec: not required`** → the plan is the blueprint.
39
41
  - **`spec: after-root-cause`** → run only the plan's investigation steps (read-only against the
@@ -69,8 +71,10 @@ foreign owners share is a hard stop listing the `owner/id` choices, never a pick
69
71
  first warehouse run (a bad join is cheapest to catch before it costs a warehouse-minute), and
70
72
  hand the exported CSVs over after. Both via
71
73
  `bash "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" handoff.sh <paths>`, then wait for
72
- sign-off before continuing. It exits 0 and stays silent when that user has no viewer config —
73
- note it once and carry on; this never blocks a build.
74
+ sign-off before continuing. Say a file was opened only when handoff printed an `opened:` line
75
+ for it. With no viewer config, `enabled: false`, or only `would run:` lines, nothing opened: say
76
+ so once, with the reason handoff gave (for example "No viewer is configured, so nothing was
77
+ opened"), list the paths, and carry on. This never blocks a build.
74
78
  5. **Any mutation** ⇒ policy `db_write_requires_approval` (`off` | `high_risk` | `all`). Under the
75
79
  default `high_risk`: show the exact SQL, explain the change, and wait for explicit `yes` before
76
80
  anything irreversible or access-changing (DROP/DELETE/UPDATE/TRUNCATE/MERGE/GRANT/
@@ -78,9 +78,12 @@ canonical order resolves next — the ticket's declared target before the seam d
78
78
  bullet when it is `off`). Layers ①–④ prove the query is *internally* consistent; only a person can
79
79
  say the numbers are the right numbers. Do not emit a verdict before this:
80
80
  1. `bash "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/bin/tw" handoff.sh <final_deliverables + qc_queries paths>`
81
- — routes each file to the app that user chose. It exits 0 and stays silent when they have no
82
- viewer config; in that case say so **once** and continue, never block.
83
- 2. If it produced no output and no **usable** config exists, offer the one-time setup: which app
81
+ routes each file to the app that user chose. **Say a file was opened only when handoff
82
+ printed an `opened:` line for it.** With no `opened:` line (no viewer config, `enabled: false`,
83
+ or `would run:` in a session with no desktop), nothing was opened: say so plainly and **once**
84
+ (for example "No viewer is configured, so nothing was opened"), list the paths so the person
85
+ can open them, and continue. Never block. Its stderr names which case it was.
86
+ 2. If it opened nothing and no **usable** config exists, offer the one-time setup: which app
84
87
  for `.sql`, which for `.csv`, this repo only or all their repos. Their answers are written to
85
88
  `.claude/config/viewer.local.yaml` (gitignored, per-user) or the user-level path — see
86
89
  `.claude/config/viewer.example.yaml`. "None / don't ask again" writes `enabled: false`.
@@ -233,7 +233,7 @@ deliverables open for human sign-off at `/review` only."
233
233
 
234
234
  ```yaml
235
235
  project:
236
- key_prefix: ENG
236
+ key_prefix: TEST
237
237
  assignee_dir: alice
238
238
  ticket_path: "tickets/{assignee}/{id}"
239
239
  terminal_status: Done
@@ -156,7 +156,8 @@ project-relative `.claude/statusline.sh`, so on a plugin install **copy
156
156
  vendored install it's already there).
157
157
 
158
158
  ## Folders + `.gitignore`
159
- Create `tickets/{assignee_dir}/`, `documentation/`, `resources/`, `specs/` (and `ci/` if wanted).
159
+ Create `tickets/{assignee_dir}/`, `documentation/`, `resources/` (and `ci/` if wanted). No repo-root
160
+ `specs/`: a spec lives with its ticket, at `<ticket-dir>/specs/`, written by `/ticket`.
160
161
  Render `<KIT>/templates/gitignore.tmpl` → `.gitignore` (merge if
161
162
  one exists). Deliverable exports (`final_deliverables/*.csv` etc.) are **committed by default** so
162
163
  results live with the ticket and show in the PR; PII/customer data opts out via a `*.private.csv`
@@ -133,8 +133,9 @@ context brief (including the reuse brief) first, then:
133
133
  **nothing is written to the ticket until step 12; no runtime mode is required.** Read-only here
134
134
  is GUIDANCE the agent follows, not a gate the runtime enforces — say so if asked.
135
135
  9. **Draft the plan — compose it, do not write it yet.** Its shape is `templates/plan.md.tmpl` under
136
- `kit_root`: Goal, Scope (in / explicitly out), Deliverables expected, Approach, Validation
137
- strategy, Touched, Questions for the requester, Risks, Next step, Confidence. Inputs: the priming
136
+ `kit_root`: Goal, Scope (in / explicitly out), Builds on (each prior ticket from the reuse brief and
137
+ why it matched), Deliverables expected, Approach, Validation strategy, Touched, Questions for
138
+ the requester, Risks, Next step, Confidence. Inputs: the priming
138
139
  brief + the reuse brief. Decide the **`spec:` value** by the criteria in the template's header —
139
140
  `not required`, or a spec path (required when the work creates or alters a persisted object others
140
141
  depend on, reconciles two or more sources, or confidence is below 7), or `after-root-cause` for an
@@ -56,6 +56,11 @@ knowledge base. Each slice below was a separate command in v1 (`/prime-ticket`,
56
56
  listings). Many candidates → spawn read-only Explore agents in parallel (findings only).
57
57
  - **Write the reuse brief** (≤ ~200 words): the closest prior work, **what to copy** (which SQL/QC
58
58
  artifact + path), known **gotchas** those tickets carried, and **what's different** this time.
59
+ **Name every prior ticket you build on by `<owner>/<id>` and say why it matched**, translating the
60
+ bracketed reasons recall printed beside it into plain words: `obj:` → "shares the object `X`",
61
+ `tag:` → "shares the tag `t`", `ref` → "the two tickets cross-reference each other", `kw:` alone →
62
+ "keyword overlap only" (the weakest signal; say so). A match with no stated reason reads as
63
+ staged, and the reason is what lets a reader check it.
59
64
 
60
65
  ## 3 · Domain slice (business meaning)
61
66
 
@@ -30,8 +30,10 @@ the scope, the deliverables and the open questions — read it first and do not
30
30
  returned — no second launcher call) and fill every section it defines; its **validation gates**
31
31
  become `/review`'s checklist, and its **dev target** is `seams.warehouse.dev_target`, else the key
32
32
  the warehouse adapter names in its `dev_key:` frontmatter. Set the plan's `spec:` line to the path
33
- it will be written to, `specs/<id>-<slug>.md`. Like the plan, it is drafted now and written to the
34
- ticket only at `/ticket` step 12, after approval.
33
+ it will be written to, `<ticket-dir>/specs/<id>-<slug>.md`, spelled out from the repo root (for
34
+ example `tickets/alice/TEST-130/specs/TEST-130-returns.md`) so `/build` cannot resolve it anywhere
35
+ else. Like the plan, it is drafted now and written to the ticket only at `/ticket` step 12, after
36
+ approval.
35
37
  4. **Reduce assumptions:** add the spec's open questions to the plan's list — `/ticket` asks them all
36
38
  at once, before approval (`reduce_assumptions`).
37
39
  5. **Do not write or commit here.** `/ticket` step 12 writes the plan and the spec and commits them
@@ -4,6 +4,106 @@ 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
+ ## [Unreleased]
8
+
9
+ ## [4.2.0] — 2026-10-03
10
+
11
+ ### Changed
12
+ - **Upgrades now remove retired skills instead of only warning about them.** A repo installed
13
+ before the `productize` → `skillify` or `spec-and-build` → `build` rename kept the old skill
14
+ directory, so `/productize` and `/spec-and-build` still showed up in the slash menu beside their
15
+ replacements. `ticketwright init` and `ticketwright install --runtime <name>` now delete a retired
16
+ directory when every file in it is byte-identical to a version Ticketwright shipped (or, in an
17
+ emitted tree, still carries the installer's provenance header), and print what they removed. A
18
+ directory with anything edited is kept, and the warning now names the files that differ. A
19
+ vendored copy upgraded with `git pull` can run the same check with `bash bin/tw retired_skills.py`.
20
+ The verify-only paths still delete nothing. The rule and the list of shipped-version hashes live
21
+ in one place, `bin/retired_skills.py`, which both installers import.
22
+ - **Example tickets use the `TEST` project key.** The README's team-repo tree, the docs, the shipped
23
+ `stack.yaml` and every stack example, the adapter docs, the setup interview's sample config, the
24
+ example index store, the golden-replay template and the CLI help all showed `ENG-` (and one
25
+ `DEMO-`) keys; they now use `TEST-` with the same numbers, so cross-references in the examples
26
+ still line up. Test fixtures keep their existing keys, and `ENG-`/`DEMO-`/`TEST-`/`SAMPLE-` all
27
+ stay legal fixture prefixes.
28
+ - **The README is a two-minute read.** It dropped from about 7,300 words to under 600 words of
29
+ prose, and now shows how the system works as two diagrams (the ticket lifecycle, and how skills
30
+ reach your tools through adapters) and what a working team repo looks like as a folder tree:
31
+ one folder per person, one per ticket, each file labeled with the step that writes it. Nothing was cut: the install tracks, the doctor
32
+ checklist, the committed settings block, adoption and non-plugin installs moved verbatim to
33
+ [docs/getting-started.md](docs/getting-started.md); the team-brain mechanisms, lifecycle detail,
34
+ voice profiles, safety rails and the full hooks table moved to
35
+ [docs/how-it-works.md](docs/how-it-works.md). Every selftest pin on that text follows it to its
36
+ new home, and a new section 58 holds the README to a 600-word prose budget, requires the
37
+ visuals, and checks that every relative link resolves.
38
+ - **The hooks table now lists every hook, and the DB guard's reach is stated honestly.** The old
39
+ README's "Hooks, in full" table left out `source_material_guard.py`; it is listed now. The
40
+ safety-rails line said the DB guard inspects "every warehouse command", but it sees only CLI
41
+ commands run through Bash, and SQL sent through an MCP tool never reaches it. Both surfaces now
42
+ say so.
43
+ - **Upgrades are stated as manual, because they are.** `autoUpdate` refreshes the marketplace
44
+ catalog and never upgrades the installed plugin (claude-code #61854, #52218, #49410, #17361). The
45
+ README, `docs/getting-started.md` (Track 1 step 4, "What setup writes", the committed-settings
46
+ notes) and the troubleshooting upgrade table now say so plainly, and name the upgrade as
47
+ uninstall, install, relaunch. ROADMAP's open question on this is closed with the finding.
48
+ - **A plan names the prior work it builds on, and why recall matched it.** The reuse brief now names
49
+ each prior ticket by `owner/id` with the reason `recall.py` already prints (a shared object, a
50
+ shared tag, a cross-reference, or keyword overlap only), and `templates/plan.md.tmpl` gains a
51
+ `## Builds on` section to carry it into the approved plan.
52
+ - **The example configs spell the write policy out.** `stack.example.solo.yaml` and
53
+ `stack.example.multi-warehouse.yaml` used the legacy `db_write_requires_approval: true`; they now
54
+ say `high_risk`, which is what `true` already meant.
55
+
56
+ ### Added
57
+ - **`plugin_doctor` names the "network source differs" install failure.** A new check,
58
+ `marketplace_source`, reads your user-level `settings.json` for the marketplace's declared source.
59
+ When it differs from what this repo declares (most often the GitHub shorthand left by an older
60
+ install), `claude plugin marketplace add` fails with "its network source differs from the one
61
+ declared for it in settings". The check says so before you hit it and gives the fix. Only the
62
+ source's kind is reported, never the value. The install checklists in `docs/getting-started.md`
63
+ and `templates/AGENTS.md.tmpl` gain the matching line (now fifteen checks).
64
+
65
+ ### Fixed
66
+ - **The review hand-off no longer lets an agent claim it opened files it did not.** With no viewer
67
+ configured, or `enabled: false`, `bin/handoff.sh` opened nothing and printed nothing, and an agent
68
+ told a person "I've opened the note and the checklist for you to read". It now says on stderr that
69
+ nothing was opened and why (stdout stays empty), and `/review` and `/build` claim a file opened
70
+ only when handoff printed an `opened:` line for it.
71
+ - **Switching a keyed repo to `id_mode: slug` no longer loses tickets silently.** A slug id must be
72
+ lowercase, so folders named with a tracker key (`tickets/alice/TEST-14`) stopped being tickets and
73
+ dropped out of `INDEX.md`, `OBJECTS.md` and the graph with no message (an audit saw `OBJECTS.md`
74
+ go from 3 objects to 0). Worse, their curated records then looked orphaned: the session banner
75
+ recommended `/refresh index --prune`, and `--prune` deleted them. `bin/build_ticket_index.py` now
76
+ prints a `WARNING` naming the hidden folders on every run (render, `--check`, `--stats`,
77
+ `--prune`); `--check` keeps its staleness exit code. `--prune` refuses while any are hidden, and
78
+ the session banner shows the warning instead of recommending a prune. Slug discovery itself is
79
+ unchanged.
80
+ - **A spec has one home: its ticket folder.** `/ticket` wrote the spec to `<ticket-dir>/specs/`,
81
+ but the plan's `spec:` line (the path `/build` loads) read a bare `specs/<id>-<slug>.md`, and
82
+ `/setup` also created an empty repo-root `specs/` that nothing ever wrote to. The same path
83
+ therefore named two places, depending on where it was resolved. `/setup` no longer creates the
84
+ root folder; the plan template and `spec.md` now write the full `<ticket-dir>/specs/<id>-<slug>.md`
85
+ path; and `/build` resolves a bare `specs/` path from an older plan inside the ticket folder, so
86
+ plans already on file keep working. A repo that already has an empty root `specs/` can delete it.
87
+
88
+
89
+ ## [4.1.1] — 2026-09-18
90
+
91
+ ### Fixed
92
+ - **A refused delivery destination now says which character was refused, and what to do.** The
93
+ delivery resolver's shell-metacharacter refusal (`bin/delivery_plan.py`) named only the key —
94
+ "the `drive_folder` value carries shell metacharacters" — and left the reader to find the
95
+ offending character themselves. Every refusal (destination, recipients, sender; both the routing
96
+ halt `/ship` acts on and the `--audit` advisory `verify_stack` prints) now names the character(s)
97
+ and the remedy — pointing at the value's actual source: the tool slot config (including a
98
+ literal `base_path` pinned there), the machine-local half of a composed docstore path
99
+ (`mount_root` / `remote`), or, for a tool-only chat slot, `chat.channel:` / `chat.recipients:`
100
+ in the ticket's delivery plan. A folder name such as `Reports & Analysis` is the common trigger. What is refused is unchanged — the same
101
+ values still halt; they are now explained. `effective_config.offending_chars()` is the one
102
+ helper behind every message. One path is deliberately unchanged and noted for a follow-up: the
103
+ `verify:`-template refusal in `bin/verify_stack.sh` (a `{base_path}` token whose machine-local
104
+ half is unsafe) still names only the token, not the character.
105
+
106
+
7
107
  ## [4.1.0] — 2026-09-12
8
108
 
9
109
  ### Added
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.5
2
+ Name: ticketwright
3
+ Version: 4.2.0
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
+ Project-URL: Homepage, https://github.com/kyle-chalmers/ticketwright
6
+ Project-URL: Repository, https://github.com/kyle-chalmers/ticketwright
7
+ Project-URL: Issues, https://github.com/kyle-chalmers/ticketwright/issues
8
+ Author: Kyle Chalmers
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: claude-code,jira,linear,qc,recall,snowflake,ticket-index,tickets,workflow
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Software Development :: Code Generators
18
+ Requires-Python: >=3.9
19
+ Provides-Extra: dev
20
+ Requires-Dist: build>=1; extra == 'dev'
21
+ Requires-Dist: pytest>=8; extra == 'dev'
22
+ Requires-Dist: twine>=5; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Ticketwright
26
+
27
+ [![CI](https://github.com/kyle-chalmers/ticketwright/actions/workflows/ci.yml/badge.svg)](https://github.com/kyle-chalmers/ticketwright/actions/workflows/ci.yml)
28
+ [![release](https://img.shields.io/github/v/tag/kyle-chalmers/ticketwright?label=release&sort=semver&color=blue)](https://github.com/kyle-chalmers/ticketwright/releases)
29
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
30
+ ![Python](https://img.shields.io/badge/python-3%20%C2%B7%20stdlib--only-3776AB)
31
+ ![tool-agnostic](https://img.shields.io/badge/works%20with-your%20tracker%20%C2%B7%20warehouse%20%C2%B7%20chat%20%C2%B7%20docs%20%C2%B7%20meetings%20%C2%B7%20git-success)
32
+
33
+ ## Mission
34
+
35
+ **Ticketwright empowers a team to do a high volume of analysis without letting quality slide, on whatever tools they already use.**
36
+
37
+ ## Vision
38
+
39
+ **Any new or experienced member can pick up any analysis and be productive the same day, because the team's past work is written down and organized, and AI can trace it.**
40
+
41
+ ---
42
+
43
+ Built for analysts, BI, ops, research and reporting teams whose work product is an answer rather
44
+ than a feature. It sits beside your tracker, warehouse and dashboards.
45
+
46
+ ## What it builds: a team brain
47
+
48
+ Every ticket lands in one folder with its business context, written assumptions, QC verdict and
49
+ deliverables. An auto-maintained catalog (`tickets/INDEX.md`) and object map (`tickets/OBJECTS.md`)
50
+ make that history searchable by people and agents, so `/ticket` opens new work with the closest
51
+ prior work already ranked. Details: [docs/how-it-works.md](docs/how-it-works.md).
52
+
53
+ ## The lifecycle is the map
54
+
55
+ Every ticket moves through the same five phases, and each shipped ticket feeds the next:
56
+
57
+ ```mermaid
58
+ flowchart LR
59
+ B[("Team brain<br/>tickets/ + INDEX.md")] -- "closest prior work" --> P1
60
+ P1["1 · Open the work<br/>/ticket"] --> P2["2 · Do the work<br/>/build"] --> P3["3 · Quality-check<br/>/review"] --> P4["4 · Deliver<br/>/ship"] --> P5["5 · Announce<br/>/ship"]
61
+ P4 -- "files the ticket, its assumptions and QC verdict" --> B
62
+ ```
63
+
64
+ Quality checking has no slot of its own: `/review` borrows the warehouse to re-run your queries,
65
+ then a person signs off.
66
+
67
+ Skills speak to tool slots, never to a vendor, so swapping a tool is one config line and one
68
+ adapter file:
69
+
70
+ ```mermaid
71
+ flowchart LR
72
+ S["Skills<br/>/ticket · /build · /review · /ship"] -- "verbs: fetch_ticket,<br/>query, backup, draft" --> Y["stack.yaml<br/>which tool fills<br/>each slot"]
73
+ Y --> A["adapters/slot/tool.md<br/>verb to real command"]
74
+ A --> T["Your tools<br/>Jira · Snowflake · Slack<br/>GitHub · Drive · ..."]
75
+ H{{"Hooks ask first:<br/>destructive SQL,<br/>unreviewed push"}} -. guard .-> T
76
+ ```
77
+
78
+ Cloud-drive backup needs one machine-local step: [docs/drive-mount.md](docs/drive-mount.md).
79
+
80
+ | Tool slot | Works with |
81
+ |---|---|
82
+ | Tracker | Jira, Azure DevOps, Linear, Asana, Monday, GitHub Issues, or none (`id_mode: slug`) |
83
+ | Warehouse | Snowflake, BigQuery, Databricks, Postgres, Redshift, Synapse, or none |
84
+ | Chat | Slack, Teams, Gmail, Outlook |
85
+ | Docs | Google Drive, SharePoint, or Dropbox/S3/Box through rclone |
86
+ | Meetings | Zoom, Fireflies, Granola, Teams, Notion (optional) |
87
+ | Git | GitHub, GitLab, Azure Repos |
88
+
89
+ These adapters ship today; another tool is [one more adapter file](adapters/README.md).
90
+
91
+ ## What a working repo looks like
92
+
93
+ `/setup` writes the top half once. The `tickets/` half grows as the team works: one folder per
94
+ person, one folder per ticket, and the same shape inside every ticket. The labels show which step
95
+ writes each piece.
96
+
97
+ ```
98
+ your-repo/
99
+ ├── AGENTS.md # team rules, read by people and agents alike
100
+ ├── CLAUDE.md # one line: @AGENTS.md
101
+ ├── bin/tw # launcher that finds the installed kit
102
+ ├── .claude/config/
103
+ │ ├── stack.yaml # which tool fills each slot, plus team policies
104
+ │ └── connections.local.yaml # your machine only (gitignored)
105
+ ├── people/
106
+ │ ├── alice.yaml # one file per teammate: identities, viewer, voice
107
+ │ └── bob.yaml
108
+ ├── documentation/ # the domain knowledge pack
109
+ └── tickets/
110
+ ├── INDEX.md # every owner's tickets in one catalog (auto)
111
+ ├── OBJECTS.md # table or view -> tickets that touched it (auto)
112
+ ├── graph/ objects/ # Obsidian nodes for tickets and tables (auto)
113
+ ├── bob/
114
+ │ └── TEST-118/ # returns baseline, shipped last month
115
+ └── alice/
116
+ ├── TEST-127/ # shipped
117
+ └── TEST-130/ # in progress, builds on bob's TEST-118
118
+ ├── README.md /ticket business context, every assumption
119
+ ├── plan.md /ticket the plan you approved
120
+ ├── specs/ /ticket the spec, when the plan calls for one
121
+ ├── source_materials/ /ticket attachments, curated meeting notes
122
+ ├── exploratory_analysis/ /build scratch queries, kept for the record
123
+ ├── final_deliverables/ /build 01_returns_by_region_1284rows.sql + .csv
124
+ ├── qc_queries/ /build numbered checks, then /review's verdict
125
+ └── delivery-plan.yaml /ship who received it, and where
126
+ ```
127
+
128
+ Nobody maintains the catalog by hand. `/ship` files each finished ticket into `INDEX.md`,
129
+ `OBJECTS.md` and the graph, and `/ticket` searches every owner's past work, not only yours. When
130
+ Alice's TEST-130 mentions Bob's TEST-118, the two are linked, so her reviewer can trace the numbers
131
+ back to his. A new teammate adds one `people/` file and gets their own folder. Nothing else changes.
132
+
133
+ ## How work flows
134
+
135
+ ```
136
+ /ticket <id> opens or resumes the ticket, auto-loads its context + closest prior work,
137
+ writes the plan (and the spec when the plan calls for one), and waits for
138
+ your approval before anything is built ↓
139
+ /build executes the approved plan or spec in fresh context, then runs /review itself
140
+ /review [--deep] the independent QC pass /build runs for you: re-runs queries, walks the validation
141
+ pyramid → APPROVE / REQUEST-CHANGES; run it directly to re-check or go --deep
142
+ …and at the top of that pyramid, opens the deliverables in YOUR apps and waits
143
+ /ship [--go] backup → tracker comment → chat draft → commit + PR — HARD HALT before anything
144
+ external; warns, waits for a typed `ship unreviewed`, and records it when no
145
+ review verdict is on file
146
+ ```
147
+
148
+ Also: `/setup` (configure once, onboard a teammate), `/refresh` (rebuild the catalog), and
149
+ `/skillify` (turn a recurring workflow into its own skill). On a plugin install, prefix each one:
150
+ `/ticketwright:ticket`.
151
+
152
+ ## Get started
153
+
154
+ Install project-scoped, from the root of the repo you work tickets in:
155
+
156
+ ```bash
157
+ claude plugin marketplace add https://github.com/kyle-chalmers/ticketwright.git --scope project
158
+ claude plugin install ticketwright@ticketwright --scope project
159
+ ```
160
+
161
+ Run `/reload-plugins` (or fully quit the app; a new chat is not a restart), then
162
+ `/ticketwright:setup`, which detects your tools and interviews you before writing anything. First
163
+ person in a repo (Track 1) or joining one (Track 2): [docs/getting-started.md](docs/getting-started.md)
164
+ has both, with a prompt to paste to your agent. `autoUpdate` never upgrades the plugin:
165
+ [uninstall, install, relaunch](docs/troubleshooting.md#upgrading).
166
+
167
+ ## Safety rails
168
+
169
+ - Destructive warehouse SQL run through a CLI asks first, even when it's hidden in a `-f` file.
170
+ - `/ship` stops before any external post and shows exactly what it will send.
171
+ - Pushing a ticket's deliverables with no approving review on file asks first.
172
+
173
+ Every hook is listed in [docs/how-it-works.md](docs/how-it-works.md#hooks-in-full).
174
+
175
+ ## See it as a graph (Obsidian)
176
+
177
+ The index also writes `tickets/graph/` and `tickets/objects/`, so opening the repo as an Obsidian
178
+ vault shows which tickets touched which tables. See [docs/obsidian.md](docs/obsidian.md).
179
+
180
+ ## Learn more
181
+
182
+ - [docs/getting-started.md](docs/getting-started.md): install tracks, what setup writes, adopting
183
+ a repo, pip and other runtimes
184
+ - [docs/how-it-works.md](docs/how-it-works.md): the lifecycle, voice profiles, every hook
185
+ - [docs/architecture.md](docs/architecture.md): adapters, the verb contract, adding a tool
186
+ - [docs/troubleshooting.md](docs/troubleshooting.md): failed skills, unreachable tools, upgrades
187
+ - [docs/obsidian.md](docs/obsidian.md) and [docs/drive-mount.md](docs/drive-mount.md)
188
+ - [CONTRIBUTING.md](CONTRIBUTING.md) · [ROADMAP.md](ROADMAP.md) · [CHANGELOG.md](CHANGELOG.md)
189
+
190
+ ## License
191
+
192
+ MIT, see [`LICENSE`](LICENSE).