ticketwright 2.0.0__tar.gz → 3.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 (94) hide show
  1. ticketwright-3.2.0/.claude/commands/.gitkeep +0 -0
  2. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/config/stack.example.asana-bq.yaml +3 -1
  3. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/config/stack.example.azure.yaml +3 -2
  4. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/config/stack.schema.md +2 -1
  5. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/config/stack.yaml +3 -2
  6. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/hooks/regenerate_ticket_index.py +23 -3
  7. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/hooks/session_context.py +0 -2
  8. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/hooks/ticket_index_context.py +5 -1
  9. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/settings.json.tmpl +1 -1
  10. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/productize/SKILL.md +2 -2
  11. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/productize/authoring.md +1 -1
  12. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/review/SKILL.md +7 -2
  13. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/setup/SKILL.md +12 -4
  14. ticketwright-3.2.0/.claude/skills/setup/scaffold.md +80 -0
  15. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/ship/SKILL.md +7 -3
  16. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/spec-and-build/SKILL.md +4 -2
  17. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/ticket/SKILL.md +8 -3
  18. {ticketwright-2.0.0 → ticketwright-3.2.0}/CHANGELOG.md +103 -0
  19. {ticketwright-2.0.0 → ticketwright-3.2.0}/PKG-INFO +38 -6
  20. {ticketwright-2.0.0 → ticketwright-3.2.0}/README.md +37 -5
  21. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/chat/slack.md +9 -9
  22. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/chat/teams.md +5 -5
  23. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/docstore/gdrive.md +3 -3
  24. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/docstore/sharepoint.md +1 -1
  25. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/tracker/asana.md +4 -4
  26. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/tracker/azure-devops.md +2 -2
  27. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/tracker/jira.md +17 -13
  28. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/tracker/linear.md +4 -4
  29. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/tracker/monday.md +4 -4
  30. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/warehouse/bigquery.md +6 -0
  31. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/warehouse/databricks.md +1 -0
  32. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/warehouse/snowflake.md +6 -0
  33. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/warehouse/synapse.md +5 -0
  34. {ticketwright-2.0.0 → ticketwright-3.2.0}/bin/build_ticket_index.py +91 -2
  35. {ticketwright-2.0.0 → ticketwright-3.2.0}/bin/enrich_ticket.py +7 -2
  36. {ticketwright-2.0.0 → ticketwright-3.2.0}/bin/selftest.sh +185 -12
  37. {ticketwright-2.0.0 → ticketwright-3.2.0}/bin/verify_stack.sh +12 -3
  38. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/AGENTS.md.tmpl +14 -2
  39. ticketwright-3.2.0/templates/CLAUDE.md.tmpl +1 -0
  40. ticketwright-3.2.0/templates/gitignore.tmpl +46 -0
  41. {ticketwright-2.0.0 → ticketwright-3.2.0}/ticketwright/__init__.py +1 -1
  42. ticketwright-2.0.0/.claude/commands/build-context-pack.md +0 -8
  43. ticketwright-2.0.0/.claude/commands/build-ticket-index.md +0 -8
  44. ticketwright-2.0.0/.claude/commands/configure-workspace.md +0 -8
  45. ticketwright-2.0.0/.claude/commands/deliver-ticket.md +0 -8
  46. ticketwright-2.0.0/.claude/commands/onboard-teammate.md +0 -8
  47. ticketwright-2.0.0/.claude/commands/prime-domain.md +0 -8
  48. ticketwright-2.0.0/.claude/commands/prime-ticket.md +0 -8
  49. ticketwright-2.0.0/.claude/commands/prime-warehouse.md +0 -8
  50. ticketwright-2.0.0/.claude/commands/productize-workflow.md +0 -8
  51. ticketwright-2.0.0/.claude/commands/qc-review.md +0 -8
  52. ticketwright-2.0.0/.claude/commands/recall.md +0 -8
  53. ticketwright-2.0.0/.claude/commands/start-ticket.md +0 -8
  54. ticketwright-2.0.0/.claude/skills/setup/scaffold.md +0 -38
  55. ticketwright-2.0.0/templates/gitignore.tmpl +0 -35
  56. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/agents/qc-reviewer.md +0 -0
  57. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/hooks/db_write_guard.py +0 -0
  58. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/refresh/SKILL.md +0 -0
  59. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/refresh/context-pack.md +0 -0
  60. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/refresh/index.md +0 -0
  61. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/setup/adopt.md +0 -0
  62. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/setup/teammate.md +0 -0
  63. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/skills/ticket/priming.md +0 -0
  64. {ticketwright-2.0.0 → ticketwright-3.2.0}/.claude/statusline.sh +0 -0
  65. {ticketwright-2.0.0 → ticketwright-3.2.0}/.gitignore +0 -0
  66. {ticketwright-2.0.0 → ticketwright-3.2.0}/LICENSE +0 -0
  67. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/README.md +0 -0
  68. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/tracker/github-issues.md +0 -0
  69. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/vcs/azure-repos.md +0 -0
  70. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/vcs/github.md +0 -0
  71. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/vcs/gitlab.md +0 -0
  72. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/warehouse/postgres.md +0 -0
  73. {ticketwright-2.0.0 → ticketwright-3.2.0}/adapters/warehouse/redshift.md +0 -0
  74. {ticketwright-2.0.0 → ticketwright-3.2.0}/bin/ingest_index_records.py +0 -0
  75. {ticketwright-2.0.0 → ticketwright-3.2.0}/bin/recall.py +0 -0
  76. {ticketwright-2.0.0 → ticketwright-3.2.0}/bin/render.sh +0 -0
  77. {ticketwright-2.0.0 → ticketwright-3.2.0}/bin/render_and_validate.sh +0 -0
  78. {ticketwright-2.0.0 → ticketwright-3.2.0}/bin/split_and_export.sh +0 -0
  79. {ticketwright-2.0.0 → ticketwright-3.2.0}/pyproject.toml +0 -0
  80. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/plan.md.tmpl +0 -0
  81. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/productized-skill/SKILL.md.tmpl +0 -0
  82. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/productized-skill/bin/drift_check.sh +0 -0
  83. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/productized-skill/golden/example.json +0 -0
  84. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/productized-skill/sql/qc.sql.tmpl +0 -0
  85. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/productized-skill/sql/step.sql.tmpl +0 -0
  86. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/productized-skill/templates/README.md.tmpl +0 -0
  87. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/productized-skill/templates/tracker_comment.txt.tmpl +0 -0
  88. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/roles/analyst.md +0 -0
  89. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/roles/engineer.md +0 -0
  90. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/roles/generalist.md +0 -0
  91. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/roles/scientist.md +0 -0
  92. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/spec.md.tmpl +0 -0
  93. {ticketwright-2.0.0 → ticketwright-3.2.0}/templates/ticket-README.md.tmpl +0 -0
  94. {ticketwright-2.0.0 → ticketwright-3.2.0}/ticketwright/cli.py +0 -0
File without changes
@@ -17,6 +17,7 @@ seams:
17
17
  tool: asana
18
18
  adapter: adapters/tracker/asana.md
19
19
  transport: mcp
20
+ mcp: asana # your Asana MCP server name (adapter calls resolve as mcp__{mcp}__…)
20
21
  workspace_gid: "1122334455"
21
22
  default_project_gid: "9988776655"
22
23
  verify: null # MCP-only
@@ -34,6 +35,7 @@ seams:
34
35
  tool: teams
35
36
  adapter: adapters/chat/teams.md
36
37
  transport: mcp
38
+ mcp: teams # your Teams MCP server name (adapter calls resolve as mcp__{mcp}__…)
37
39
  channel: "Data Team / General"
38
40
  default_mode: draft
39
41
  always_include: [Ana]
@@ -60,7 +62,7 @@ policies:
60
62
  db_write_requires_approval: true
61
63
  chat_default_draft: true
62
64
  hyperlink_everything: true
63
- commandify_everything: true
65
+ skillify_everything: true
64
66
  reduce_assumptions: true
65
67
  commit_plan_before_implement: true
66
68
  system_evolution: true
@@ -22,7 +22,7 @@ seams:
22
22
  org: acme-corp
23
23
  project: Platform
24
24
  done_state: Closed
25
- verify: "az boards work-item show --id {default_epic}"
25
+ verify: "az devops project show --project {project}"
26
26
 
27
27
  warehouse:
28
28
  tool: synapse
@@ -37,6 +37,7 @@ seams:
37
37
  tool: teams
38
38
  adapter: adapters/chat/teams.md
39
39
  transport: mcp
40
+ mcp: teams # your Teams MCP server name (adapter calls resolve as mcp__{mcp}__…)
40
41
  channel: "Data Platform / General"
41
42
  default_mode: draft
42
43
  always_include: [Dana]
@@ -66,7 +67,7 @@ policies:
66
67
  db_write_requires_approval: true
67
68
  chat_default_draft: true
68
69
  hyperlink_everything: true
69
- commandify_everything: true
70
+ skillify_everything: true
70
71
  reduce_assumptions: true
71
72
  commit_plan_before_implement: true
72
73
  system_evolution: true
@@ -33,6 +33,7 @@ policies: # behavioral rules every skill inherits (the kit's "global rules
33
33
  | `terminal_status` | string | `Done` | The "done" workflow state (not always "Done"). |
34
34
  | `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. |
35
35
  | `word_limits` | map | `{tracker_comment: 100, chat: 100, pr: 200, ticket: 200}` | Hard caps the comms skills enforce. |
36
+ | `graph_notes` | bool | `true` | Generate the Obsidian graph layer (`tickets/graph/` + `tickets/objects/`). On by default; set `false` to disable. |
36
37
 
37
38
  ## `seams`
38
39
 
@@ -57,7 +58,7 @@ and `refresh context` degrade gracefully (skip warehouse steps) when it is.
57
58
  | `db_write_requires_approval` | `true` | any skill issuing a non-SELECT — show SQL, explain, wait for `yes`. |
58
59
  | `chat_default_draft` | `true` | `chat.draft` not `chat.send` unless the user says "send it". |
59
60
  | `hyperlink_everything` | `true` | comms skills wrap every ticket-ID / file / PR in a smart link. |
60
- | `commandify_everything` | `true` | recurring work → `productize`, not a one-off. |
61
+ | `skillify_everything` | `true` | recurring work → a `/productize` skill the agent can invoke, not a one-off. |
61
62
  | `reduce_assumptions` | `true` | ask before building; still document every assumption in the ticket README. |
62
63
  | `commit_plan_before_implement` | `true` | `spec-and-build` commits the spec/plan artifact before `build` (blame-free retry). |
63
64
  | `system_evolution` | `true` | `ship` retro: a failure fixes the AI layer (rule/context/command/adapter), not just the ticket. |
@@ -17,6 +17,7 @@ project:
17
17
  terminal_status: Done # the workflow's "done" state (not always "Done")
18
18
  ticket_url_template: "https://acme.atlassian.net/browse/{id}" # how INDEX.md links a ticket ({id} token); omit/null = no link
19
19
  word_limits: {tracker_comment: 100, chat: 100, pr: 200, ticket: 200}
20
+ graph_notes: true # Obsidian graph layer (tickets/graph + tickets/objects); false to disable
20
21
 
21
22
  seams:
22
23
  tracker:
@@ -26,7 +27,7 @@ seams:
26
27
  site: acme.atlassian.net
27
28
  cli: acli
28
29
  mcp: atlassian
29
- verify: "acli jira workitem view {default_epic}"
30
+ verify: 'acli jira workitem search --jql "project = {key_prefix}" --limit 1'
30
31
 
31
32
  warehouse:
32
33
  tool: snowflake
@@ -69,7 +70,7 @@ policies:
69
70
  db_write_requires_approval: true
70
71
  chat_default_draft: true
71
72
  hyperlink_everything: true
72
- commandify_everything: true
73
+ skillify_everything: true
73
74
  reduce_assumptions: true
74
75
  commit_plan_before_implement: true
75
76
  system_evolution: true
@@ -46,12 +46,23 @@ def main() -> int:
46
46
  # index_data.json is source, NOT generated — editing the store SHOULD re-render.
47
47
  if changed.name in ("INDEX.md", "OBJECTS.md"):
48
48
  return 0
49
+ # generated graph layer (tickets/graph/, tickets/objects/) — regenerated below; never react to it
50
+ if any(part in ("graph", "objects") for part in changed.relative_to(tickets_dir).parts[:-1]):
51
+ return 0
49
52
 
50
- sys.path.insert(0, str(root / "bin"))
53
+ # The renderer module ships with the kit (bin/), not the user's project — resolve it against
54
+ # CLAUDE_PLUGIN_ROOT (else this hook's own kit dir). `root` stays the project for the data.
55
+ kit = os.environ.get("CLAUDE_PLUGIN_ROOT")
56
+ bindir = (Path(kit).resolve() if kit else Path(__file__).resolve().parent.parent.parent) / "bin"
57
+ sys.path.insert(0, str(bindir))
51
58
  try:
52
- from build_ticket_index import build_rows, render, render_objects # type: ignore
59
+ from build_ticket_index import build_rows, render, render_objects, render_graph_layer, load_config # type: ignore
53
60
  rows = build_rows(root)
61
+ cfg = load_config(root)
54
62
  fresh = {tickets_dir / "INDEX.md": render(rows), tickets_dir / "OBJECTS.md": render_objects(rows)}
63
+ graph_dirs = [tickets_dir / "graph", tickets_dir / "objects"] # always tracked so disabling cleans up
64
+ if cfg.get("graph_notes", True):
65
+ fresh.update(render_graph_layer(rows, root))
55
66
  except SystemExit:
56
67
  return 0 # malformed index_data.json — surfaced when the agent runs the renderer
57
68
  except Exception:
@@ -61,9 +72,18 @@ def main() -> int:
61
72
  changed_any = False
62
73
  for p, txt in fresh.items():
63
74
  if (p.read_text() if p.is_file() else None) != txt:
75
+ p.parent.mkdir(parents=True, exist_ok=True)
64
76
  p.write_bytes(txt.encode("utf-8")); changed_any = True
77
+ fresh_keys_ci = {str(p).lower() for p in fresh} # orphan cleanup (case-insensitive for macOS)
78
+ for gd in graph_dirs:
79
+ if gd.is_dir():
80
+ for existing in gd.glob("*.md"):
81
+ if str(existing).lower() not in fresh_keys_ci:
82
+ existing.unlink(); changed_any = True
83
+ if not any(gd.iterdir()):
84
+ gd.rmdir()
65
85
  if changed_any:
66
- print("Auto-regenerated tickets/INDEX.md + OBJECTS.md (ticket folder changed). "
86
+ print("Auto-regenerated tickets/INDEX.md + OBJECTS.md + graph layer (ticket folder changed). "
67
87
  "Run bin/ingest_index_records.py to refresh a ticket's curated summary.")
68
88
  except OSError:
69
89
  pass
@@ -52,10 +52,8 @@ def main() -> int:
52
52
 
53
53
  skills = sorted(p.parent.name for p in (root / ".claude/skills").glob("*/SKILL.md")) \
54
54
  if (root / ".claude/skills").is_dir() else []
55
- # Hide deprecated v1 alias stubs — they route to v2 skills and shouldn't re-clutter the surface.
56
55
  commands = sorted(
57
56
  p.stem for p in (root / ".claude/commands").glob("*.md")
58
- if "Deprecated" not in p.read_text(errors="replace")[:200]
59
57
  ) if (root / ".claude/commands").is_dir() else []
60
58
 
61
59
  lines = [
@@ -38,7 +38,11 @@ def discovered_total(root: Path) -> int | None:
38
38
  """True ticket count from the renderer's discovery (cheap globs, no README reads)."""
39
39
  try:
40
40
  import sys
41
- sys.path.insert(0, str(root / "bin"))
41
+ # The renderer module ships with the kit (bin/), not the project — resolve via
42
+ # CLAUDE_PLUGIN_ROOT (else this hook's own kit dir); `root` stays the project for discover().
43
+ kit = os.environ.get("CLAUDE_PLUGIN_ROOT")
44
+ bindir = (Path(kit).resolve() if kit else Path(__file__).resolve().parent.parent.parent) / "bin"
45
+ sys.path.insert(0, str(bindir))
42
46
  from build_ticket_index import discover # type: ignore
43
47
  return len(discover(root))
44
48
  except Exception:
@@ -1,5 +1,5 @@
1
1
  {
2
- "_README": "Rendered to .claude/settings.json by /setup. Wires the kit's hooks (policy enforcement + session priming + ticket-index surfacing/auto-regen), a conservative read-only permission baseline (setup appends stack-specific query/CLI allows), and the stack statusline. The db_write_guard hook turns 'db_write_requires_approval' into a mechanical ask before any destructive warehouse statement.",
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 ADDS an 'extraKnownMarketplaces' (ticketwright github source, autoUpdate:true) + 'enabledPlugins' (ticketwright@ticketwright:true) block so the REPO opts every user into the plugin at PROJECT scope (a plugin cannot set its own install scope — the repo does; teammates are prompted to install), committed with the repo, and self-updates on formal releases only (version-string change; never un-released main commits). 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.",
3
3
  "hooks": {
4
4
  "PreToolUse": [
5
5
  {
@@ -8,7 +8,7 @@ disable-model-invocation: true
8
8
 
9
9
  # /productize
10
10
 
11
- The meta-skill (`commandify_everything`). When a task recurs (quarterly sale, monthly report,
11
+ The meta-skill (`skillify_everything`). When a task recurs (quarterly sale, monthly report,
12
12
  periodic reconciliation), this stamps out a new **folder skill** with the productized skeleton:
13
13
  a phased pipeline with hard halts, parameterized SQL/steps, a QC checkpoint, a golden-replay
14
14
  regression test, and a hard halt before any external post. `/refresh index` + the `--recurring`
@@ -25,7 +25,7 @@ Ask (AskUserQuestion) for:
25
25
  to assert; the byte-identical output file to diff against).
26
26
 
27
27
  ## Phase 2 — Stamp from the template
28
- 1. Copy `templates/productized-skill/` → `.claude/skills/<name>/` (SKILL.md + `sql/ templates/
28
+ 1. Copy `${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/templates/productized-skill/` → `.claude/skills/<name>/` (SKILL.md + `sql/ templates/
29
29
  bin/ golden/`).
30
30
  2. Render `SKILL.md.tmpl` with the interview answers into the canonical phase shape:
31
31
  **Phase 0** pre-flight (validate params + `verify_stack` for touched seams + an object drift
@@ -24,7 +24,7 @@ warehouse round-trip — cheap insurance. It asserts: zero leftover tokens · no
24
24
  balanced single-quotes / parens · flags an unquoted SQL literal. Halt on any error.
25
25
 
26
26
  ```bash
27
- bash bin/render_and_validate.sh sql/step.sql.tmpl asof=2026-06-30 src=T [--strict]
27
+ bash "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/render_and_validate.sh" sql/step.sql.tmpl asof=2026-06-30 src=T [--strict]
28
28
  ```
29
29
 
30
30
  ## Export helpers — `bin/split_and_export.sh`
@@ -24,7 +24,10 @@ Read-only: it reviews and re-runs, it does not edit code (the build owns fixes).
24
24
  - `= NULL` → must be `IS NULL`; missing div-by-zero guard; `SELECT *` in deliverables; functions on
25
25
  filtered columns; implicit cross-source type mismatches (missing `CAST`); hardcoded values that
26
26
  should be parameters; missing required schema/instance filters; `LEFT JOIN` predicate in `WHERE`
27
- that silently becomes an inner join; `NOT IN` with nullable columns; `UNION` vs `UNION ALL`.
27
+ that silently becomes an inner join; `NOT IN` with nullable columns; `UNION` vs `UNION ALL`;
28
+ session-variable/`DECLARE` parameterization in a shipped or exported query (prefer a CTE params
29
+ row — portable, single-statement, export-clean; session vars pollute `--format csv` output and
30
+ don't survive into single-statement JDBC/ODBC clients).
28
31
 
29
32
  **② Counts & dedup** (re-run independently)
30
33
  - Re-run the row-count; compare to the documented count. **Duplicate detection is the primary test:**
@@ -42,7 +45,9 @@ Read-only: it reviews and re-runs, it does not edit code (the build owns fixes).
42
45
  maintainability). Classify each finding Critical / Should-fix / Review.
43
46
 
44
47
  **⑤ Human sign-off**
45
- - Output format check (CSV headers row 1, no preamble/blank rows; filenames carry record counts).
48
+ - Output format check (CSV headers row 1, no preamble/blank rows; filenames carry record counts;
49
+ **ASCII punctuation only in cell values** — no em/en dashes, smart quotes, or ellipsis chars, which
50
+ render as mojibake in Excel/CSV viewers).
46
51
  - README completeness (assumptions enumerated, QC results, business context). Flag for the human.
47
52
 
48
53
  ## Tiers & halting
@@ -46,10 +46,11 @@ include the key commented with a `# TODO` and keep going — `verify` will point
46
46
  4. Compose `.claude/config/stack.yaml` per `stack.schema.md` (chosen seams live; optional seams as
47
47
  commented blocks; the 9 policies with a one-line "when to change this" comment each). Warn if a
48
48
  chosen adapter is `status: stub`.
49
- 5. Scaffold the repo per [scaffold.md](scaffold.md): render `AGENTS.md` (+ role focus),
50
- `.claude/settings.json` (hooks + read-only CLI allows), folders, `.gitignore` (with the
51
- anchored `**/final_deliverables/*.csv` export guard), the AI-layer index, and the seeded ticket
52
- index.
49
+ 5. Scaffold the repo per [scaffold.md](scaffold.md): render `AGENTS.md` (+ role focus) and a one-line
50
+ `CLAUDE.md` (`@AGENTS.md`, so Claude Code auto-loads the rules),
51
+ `.claude/settings.json` (hooks — omitted on a plugin install — + read-only CLI allows), folders,
52
+ `.gitignore` (deliverable CSVs committed by default; PII opts out via `*.private.csv` / a
53
+ `private/` subfolder), the AI-layer index, and the seeded ticket index.
53
54
 
54
55
  ### Phase 4 — Verify & hand off
55
56
  6. Run `!bash "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/selftest.sh"` (kit integrity — a failure here is fatal) and
@@ -57,3 +58,10 @@ include the key commented with a `# TODO` and keep going — `verify` will point
57
58
  **not** fatal at setup time; print its adapter's auth notes as the fix).
58
59
  7. **Report:** the chosen stack, files written, any `# TODO` keys or stub adapters, and the next
59
60
  step — `/ticket <id>` to start work, or `/setup --teammate` for a new person.
61
+ 8. **Offer to commit the scaffold.** What setup just wrote (`.claude/config/stack.yaml`, `AGENTS.md`,
62
+ `CLAUDE.md`, `.claude/settings.json`, `.gitignore`, `documentation/AI_LAYER_INDEX.md`, the seeded `tickets/`
63
+ index — plus, on a vendored install, the kit itself) is untracked; if it isn't committed, a later
64
+ ticket PR references rules/adapters absent from the repo's history. Offer a commit (e.g.
65
+ `chore: initialize ticketwright workspace`). First flag that `stack.yaml` may hold internal
66
+ identifiers (tracker site, warehouse project/dataset) — config, not secrets, but worth a glance
67
+ before committing to a public repo.
@@ -0,0 +1,80 @@
1
+ # Scaffold details — what `/setup` writes into a fresh repo
2
+
3
+ ## Global rules (`AGENTS.md`)
4
+ Render `${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/templates/AGENTS.md.tmpl` → `AGENTS.md` (tokens
5
+ from `stack.yaml`: tool names, key_prefix, terminal_status, word limits, policies). Fill
6
+ `{{role_focus}}` from `${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/templates/roles/<role>.md` using
7
+ `project.role` (`generalist` unless the user changed it). This is the always-loaded tier —
8
+ keep it the rendered template; repo-specific rules get added by humans over time.
9
+
10
+ Also write `CLAUDE.md` from `${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/templates/CLAUDE.md.tmpl` — a
11
+ one-line `@AGENTS.md` import so **Claude Code** auto-loads these rules (it reads `CLAUDE.md`; other
12
+ agents read `AGENTS.md` directly). Keep it to that single import line.
13
+
14
+ ## Hooks + settings (`.claude/settings.json`)
15
+ Render `${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/.claude/settings.json.tmpl` → `.claude/settings.json`.
16
+ **The `hooks` block is install-mode-dependent:**
17
+ - **Plugin install** (`${CLAUDE_PLUGIN_ROOT}` set): `.claude-plugin/plugin.json` already wires the
18
+ kit's hooks from the plugin dir — **OMIT the `hooks` block** here, or they double-fire (double
19
+ db-write prompts, double index regen). Keep `permissions` + `statusLine`.
20
+ - **Vendored install** (`cp -r`, no `${CLAUDE_PLUGIN_ROOT}`): **keep the `hooks` block** — nothing
21
+ else wires it: `db_write_guard.py` (PreToolUse) makes `db_write_requires_approval` mechanical;
22
+ `session_context.py` + `ticket_index_context.py` (SessionStart) prime the stack + ticket catalog;
23
+ `regenerate_ticket_index.py` (PostToolUse) keeps `tickets/INDEX.md` fresh on folder changes.
24
+
25
+ **On a plugin install, also commit a project-scoped enablement.** A plugin can't set its own install
26
+ scope — the *repo* opts in. Merge these two keys
27
+ into the rendered `.claude/settings.json` so the plugin is enabled *for this repo* (committed →
28
+ travels with the repo; teammates who open and trust it are prompted to install it; it survives the
29
+ original author leaving) and refreshes
30
+ itself:
31
+
32
+ ```json
33
+ {
34
+ "extraKnownMarketplaces": {
35
+ "ticketwright": {
36
+ "source": { "source": "github", "repo": "kyle-chalmers/ticketwright" },
37
+ "autoUpdate": true
38
+ }
39
+ },
40
+ "enabledPlugins": {
41
+ "ticketwright@ticketwright": true
42
+ }
43
+ }
44
+ ```
45
+
46
+ `autoUpdate` re-installs **only when the plugin's version string changes** — i.e. only on a formal
47
+ release (the release commit bumps `plugin.json`/`marketplace.json`/`__init__.py` in lockstep and tags
48
+ `v*`). Between releases, ordinary commits to the default branch leave the version untouched, so
49
+ teammates are never pulled onto un-released mid-flight work. Do **not** add these keys on a vendored
50
+ (`cp -r`/pip) install — there's no marketplace to enable from; the kit is already in-repo.
51
+
52
+ Then append the chosen warehouse/tracker/vcs **read-only** CLI allows to `permissions.allow` (e.g.
53
+ `Bash(<warehouse_cli> …:*)`). **Statusline:** the template's `statusLine.command` is the
54
+ project-relative `.claude/statusline.sh`, so on a plugin install **copy
55
+ `${CLAUDE_PLUGIN_ROOT}/.claude/statusline.sh` → `.claude/statusline.sh`** so it resolves (on a
56
+ vendored install it's already there).
57
+
58
+ ## Folders + `.gitignore`
59
+ Create `tickets/{assignee_dir}/`, `documentation/`, `resources/`, `specs/` (and `ci/` if wanted).
60
+ Render `${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/templates/gitignore.tmpl` → `.gitignore` (merge if
61
+ one exists). Deliverable exports (`final_deliverables/*.csv` etc.) are **committed by default** so
62
+ results live with the ticket and show in the PR; PII/customer data opts out via a `*.private.csv`
63
+ name or a `private/` subfolder (both gitignored). If the tracker adapter ships an attachment-download
64
+ helper, copy it into `resources/`.
65
+
66
+ ## AI-layer index (`documentation/AI_LAYER_INDEX.md`)
67
+ A one-line-each inventory of the installed skills (`setup`, `ticket`, `spec-and-build`, `review`,
68
+ `ship`, `productize`, `refresh`), the `qc-reviewer` agent, the hooks, and the adapters in use — so
69
+ humans and agents can find what exists.
70
+
71
+ ## Ticket index
72
+ Seed an empty curated store — `tickets/index_data.json` with
73
+ `{"schema_version": 1, "tickets": []}` — then run
74
+ `python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/build_ticket_index.py"` to write the
75
+ initial `tickets/INDEX.md`. From here it self-maintains (PostToolUse regen on folder changes,
76
+ SessionStart surfacing, curated summaries at ship time). An existing backlog gets bootstrapped with
77
+ `/refresh index --all`.
78
+
79
+ That renderer also writes the Obsidian graph layer (`tickets/graph/` + `tickets/objects/`) when
80
+ `project.graph_notes` is on (the default), committed alongside `INDEX.md`/`OBJECTS.md`.
@@ -40,9 +40,13 @@ authorization, execute in order:
40
40
  7. **chat.draft** to `seams.chat.default_channel` (policy `chat_default_draft` — the human clicks
41
41
  send unless they said "send it", in which case `chat.send`). Smart links for ticket id(s),
42
42
  files, PR.
43
- 8. **vcs.commit** (stage this ticket's paths **plus `tickets/INDEX.md` + `tickets/OBJECTS.md` +
44
- `tickets/index_data.json`** — all three, or `--check` flags drift in CI; semantic message +
45
- Co-Authored-By) then **vcs.open_pr** (semantic title; body = Business Impact / Deliverables /
43
+ 8. **vcs.commit** — stage this ticket's paths (deliverable files included: they're committed by
44
+ default so results live with the ticket and show in the PR) **plus `tickets/INDEX.md` +
45
+ `tickets/OBJECTS.md` + `tickets/index_data.json`** (all three, or `--check` flags drift in CI;
46
+ semantic message + Co-Authored-By). **Before staging, list the `final_deliverables/` files that
47
+ will be committed and confirm none carry PII/customer data that shouldn't be in git** — if any do,
48
+ have the user rename them `*.private.csv` (etc.) or move them under a `private/` subfolder (both
49
+ gitignored) first. Then **vcs.open_pr** (semantic title; body = Business Impact / Deliverables /
46
50
  Technical Notes / QC).
47
51
  9. **transition** the ticket toward `project.terminal_status` if appropriate.
48
52
 
@@ -27,7 +27,7 @@ context-engineering core idea: AI fails from missing context, not weak models.
27
27
  `bin/recall.py --for <id>`) and reuse their SQL/QC where it fits;
28
28
  - pull the business rules from the `documentation/` glossary (the domain slice).
29
29
  Research agents return findings only; **they do not write code.**
30
- 3. **Write the spec** from `templates/spec.md.tmpl` into the ticket's folder
30
+ 3. **Write the spec** from `${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/templates/spec.md.tmpl` into the ticket's folder
31
31
  (`specs/<id>-<slug>.md` or `final_deliverables/`): operation type (new/alter), data grain,
32
32
  sources + join/cast rules, transformation logic, **validation gates** (the exact QC the build must
33
33
  pass), downstream impact, dev-env target (`seams.warehouse.dev_db`), and a **confidence score
@@ -41,7 +41,9 @@ context-engineering core idea: AI fails from missing context, not weak models.
41
41
  6. **Load** the committed spec (path arg or newest in the ticket's `specs/`). Treat it as the source
42
42
  of truth, but **validate each step independently** — don't blindly follow; the spec can be wrong.
43
43
  7. **Implement in small build-and-check sub-loops:** one object/step at a time. Develop against
44
- `seams.warehouse.dev_db` first; parameterize values at the top; explicit `ORDER BY` on any export
44
+ `seams.warehouse.dev_db` first; parameterize values at the top **via a CTE params row
45
+ (`WITH params AS (SELECT … AS anchor) … CROSS JOIN params`), not a session `DECLARE`/`SET`** —
46
+ CTE params stay portable and keep CSV exports clean; explicit `ORDER BY` on any export
45
47
  (deterministic outputs).
46
48
  8. **Embed validation between steps** — after each, run the relevant gate from the spec; self-correct.
47
49
  9. **Any non-SELECT / DDL** ⇒ policy `db_write_requires_approval`: show the exact SQL, explain the
@@ -35,9 +35,14 @@ this before?", "which tickets touched VW_X?").
35
35
  5. **Branch** via the vcs adapter, named `<id>`, off `seams.vcs.default_branch` — or a **worktree**
36
36
  when `--worktree` is passed (isolates this ticket from other in-flight work; recommended when you
37
37
  run several tickets in parallel).
38
- 6. **Scaffold** `project.ticket_path` with `project.ticket_subdirs`; tracker adapter
39
- `download_attachments` → `source_materials/` (silent if none); render
40
- `templates/ticket-README.md.tmpl` → the ticket dir.
38
+ 6. **Scaffold** `project.ticket_path` with `project.ticket_subdirs` (create the subdirs empty —
39
+ **no `.gitkeep` placeholders**; they fill with real files during build and git picks them up
40
+ then); tracker adapter `download_attachments` → `source_materials/` (silent if none); render
41
+ `${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/templates/ticket-README.md.tmpl` → the ticket dir.
42
+ 6b. **Refresh the catalog** so the new ticket shows up immediately — it won't otherwise, because the
43
+ PostToolUse index hook only fires on `Write`/`Edit` and scaffolding happens via Bash:
44
+ `python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/build_ticket_index.py"` (writes this
45
+ project's `tickets/INDEX.md` + `OBJECTS.md`; the new row shows `▱` until `/ship` curates it).
41
46
 
42
47
  ## Phase 3 — Prime context automatically (the part you never have to ask for)
43
48
  7. Follow [priming.md](priming.md), in order:
@@ -3,6 +3,109 @@
3
3
  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
+ ## [3.2.0] — 2026-07-06
7
+
8
+ Makes a plugin install **project-scoped by default** — the *repo* commits the enablement (a plugin
9
+ can't set its own install scope), so it travels with the repo and every teammate who opens the repo is
10
+ prompted to install it (no marketplace to add or config to write), staying current across the team.
11
+
12
+ ### Added
13
+ - **Project-scoped enablement is the default on plugin installs.** On a plugin install, `/setup` now
14
+ also writes an `extraKnownMarketplaces` (ticketwright github source, `autoUpdate: true`) +
15
+ `enabledPlugins` (`ticketwright@ticketwright`) block into the repo's committed
16
+ `.claude/settings.json`. It's the *repo* opting in at project scope (a plugin can't set its own install scope): teammates who
17
+ open and trust the repo are prompted to install it — that repo only — and it keeps working after the
18
+ person who set it up moves on. `autoUpdate` re-installs
19
+ **only on a formal release**: Claude Code refreshes when the version string changes, and the version
20
+ only moves in a tagged release commit, so ordinary `main` commits never pull teammates onto
21
+ un-released work. Not written on vendored (`cp -r`/pip) installs — there's no marketplace to enable
22
+ from. README documents it as the recommended team install; `scaffold.md` + the `settings.json.tmpl`
23
+ `_README` carry the rationale.
24
+ - **`bin/selftest.sh` §21** asserts the enablement block is documented, is valid JSON, targets the
25
+ real marketplace repo, and is release-gated (`autoUpdate`).
26
+
27
+ ## [3.1.0] — 2026-07-06
28
+
29
+ Hardening release from two real end-to-end runs (a `/setup` session and a full analysis ticket). Fixes
30
+ the plugin/pip install path bugs those runs surfaced, de-hardcodes the shipped adapters for a clean
31
+ first run on any stack, adds a SQL-portability guardrail, and flips the deliverable-CSV default to
32
+ commit-by-default with a PII opt-out. Also adds an **Obsidian graph layer** for browsing the ticket
33
+ archive as a knowledge graph.
34
+
35
+ ### Fixed
36
+ - **Kit-root vs project-root resolution** (the class behind a `/ship` crash on plugin/pip installs).
37
+ `bin/enrich_ticket.py` resolved its sibling scripts off the project dir (`repo_root() / "bin"`),
38
+ which doesn't exist when the kit and project diverge — now resolved from the script's own kit dir.
39
+ The two SessionStart/PostToolUse index hooks imported the renderer from the project's `bin/` (silent
40
+ fail-open on a plugin install) — now from `${CLAUDE_PLUGIN_ROOT}` / the hook's own kit dir.
41
+ `bin/verify_stack.sh` resolved adapters off the stack.yaml location (the project) — now off the kit
42
+ (`${CLAUDE_PLUGIN_ROOT}`, else the script's dir), with a project fallback for repo-vendored adapters.
43
+ - **Self-maintaining index went stale on ticket creation.** `/ticket` scaffolds via Bash, which the
44
+ `Write|Edit` PostToolUse hook never sees, so a new ticket didn't appear in `INDEX.md` until a manual
45
+ run. `/ticket` now renders the index as its final scaffold step; `build_ticket_index.py` also gained
46
+ a `git rev-parse --show-toplevel` fallback for by-hand runs without `CLAUDE_PROJECT_DIR`.
47
+
48
+ ### Changed
49
+ - **Deliverable exports are committed by default.** The old `.gitignore` blanket-ignored
50
+ `**/final_deliverables/*.csv` (+ `.tsv`/`.xlsx`/`.parquet`), so results silently never reached git or
51
+ the PR. Now they're committed; PII/customer data opts out via a `*.private.csv` name or a `private/`
52
+ subfolder (both gitignored). `/ship` lists the deliverable files and asks you to confirm none carry
53
+ PII before committing. To restore the strict "docstore only" default, uncomment the four blanket
54
+ rules in the rendered `.gitignore` (documented in-file).
55
+ - **Adapters de-hardcoded for a clean first run on any stack.** MCP server names now use the `{mcp}`
56
+ token from `stack.yaml` (was `mcp__atlassian__…` / `mcp__slack__…` / `mcp__plugin_productivity_*__…`);
57
+ the Jira and Azure DevOps `verify` no longer depend on the nullable `default_epic`; removed
58
+ org-specific Jira issue-type / mandatory-Epic / terminal-state content; `--parent` is now conditional
59
+ on `default_epic` being set. Added `mcp` to the asana/linear/monday adapter `requires`.
60
+ - **`/setup` is install-mode aware.** On a plugin install it omits the settings.json `hooks` block
61
+ (`plugin.json` already wires them — duplicating double-fired), copies `statusline.sh` into the repo
62
+ so the statusline resolves, and offers to commit the scaffold at the end (so a later ticket PR
63
+ doesn't reference rules/adapters absent from history).
64
+ - Ticket scaffolding no longer creates `.gitkeep` placeholders in subdirs that fill during build.
65
+ - **Renamed the `commandify_everything` policy → `skillify_everything`**, completing the v2/v3
66
+ commands→skills shift: recurring work becomes a `/productize` **skill the agent can invoke itself**,
67
+ not a command a human must issue. Behavior is unchanged; update the key in an existing `stack.yaml`.
68
+ - Spelled out **KISS (Keep It Simple, Stupid)** and **YAGNI (You Aren't Gonna Need It)** in the
69
+ always-loaded rules so a human reviewer knows exactly what they mean.
70
+
71
+ ### Added
72
+ - **Obsidian graph layer.** `build_ticket_index.py` now also generates `tickets/graph/<id>.md`
73
+ (a node per ticket) and `tickets/objects/<object>.md` (a node per data object), so the repo opens as
74
+ an Obsidian vault: object clusters show every ticket that touched a table, and cross-refs show as
75
+ direct build-on lines. Plain markdown (no plugins/wikilinks), auto-maintained on the index hook, on
76
+ by default (`project.graph_notes: false` to disable). README convention untouched.
77
+ - **Portable-params guardrail (CTE vs session `DECLARE`).** BigQuery/Snowflake/Synapse `dialect_notes`,
78
+ the `/review` dialect-lint tier, and `/spec-and-build` now steer to a `WITH params AS (…) … CROSS
79
+ JOIN params` row — a `DECLARE` script pollutes `--format csv` exports and breaks in single-statement
80
+ JDBC/ODBC clients (DataGrip/Simba).
81
+ - **CSV deliverables use ASCII punctuation only.** Em/en dashes, smart quotes, and ellipsis characters
82
+ render as mojibake in Excel and many CSV viewers, so the always-loaded rules (`AGENTS.md`) and the
83
+ `/review` output-format check now steer cell values to plain ASCII (`-`, or "to" for ranges).
84
+ - **Scaffolds a one-line `CLAUDE.md` (`@AGENTS.md`)** so Claude Code auto-loads the always-loaded
85
+ rules (it reads `CLAUDE.md`; other agents read `AGENTS.md`). `AGENTS.md` now also points at
86
+ `tickets/INDEX.md` / `OBJECTS.md`, putting the reuse-prior-work habit in the always-loaded tier.
87
+ - **`bin/selftest.sh` §20** (path resolution + adapter hygiene) locks each fix above; §19 updated for
88
+ the new CSV default. Suite is at **119 checks**.
89
+
90
+ ## [3.0.0] — 2026-07-04
91
+
92
+ Breaking: removed the 12 deprecated v1 alias stubs. The v2.0 release kept the old command names
93
+ (`/start-ticket`, `/qc-review`, …) working as thin routers to their v2 skills, with a documented
94
+ "removed in v3" lifecycle. That removal is this release. No engine, adapter, template, or skill
95
+ behavior changed — purely the scheduled drop of the compatibility layer.
96
+
97
+ ### Removed
98
+ - **12 v1 alias stubs** (`.claude/commands/`). Use the v2 names instead:
99
+ `start-ticket` / `prime-ticket` / `prime-domain` / `prime-warehouse` / `recall` → **`/ticket`**
100
+ (`--recall` for standalone lookups) · `qc-review` → **`/review`** · `deliver-ticket` →
101
+ **`/ship`** · `configure-workspace` / `onboard-teammate` → **`/setup`** (`--teammate`) ·
102
+ `productize-workflow` → **`/productize`** · `build-ticket-index` / `build-context-pack` →
103
+ **`/refresh`** (`index` / `context` / `all`). Full map: `docs/troubleshooting.md`.
104
+
105
+ ### Changed
106
+ - `bin/selftest.sh` check 14b now asserts the 12 stubs are absent (was: asserts all present).
107
+ - `.claude/hooks/session_context.py` dropped the now-unneeded deprecated-alias filter.
108
+
6
109
  ## [2.0.0] — 2026-07-01
7
110
 
8
111
  The UX release: **13 invokables → 7 skills**, one front door, ≤5-question setup, plain language on
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ticketwright
3
- Version: 2.0.0
3
+ Version: 3.2.0
4
4
  Summary: Tool-agnostic AI layer for ticket-driven work repos: prior-art recall, object reverse-index, deep QC, and a self-maintaining ticket index. Stdlib-only, no vector store.
5
5
  Project-URL: Homepage, https://github.com/kyle-chalmers/ticketwright
6
6
  Project-URL: Repository, https://github.com/kyle-chalmers/ticketwright
@@ -59,6 +59,26 @@ That's it. `setup` also handles repos that **already have** ticket history — i
59
59
  existing layout instead of scaffolding, and writes a `MIGRATION.md` checklist (see
60
60
  [Adopting an existing repo](#adopting-an-existing-repo)).
61
61
 
62
+ **Project-scoped by default.** A plugin can't set its own install scope — so instead, `setup` commits
63
+ the enablement into the repo's `.claude/settings.json`. That's the *repo* opting in at project scope:
64
+ it travels *with the repo*, so every teammate who opens (and trusts) the repo is prompted to install
65
+ Ticketwright (no marketplace to add, no config to write), and it keeps working after the person who
66
+ set it up moves on:
67
+
68
+ ```jsonc
69
+ {
70
+ "extraKnownMarketplaces": {
71
+ "ticketwright": { "source": { "source": "github", "repo": "kyle-chalmers/ticketwright" }, "autoUpdate": true }
72
+ },
73
+ "enabledPlugins": { "ticketwright@ticketwright": true }
74
+ }
75
+ ```
76
+
77
+ `autoUpdate` re-installs **only on a formal release** — Claude Code refreshes when the plugin's
78
+ *version* changes, and the version only moves in a tagged release commit, so day-to-day commits to
79
+ `main` never pull teammates onto un-released work. (Prefer the user-level `/plugin install` above for
80
+ personal, cross-repo use; use the committed block when you want the whole team on it.)
81
+
62
82
  ## How work flows
63
83
 
64
84
  Four steps — **plan → build → check → ship** — and one command to remember:
@@ -80,8 +100,8 @@ Three supporting skills you'll reach for occasionally:
80
100
  | `/productize` | Turn a recurring workflow (quarterly pull, monthly report) into its own parameterized, golden-tested skill |
81
101
 
82
102
  Plugin skills are namespaced (`/ticketwright:ticket`); inside a configured repo the short names
83
- work too. v1 command names (`/start-ticket`, `/qc-review`, …) still route to their v2 equivalents
84
- for this release.
103
+ work too. (The v1 command names — `/start-ticket`, `/qc-review`, … — were retired in v3; see the
104
+ rename map in [docs/troubleshooting.md](docs/troubleshooting.md#upgrading).)
85
105
 
86
106
  ## Never rebuild what's been built
87
107
 
@@ -92,6 +112,17 @@ vector store) and writes a *reuse brief*: what to copy, which gotchas carry over
92
112
  this time. `tickets/OBJECTS.md` answers the reverse question — "which tickets touched `VW_X`?"
93
113
  Details: [docs/ticket-index.md](docs/ticket-index.md).
94
114
 
115
+ ## See it as a graph (Obsidian)
116
+
117
+ Ticketwright also writes a small, auto-maintained graph layer under `tickets/` — `graph/<id>.md`
118
+ (a node per ticket) and `objects/<object>.md` (a node per data object) — so you can open the repo as
119
+ an [Obsidian](https://obsidian.md) vault and *browse* your work: open a table like `ANALYTICS.VW_LOAN`
120
+ and its local graph is every ticket that touched it; open a ticket and you see the objects it touched
121
+ plus the tickets it built on. Point Obsidian at the repo (or `tickets/`), open Graph view, and for the
122
+ cleanest picture set the filter to `-README -INDEX -OBJECTS -AGENTS -CLAUDE` and add a color Group on
123
+ `path:objects`. It's plain markdown (no plugins, no wikilinks) and renders on GitHub too. On by
124
+ default; set `project.graph_notes: false` in `stack.yaml` to turn it off.
125
+
95
126
  ## Safety rails (on by default)
96
127
 
97
128
  - **DB writes ask first** — a hook inspects every warehouse command and prompts before anything
@@ -99,8 +130,9 @@ Details: [docs/ticket-index.md](docs/ticket-index.md).
99
130
  - **External posts hard-halt** — `/ship` prints exactly what it's about to post (tracker comment,
100
131
  chat message, PR) and waits for your explicit go.
101
132
  - **Chat defaults to draft** — you click send.
102
- - **Exports can't leak into git** — the shipped `.gitignore` blocks `final_deliverables/*.csv` at
103
- any depth; deliverables go to your docstore, not the repo.
133
+ - **Deliverables commit with the ticket, PII opts out** — exports are committed by default so results
134
+ show in the PR; keep customer data out of git by naming it `*.private.csv` or dropping it in a
135
+ `private/` subfolder, and `/ship` lists what it's about to commit so nothing sensitive slips in.
104
136
  - **Every assumption is written down** — the ticket README template enumerates them by category.
105
137
 
106
138
  ## Adopting an existing repo
@@ -131,7 +163,7 @@ ticketwright init # vendor the kit into a repo (plugin-fre
131
163
  - [docs/ticket-index.md](docs/ticket-index.md) — the ticket catalog + recall engine in depth.
132
164
  - [CONTRIBUTING.md](CONTRIBUTING.md) · [ROADMAP.md](ROADMAP.md) · [CHANGELOG.md](CHANGELOG.md)
133
165
 
134
- CI runs the 95-check self-test on every push; PyPI publishing is OIDC Trusted Publishing (no stored
166
+ CI runs the full self-test on every push; PyPI publishing is OIDC Trusted Publishing (no stored
135
167
  tokens) — see [docs/pypi-setup.md](docs/pypi-setup.md).
136
168
 
137
169
  ## License