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.
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.azure.yaml +2 -2
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.multi-audience.yaml +3 -3
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.multi-warehouse.yaml +4 -4
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.no-warehouse.yaml +3 -3
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.solo.yaml +2 -2
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.schema.md +5 -5
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.yaml +3 -3
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/ticket_index_context.py +23 -1
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/settings.json.tmpl +1 -1
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/build/SKILL.md +7 -3
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/review/SKILL.md +6 -3
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/interview.md +1 -1
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/scaffold.md +2 -1
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/ticket/SKILL.md +3 -2
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/ticket/priming.md +5 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/ticket/spec.md +4 -2
- {ticketwright-4.1.0 → ticketwright-4.2.0}/CHANGELOG.md +100 -0
- ticketwright-4.2.0/PKG-INFO +192 -0
- ticketwright-4.2.0/README.md +168 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/README.md +2 -2
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/chat/gmail.md +3 -3
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/chat/outlook.md +2 -2
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/chat/slack.md +1 -1
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/github-issues.md +1 -1
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/jira.md +2 -2
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/linear.md +3 -3
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/build_ticket_index.py +57 -13
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/delivery_plan.py +56 -11
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/effective_config.py +6 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/emit_runtime.py +7 -22
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/enrich_ticket.py +7 -7
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/handoff.sh +10 -7
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/plugin_doctor.py +104 -11
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/recall.py +2 -2
- ticketwright-4.2.0/bin/retired_skills.py +169 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/selftest.sh +459 -67
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/AGENTS.md.tmpl +15 -11
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/golden/example.json +2 -2
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/plan.md.tmpl +7 -1
- {ticketwright-4.1.0 → ticketwright-4.2.0}/ticketwright/__init__.py +1 -1
- {ticketwright-4.1.0 → ticketwright-4.2.0}/ticketwright/cli.py +10 -11
- ticketwright-4.1.0/PKG-INFO +0 -781
- ticketwright-4.1.0/README.md +0 -757
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/agents/qc-reviewer.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/commands/.gitkeep +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/connections.example.yaml +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/stack.example.asana-bq.yaml +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/config/viewer.example.yaml +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/_stack.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/db_write_guard.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/regenerate_ticket_index.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/review_verdict_guard.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/session_context.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/hooks/source_material_guard.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/refresh/SKILL.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/refresh/context-pack.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/refresh/index.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/SKILL.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/adopt.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/teammate.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/setup/voice.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/ship/SKILL.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/skillify/SKILL.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/skills/skillify/authoring.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.claude/statusline.sh +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/.gitignore +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/LICENSE +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/chat/teams.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/docstore/gdrive.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/docstore/rclone.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/docstore/sharepoint.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/fireflies.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/granola.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/notion.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/teams.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/meetings/zoom.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/antigravity.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/claude-code.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/cline.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/codex-cli.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/cursor.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/devin.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/runtime/opencode.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/asana.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/azure-devops.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/local.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/tracker/monday.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/vcs/azure-repos.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/vcs/github.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/vcs/gitlab.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/viewer/macos-open.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/viewer/windows-start.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/viewer/xdg-open.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/bigquery.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/databricks.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/postgres.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/redshift.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/snowflake.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/adapters/warehouse/synapse.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/_yamlite.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/bump_version.sh +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/hook_shim.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/ingest_index_records.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/install.sh +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/kit_paths.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/meeting_refs.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/opencode_tool_gate.js +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/render.sh +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/render_and_validate.sh +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/resolve_user.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/review_verdict.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/scan_source_materials.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/split_and_export.sh +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/sql_scan.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/tw +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/update_notice.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/verify_stack.sh +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/bin/whoami.py +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/pyproject.toml +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/CLAUDE.md.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/SKILL.md.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/bin/drift_check.sh +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/sql/qc.sql.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/sql/step.sql.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/templates/README.md.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/generated-skill/templates/tracker_comment.txt.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/gitignore.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/person.yaml.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/project-README.md.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/roles/analyst.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/roles/engineer.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/roles/generalist.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/roles/scientist.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/spec.md.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/ticket-README.md.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/templates/voice-profile.md.tmpl +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/tests/emit/README.md +0 -0
- {ticketwright-4.1.0 → ticketwright-4.2.0}/tests/meeting_refs/README.md +0 -0
- {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:
|
|
8
|
-
key_prefixes: [
|
|
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:
|
|
23
|
-
key_prefixes: [
|
|
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:
|
|
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:
|
|
13
|
-
key_prefixes: [
|
|
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:
|
|
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:
|
|
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:
|
|
17
|
-
key_prefixes: [
|
|
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
|
|
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
|
|
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:
|
|
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 | `
|
|
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 | `[
|
|
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 | `
|
|
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 (`
|
|
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 `
|
|
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:
|
|
17
|
-
key_prefixes: [
|
|
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:
|
|
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,
|
|
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).
|
|
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.
|
|
73
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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`.
|
|
@@ -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
|
|
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),
|
|
137
|
-
|
|
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,
|
|
34
|
-
|
|
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
|
+
[](https://github.com/kyle-chalmers/ticketwright/actions/workflows/ci.yml)
|
|
28
|
+
[](https://github.com/kyle-chalmers/ticketwright/releases)
|
|
29
|
+
[](LICENSE)
|
|
30
|
+

|
|
31
|
+

|
|
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).
|