ticketwright 2.0.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 (90) hide show
  1. ticketwright-2.0.0/.claude/agents/qc-reviewer.md +50 -0
  2. ticketwright-2.0.0/.claude/commands/build-context-pack.md +8 -0
  3. ticketwright-2.0.0/.claude/commands/build-ticket-index.md +8 -0
  4. ticketwright-2.0.0/.claude/commands/configure-workspace.md +8 -0
  5. ticketwright-2.0.0/.claude/commands/deliver-ticket.md +8 -0
  6. ticketwright-2.0.0/.claude/commands/onboard-teammate.md +8 -0
  7. ticketwright-2.0.0/.claude/commands/prime-domain.md +8 -0
  8. ticketwright-2.0.0/.claude/commands/prime-ticket.md +8 -0
  9. ticketwright-2.0.0/.claude/commands/prime-warehouse.md +8 -0
  10. ticketwright-2.0.0/.claude/commands/productize-workflow.md +8 -0
  11. ticketwright-2.0.0/.claude/commands/qc-review.md +8 -0
  12. ticketwright-2.0.0/.claude/commands/recall.md +8 -0
  13. ticketwright-2.0.0/.claude/commands/start-ticket.md +8 -0
  14. ticketwright-2.0.0/.claude/config/stack.example.asana-bq.yaml +67 -0
  15. ticketwright-2.0.0/.claude/config/stack.example.azure.yaml +73 -0
  16. ticketwright-2.0.0/.claude/config/stack.schema.md +76 -0
  17. ticketwright-2.0.0/.claude/config/stack.yaml +76 -0
  18. ticketwright-2.0.0/.claude/hooks/db_write_guard.py +145 -0
  19. ticketwright-2.0.0/.claude/hooks/regenerate_ticket_index.py +74 -0
  20. ticketwright-2.0.0/.claude/hooks/session_context.py +78 -0
  21. ticketwright-2.0.0/.claude/hooks/ticket_index_context.py +99 -0
  22. ticketwright-2.0.0/.claude/settings.json.tmpl +59 -0
  23. ticketwright-2.0.0/.claude/skills/productize/SKILL.md +58 -0
  24. ticketwright-2.0.0/.claude/skills/productize/authoring.md +36 -0
  25. ticketwright-2.0.0/.claude/skills/refresh/SKILL.md +40 -0
  26. ticketwright-2.0.0/.claude/skills/refresh/context-pack.md +34 -0
  27. ticketwright-2.0.0/.claude/skills/refresh/index.md +45 -0
  28. ticketwright-2.0.0/.claude/skills/review/SKILL.md +76 -0
  29. ticketwright-2.0.0/.claude/skills/setup/SKILL.md +59 -0
  30. ticketwright-2.0.0/.claude/skills/setup/adopt.md +43 -0
  31. ticketwright-2.0.0/.claude/skills/setup/scaffold.md +38 -0
  32. ticketwright-2.0.0/.claude/skills/setup/teammate.md +33 -0
  33. ticketwright-2.0.0/.claude/skills/ship/SKILL.md +53 -0
  34. ticketwright-2.0.0/.claude/skills/spec-and-build/SKILL.md +54 -0
  35. ticketwright-2.0.0/.claude/skills/ticket/SKILL.md +59 -0
  36. ticketwright-2.0.0/.claude/skills/ticket/priming.md +47 -0
  37. ticketwright-2.0.0/.claude/statusline.sh +22 -0
  38. ticketwright-2.0.0/.gitignore +35 -0
  39. ticketwright-2.0.0/CHANGELOG.md +208 -0
  40. ticketwright-2.0.0/LICENSE +21 -0
  41. ticketwright-2.0.0/PKG-INFO +139 -0
  42. ticketwright-2.0.0/README.md +115 -0
  43. ticketwright-2.0.0/adapters/README.md +103 -0
  44. ticketwright-2.0.0/adapters/chat/slack.md +46 -0
  45. ticketwright-2.0.0/adapters/chat/teams.md +38 -0
  46. ticketwright-2.0.0/adapters/docstore/gdrive.md +39 -0
  47. ticketwright-2.0.0/adapters/docstore/sharepoint.md +41 -0
  48. ticketwright-2.0.0/adapters/tracker/asana.md +55 -0
  49. ticketwright-2.0.0/adapters/tracker/azure-devops.md +68 -0
  50. ticketwright-2.0.0/adapters/tracker/github-issues.md +59 -0
  51. ticketwright-2.0.0/adapters/tracker/jira.md +71 -0
  52. ticketwright-2.0.0/adapters/tracker/linear.md +56 -0
  53. ticketwright-2.0.0/adapters/tracker/monday.md +62 -0
  54. ticketwright-2.0.0/adapters/vcs/azure-repos.md +58 -0
  55. ticketwright-2.0.0/adapters/vcs/github.md +55 -0
  56. ticketwright-2.0.0/adapters/vcs/gitlab.md +52 -0
  57. ticketwright-2.0.0/adapters/warehouse/bigquery.md +44 -0
  58. ticketwright-2.0.0/adapters/warehouse/databricks.md +58 -0
  59. ticketwright-2.0.0/adapters/warehouse/postgres.md +44 -0
  60. ticketwright-2.0.0/adapters/warehouse/redshift.md +49 -0
  61. ticketwright-2.0.0/adapters/warehouse/snowflake.md +54 -0
  62. ticketwright-2.0.0/adapters/warehouse/synapse.md +52 -0
  63. ticketwright-2.0.0/bin/build_ticket_index.py +498 -0
  64. ticketwright-2.0.0/bin/enrich_ticket.py +138 -0
  65. ticketwright-2.0.0/bin/ingest_index_records.py +149 -0
  66. ticketwright-2.0.0/bin/recall.py +268 -0
  67. ticketwright-2.0.0/bin/render.sh +54 -0
  68. ticketwright-2.0.0/bin/render_and_validate.sh +139 -0
  69. ticketwright-2.0.0/bin/selftest.sh +408 -0
  70. ticketwright-2.0.0/bin/split_and_export.sh +138 -0
  71. ticketwright-2.0.0/bin/verify_stack.sh +74 -0
  72. ticketwright-2.0.0/pyproject.toml +57 -0
  73. ticketwright-2.0.0/templates/AGENTS.md.tmpl +74 -0
  74. ticketwright-2.0.0/templates/gitignore.tmpl +35 -0
  75. ticketwright-2.0.0/templates/plan.md.tmpl +22 -0
  76. ticketwright-2.0.0/templates/productized-skill/SKILL.md.tmpl +67 -0
  77. ticketwright-2.0.0/templates/productized-skill/bin/drift_check.sh +30 -0
  78. ticketwright-2.0.0/templates/productized-skill/golden/example.json +13 -0
  79. ticketwright-2.0.0/templates/productized-skill/sql/qc.sql.tmpl +21 -0
  80. ticketwright-2.0.0/templates/productized-skill/sql/step.sql.tmpl +20 -0
  81. ticketwright-2.0.0/templates/productized-skill/templates/README.md.tmpl +20 -0
  82. ticketwright-2.0.0/templates/productized-skill/templates/tracker_comment.txt.tmpl +14 -0
  83. ticketwright-2.0.0/templates/roles/analyst.md +6 -0
  84. ticketwright-2.0.0/templates/roles/engineer.md +6 -0
  85. ticketwright-2.0.0/templates/roles/generalist.md +3 -0
  86. ticketwright-2.0.0/templates/roles/scientist.md +5 -0
  87. ticketwright-2.0.0/templates/spec.md.tmpl +34 -0
  88. ticketwright-2.0.0/templates/ticket-README.md.tmpl +29 -0
  89. ticketwright-2.0.0/ticketwright/__init__.py +9 -0
  90. ticketwright-2.0.0/ticketwright/cli.py +115 -0
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: qc-reviewer
3
+ description: Independent, read-only quality reviewer for a ticket's deliverables. Re-runs queries via the configured warehouse adapter, walks the validation pyramid, sweeps anti-patterns, and returns an APPROVE / REQUEST-CHANGES verdict. Spawn from /review (or before any ship) for a true second-context pass. Tool-agnostic via stack.yaml.
4
+ tools: Read, Bash, Glob, Grep
5
+ ---
6
+
7
+ # QC Reviewer (sub-agent)
8
+
9
+ You are an **independent** reviewer with fresh context — you did not write this work. Your job is to
10
+ verify a ticket's deliverables and return a clear verdict, not to fix code (the build owns fixes).
11
+ You re-run things yourself; you do not trust the author's claimed numbers.
12
+
13
+ ## Setup
14
+ 1. Read `.claude/config/stack.yaml`. Load `seams.warehouse.adapter` and its `dialect_notes`
15
+ (function names, sizing model, dedup idiom, cast/filter rules, dialect anti-patterns). If no
16
+ warehouse seam, review is code/output/doc only.
17
+ 2. Read the ticket README, the spec (if any), and list `final_deliverables/` + `qc_queries/`.
18
+
19
+ ## Walk the validation pyramid (cheap→expensive, automated→human)
20
+ 1. **Dialect lint** (per `dialect_notes`): `=NULL` vs `IS NULL`; unguarded division; `SELECT *` in
21
+ deliverables; functions on filtered columns; cross-source type mismatch (missing cast); hardcoded
22
+ values that should be params; `LEFT JOIN` predicate misplaced into `WHERE`; `NOT IN` with nullable
23
+ columns; `UNION` vs `UNION ALL`; missing required schema/instance filters.
24
+ 2. **Counts & dedup** (re-run independently via the adapter's `query`): re-derive the row count and
25
+ compare to the documented one; **duplicate detection is the primary test** — `COUNT(*)` vs
26
+ `COUNT(DISTINCT <grain key>)`; NULL-rate on key columns; value-range sanity.
27
+ 3. **Cross-source reconciliation**: join-match rates (quantify unmatched `LEFT JOIN` rows);
28
+ date ranges within scope; totals reconciled against the source of truth within tolerance.
29
+ 4. **Independent re-run + anti-pattern sweep**: re-execute the main deliverable end-to-end; diff to
30
+ the committed output (byte-level for CSVs — deterministic outputs need explicit `ORDER BY`). Sweep
31
+ correctness / performance / data-quality / dialect / maintainability anti-patterns; classify each
32
+ Critical / Should-fix / Review.
33
+ 5. **Output & docs**: CSV headers row 1, no preamble/blank rows, filenames carry record counts;
34
+ README has assumptions enumerated + QC results + business context.
35
+
36
+ ## Tiers
37
+ Hard-halt: Critical findings, count mismatch, duplicate-gap, reconciliation break → **REQUEST-CHANGES**.
38
+ Warn: performance/style → list, don't block. Info: distributions → record.
39
+
40
+ ## Output (return this; it is the tool result, not a chat message)
41
+ ```
42
+ ## QC Review — <TICKET-ID>
43
+ Verdict: APPROVE | REQUEST-CHANGES
44
+ Pyramid: lint <ok/n> · counts&dedup <ok/n> · reconcile <ok/n> · re-run-diff <ok/n> · output&docs <ok/n>
45
+ Findings:
46
+ - [Critical|Should-fix|Review] <file:line> — <what> — <remediation>
47
+ Verification queries run:
48
+ - <each query you executed independently>
49
+ ```
50
+ Read-only: never edit code, never post anything, never approve a merge — that's the human's call.
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /refresh context instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /build-context-pack (renamed in v2.0)
6
+
7
+ This command is now part of `/refresh context`. Invoke the `refresh` skill with the same arguments: $ARGUMENTS
8
+ Pass `context` mode.
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /refresh index instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /build-ticket-index (renamed in v2.0)
6
+
7
+ This command is now part of `/refresh index`. Invoke the `refresh` skill with the same arguments: $ARGUMENTS
8
+ Pass `index` mode.
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /setup instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /configure-workspace (renamed in v2.0)
6
+
7
+ This command is now part of `/setup`. Invoke the `setup` skill with the same arguments: $ARGUMENTS
8
+
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /ship instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /deliver-ticket (renamed in v2.0)
6
+
7
+ This command is now part of `/ship`. Invoke the `ship` skill with the same arguments: $ARGUMENTS
8
+
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /setup --teammate instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /onboard-teammate (renamed in v2.0)
6
+
7
+ This command is now part of `/setup --teammate`. Invoke the `setup` skill with the same arguments: $ARGUMENTS
8
+ Pass `--teammate` mode.
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /ticket instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /prime-domain (renamed in v2.0)
6
+
7
+ This command is now part of `/ticket`. Invoke the `ticket` skill with the same arguments: $ARGUMENTS
8
+ Context priming now runs automatically inside `/ticket` (slice details: `skills/ticket/priming.md`).
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /ticket instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /prime-ticket (renamed in v2.0)
6
+
7
+ This command is now part of `/ticket`. Invoke the `ticket` skill with the same arguments: $ARGUMENTS
8
+ Context priming now runs automatically inside `/ticket` (slice details: `skills/ticket/priming.md`).
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /ticket instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /prime-warehouse (renamed in v2.0)
6
+
7
+ This command is now part of `/ticket`. Invoke the `ticket` skill with the same arguments: $ARGUMENTS
8
+ Context priming now runs automatically inside `/ticket` (slice details: `skills/ticket/priming.md`).
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /productize instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /productize-workflow (renamed in v2.0)
6
+
7
+ This command is now part of `/productize`. Invoke the `productize` skill with the same arguments: $ARGUMENTS
8
+
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /review instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /qc-review (renamed in v2.0)
6
+
7
+ This command is now part of `/review`. Invoke the `review` skill with the same arguments: $ARGUMENTS
8
+
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /ticket --recall instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /recall (renamed in v2.0)
6
+
7
+ This command is now part of `/ticket --recall`. Invoke the `ticket` skill with the same arguments: $ARGUMENTS
8
+ Pass `--recall` mode (standalone prior-art lookup, no workspace setup).
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: Deprecated v1 alias — use /ticket instead. Will be removed in v3.
3
+ ---
4
+
5
+ # /start-ticket (renamed in v2.0)
6
+
7
+ This command is now part of `/ticket`. Invoke the `ticket` skill with the same arguments: $ARGUMENTS
8
+
@@ -0,0 +1,67 @@
1
+ # stack.example.asana-bq.yaml — ABSTRACTION PROOF (hypothetical repo).
2
+ # A completely different stack — Asana + BigQuery + Teams + SharePoint + GitLab — with the SAME
3
+ # skills and SAME verb contract. Nothing in .claude/skills/** changes; only this file + adapters do.
4
+ # Try it: bash bin/verify_stack.sh .claude/config/stack.example.asana-bq.yaml --dry-run
5
+
6
+ project:
7
+ key_prefix: GROW # a tag added to task names for branch parity
8
+ assignee_dir: ana
9
+ ticket_path: "tickets/{assignee}/{id}"
10
+ ticket_subdirs: [source_materials, final_deliverables, qc_queries, exploratory_analysis]
11
+ default_epic: null # Asana has no Epics; create_ticket uses a project section
12
+ terminal_status: Done
13
+ word_limits: {tracker_comment: 120, chat: 120, pr: 200, ticket: 200}
14
+
15
+ seams:
16
+ tracker:
17
+ tool: asana
18
+ adapter: adapters/tracker/asana.md
19
+ transport: mcp
20
+ workspace_gid: "1122334455"
21
+ default_project_gid: "9988776655"
22
+ verify: null # MCP-only
23
+
24
+ warehouse:
25
+ tool: bigquery
26
+ adapter: adapters/warehouse/bigquery.md
27
+ transport: cli
28
+ project: growth-analytics-prod
29
+ dataset: analytics
30
+ dev_dataset: analytics_dev
31
+ verify: "bq query --use_legacy_sql=false --dry_run 'SELECT 1'"
32
+
33
+ chat:
34
+ tool: teams
35
+ adapter: adapters/chat/teams.md
36
+ transport: mcp
37
+ channel: "Data Team / General"
38
+ default_mode: draft
39
+ always_include: [Ana]
40
+ verify: null
41
+
42
+ docstore:
43
+ tool: sharepoint
44
+ adapter: adapters/docstore/sharepoint.md
45
+ transport: cli
46
+ base_path: "/Users/ana/Library/CloudStorage/OneDrive-Growth/Data Tickets"
47
+ verify: "test -d \"{base_path}\""
48
+
49
+ vcs:
50
+ tool: gitlab
51
+ adapter: adapters/vcs/gitlab.md
52
+ transport: cli
53
+ default_branch: main
54
+ semantic_pr: true
55
+ worktree_root: ".worktrees"
56
+ verify: "glab auth status"
57
+
58
+ policies:
59
+ hard_halt_before_external_posts: true
60
+ db_write_requires_approval: true
61
+ chat_default_draft: true
62
+ hyperlink_everything: true
63
+ commandify_everything: true
64
+ reduce_assumptions: true
65
+ commit_plan_before_implement: true
66
+ system_evolution: true
67
+ deterministic_outputs: true
@@ -0,0 +1,73 @@
1
+ # stack.example.azure.yaml — ABSTRACTION PROOF (hypothetical Microsoft-stack repo).
2
+ # Azure DevOps (Boards) + Synapse + Teams + SharePoint + Azure Repos — the SAME skills and SAME
3
+ # verb contract as the Jira/Snowflake and Asana/BigQuery examples. Only this file + adapters change.
4
+ # Try it: bash bin/verify_stack.sh .claude/config/stack.example.azure.yaml --dry-run
5
+
6
+ project:
7
+ key_prefix: ENG # branch/index display convention (ADO ids are bare integers)
8
+ key_prefixes: [ENG]
9
+ assignee_dir: dana
10
+ ticket_path: "tickets/{assignee}/{id}"
11
+ ticket_subdirs: [source_materials, final_deliverables, qc_queries, exploratory_analysis]
12
+ default_epic: 12 # Azure Boards work-item id (integer) for the parent epic
13
+ terminal_status: Closed # Agile process "done" state
14
+ ticket_url_template: "https://dev.azure.com/acme-corp/Platform/_workitems/edit/{number}" # ADO ids are integers → use {number}, not {id}
15
+ word_limits: {tracker_comment: 100, chat: 100, pr: 200, ticket: 200}
16
+
17
+ seams:
18
+ tracker:
19
+ tool: azure-devops
20
+ adapter: adapters/tracker/azure-devops.md
21
+ transport: cli
22
+ org: acme-corp
23
+ project: Platform
24
+ done_state: Closed
25
+ verify: "az boards work-item show --id {default_epic}"
26
+
27
+ warehouse:
28
+ tool: synapse
29
+ adapter: adapters/warehouse/synapse.md
30
+ transport: cli
31
+ server: acme-synapse-ondemand.sql.azuresynapse.net
32
+ database: analytics
33
+ dev_schema: dev
34
+ verify: "sqlcmd -S {server} -d {database} -G --authentication-method ActiveDirectoryDefault -Q \"SELECT 1\""
35
+
36
+ chat:
37
+ tool: teams
38
+ adapter: adapters/chat/teams.md
39
+ transport: mcp
40
+ channel: "Data Platform / General"
41
+ default_mode: draft
42
+ always_include: [Dana]
43
+ verify: null
44
+
45
+ docstore:
46
+ tool: sharepoint
47
+ adapter: adapters/docstore/sharepoint.md
48
+ transport: cli
49
+ base_path: "/Users/dana/Library/CloudStorage/OneDrive-AcmeCorp/Data Tickets"
50
+ verify: "test -d \"{base_path}\""
51
+
52
+ vcs:
53
+ tool: azure-repos
54
+ adapter: adapters/vcs/azure-repos.md
55
+ transport: cli
56
+ org: acme-corp
57
+ project: Platform
58
+ repo: analytics
59
+ default_branch: main
60
+ semantic_pr: true
61
+ worktree_root: ".worktrees"
62
+ verify: "az repos list --project {project} --query \"[].name\""
63
+
64
+ policies:
65
+ hard_halt_before_external_posts: true
66
+ db_write_requires_approval: true
67
+ chat_default_draft: true
68
+ hyperlink_everything: true
69
+ commandify_everything: true
70
+ reduce_assumptions: true
71
+ commit_plan_before_implement: true
72
+ system_evolution: true
73
+ deterministic_outputs: true
@@ -0,0 +1,76 @@
1
+ # `stack.yaml` — the tool registry (schema + policies)
2
+
3
+ `stack.yaml` is the **single source of truth** for which concrete tool fills each abstract "seam"
4
+ and for the project facts the skills need. Skills never hardcode `acli`, `snow`, `slack`, channel
5
+ IDs, epics, or paths — those live **here** and in the per-tool adapters. Swapping Jira→Asana or
6
+ Snowflake→BigQuery means editing this file and pointing at a different adapter; **no skill changes.**
7
+
8
+ The `setup` skill writes this file by interviewing you and detecting installed tooling.
9
+ `bin/verify_stack.sh` reads it to smoke-test every seam. Every skill reads it at preflight.
10
+
11
+ ---
12
+
13
+ ## Top-level shape
14
+
15
+ ```yaml
16
+ project: # facts about this workspace, tool-independent
17
+ seams: # one entry per abstract seam → concrete tool + adapter + verify
18
+ policies: # behavioral rules every skill inherits (the kit's "global rules")
19
+ ```
20
+
21
+ ---
22
+
23
+ ## `project`
24
+
25
+ | Field | Type | Example | Meaning |
26
+ |---|---|---|---|
27
+ | `key_prefix` | string | `ENG` | Ticket-ID prefix. Branch names = `{key_prefix}-NNNN`. |
28
+ | `key_prefixes` | list | `[ENG]` | Prefixes the ticket index recognizes in folder names. Optional; defaults to `[key_prefix]`. Use when one repo holds tickets from several trackers (e.g. `[ENG, OPS]`). |
29
+ | `assignee_dir` | string | `alice` | Default owner subdir under `tickets/`. |
30
+ | `ticket_path` | template | `tickets/{assignee}/{id}` | Where a ticket folder lives. `{assignee}` `{id}` tokens. |
31
+ | `ticket_subdirs` | list | `[source_materials, final_deliverables, qc_queries, exploratory_analysis]` | Scaffolded per ticket. |
32
+ | `default_epic` | string \| null | `ENG-100` | Parent epic for newly created tickets (null if tracker has no epics). |
33
+ | `terminal_status` | string | `Done` | The "done" workflow state (not always "Done"). |
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
+ | `word_limits` | map | `{tracker_comment: 100, chat: 100, pr: 200, ticket: 200}` | Hard caps the comms skills enforce. |
36
+
37
+ ## `seams`
38
+
39
+ Exactly these five keys: `tracker`, `warehouse`, `chat`, `docstore`, `vcs`. Each:
40
+
41
+ | Field | Type | Meaning |
42
+ |---|---|---|
43
+ | `tool` | string | The concrete tool, e.g. `jira` / `asana` / `monday` / `linear`. |
44
+ | `adapter` | path | The playbook that maps the verb contract → this tool's commands. |
45
+ | `verify` | string \| null | A **read-only** smoke-test command. `{token}` interpolation from this seam's own keys + `project`. `null` = skip (skills warn). Non-zero exit ⇒ seam "unreachable". |
46
+ | `transport` | enum | `cli` \| `mcp` \| `both` — how the adapter talks to the tool. Drives the verify fallback. |
47
+ | *(extra keys)* | any | Tool-specific config the adapter reads (site, warehouse, role, channel, base_path, …). |
48
+
49
+ The `warehouse` seam may also be `null`/omitted for non-data repos — `review`, `spec-and-build`,
50
+ and `refresh context` degrade gracefully (skip warehouse steps) when it is.
51
+
52
+ ## `policies` (the 9 kit policies — see kit README "AI-layer" section)
53
+
54
+ | Policy | Default | Enforced by |
55
+ |---|---|---|
56
+ | `hard_halt_before_external_posts` | `true` | `ship`, every productized skill — pause for human go before any tracker/chat/docstore write. |
57
+ | `db_write_requires_approval` | `true` | any skill issuing a non-SELECT — show SQL, explain, wait for `yes`. |
58
+ | `chat_default_draft` | `true` | `chat.draft` not `chat.send` unless the user says "send it". |
59
+ | `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
+ | `reduce_assumptions` | `true` | ask before building; still document every assumption in the ticket README. |
62
+ | `commit_plan_before_implement` | `true` | `spec-and-build` commits the spec/plan artifact before `build` (blame-free retry). |
63
+ | `system_evolution` | `true` | `ship` retro: a failure fixes the AI layer (rule/context/command/adapter), not just the ticket. |
64
+ | `deterministic_outputs` | `true` | data exports use explicit `ORDER BY`; productized skills ship golden-replay diffs. |
65
+
66
+ `always_include` (under `seams.chat`) — names always added to a chat message (e.g. `[Alice]`); the
67
+ "never solo-DM a stakeholder" rule.
68
+
69
+ ---
70
+
71
+ ## Worked example
72
+
73
+ A worked example lives at [`stack.yaml`](stack.yaml) (Jira/Snowflake/Slack/Drive/GitHub). Two more
74
+ prove the abstraction holds with zero skill edits: `stack.example.asana-bq.yaml`
75
+ (Asana/BigQuery/Teams/SharePoint/GitLab) and `stack.example.azure.yaml`
76
+ (Azure DevOps/Synapse/Teams/SharePoint/Azure Repos). To validate any config: `bash bin/verify_stack.sh`.
@@ -0,0 +1,76 @@
1
+ # stack.yaml — the tool registry + project facts + policies (single source of truth).
2
+ #
3
+ # This is a WORKED EXAMPLE for a fictional "Acme" data team (Jira / Snowflake / Slack /
4
+ # Google Drive / GitHub). Replace the values with your own, or run `/setup`
5
+ # to generate this file by detecting your tooling and interviewing you.
6
+ # Field docs: stack.schema.md · Validate: bash bin/verify_stack.sh .claude/config/stack.yaml
7
+ # Two more examples live alongside this file: stack.example.asana-bq.yaml (Asana/BigQuery/Teams/
8
+ # SharePoint/GitLab) and stack.example.azure.yaml (Azure DevOps/Synapse/Teams/SharePoint/Azure Repos).
9
+
10
+ project:
11
+ key_prefix: ENG # ticket-ID prefix; branch names = {key_prefix}-NNNN
12
+ key_prefixes: [ENG] # prefixes the ticket index recognizes (defaults to [key_prefix])
13
+ assignee_dir: alice # default owner subdir under tickets/
14
+ ticket_path: "tickets/{assignee}/{id}"
15
+ ticket_subdirs: [source_materials, final_deliverables, qc_queries, exploratory_analysis]
16
+ default_epic: ENG-100 # parent epic for new tickets (or null if the tracker has no epics)
17
+ terminal_status: Done # the workflow's "done" state (not always "Done")
18
+ ticket_url_template: "https://acme.atlassian.net/browse/{id}" # how INDEX.md links a ticket ({id} token); omit/null = no link
19
+ word_limits: {tracker_comment: 100, chat: 100, pr: 200, ticket: 200}
20
+
21
+ seams:
22
+ tracker:
23
+ tool: jira
24
+ adapter: adapters/tracker/jira.md
25
+ transport: both
26
+ site: acme.atlassian.net
27
+ cli: acli
28
+ mcp: atlassian
29
+ verify: "acli jira workitem view {default_epic}"
30
+
31
+ warehouse:
32
+ tool: snowflake
33
+ adapter: adapters/warehouse/snowflake.md
34
+ transport: both
35
+ cli: snow
36
+ default_warehouse: ANALYTICS_WH
37
+ pii_role: PII_READER
38
+ dev_db: ANALYTICS_DEV
39
+ verify: "snow connection test"
40
+
41
+ chat:
42
+ tool: slack
43
+ adapter: adapters/chat/slack.md
44
+ transport: mcp
45
+ mcp: slack
46
+ default_channel: C0XXXXXXXXX # your channel's ID
47
+ default_mode: draft
48
+ always_include: [Alice] # names always added to a chat message (never solo-DM a stakeholder)
49
+ verify: null # MCP-only: skills check reachability via an MCP call, not the shell
50
+
51
+ docstore:
52
+ tool: gdrive
53
+ adapter: adapters/docstore/gdrive.md
54
+ transport: cli
55
+ base_path: "/path/to/your/Shared drives/Tickets"
56
+ verify: "test -d \"{base_path}\""
57
+
58
+ vcs:
59
+ tool: github
60
+ adapter: adapters/vcs/github.md
61
+ transport: cli
62
+ default_branch: main
63
+ semantic_pr: true
64
+ worktree_root: ".claude/worktrees"
65
+ verify: "gh auth status"
66
+
67
+ policies:
68
+ hard_halt_before_external_posts: true
69
+ db_write_requires_approval: true
70
+ chat_default_draft: true
71
+ hyperlink_everything: true
72
+ commandify_everything: true
73
+ reduce_assumptions: true
74
+ commit_plan_before_implement: true
75
+ system_evolution: true
76
+ deterministic_outputs: true
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env python3
2
+ """PreToolUse hook — mechanical enforcement of the `db_write_requires_approval` policy.
3
+
4
+ The starter kit's policies are only as good as the agent's memory unless something
5
+ enforces them. This hook makes the DB-write rule mechanical: when a Bash tool call
6
+ invokes a configured warehouse CLI with a *destructive* statement (CREATE/ALTER/DROP/
7
+ DELETE/UPDATE/INSERT/TRUNCATE/MERGE/GRANT/REVOKE/REPLACE), it returns an `ask`
8
+ permission decision so the human must confirm — exactly the "show SQL → explain →
9
+ wait for yes" protocol, applied by the runtime rather than trusted to the model.
10
+
11
+ Read-only statements (SELECT/DESCRIBE/SHOW/EXPLAIN/WITH/LIST/GET_DDL) pass straight
12
+ through. Non-warehouse Bash and non-Bash tools pass through untouched.
13
+
14
+ Wire it in settings.json:
15
+ "hooks": { "PreToolUse": [ { "matcher": "Bash",
16
+ "hooks": [ { "type": "command", "command": "python3 .claude/hooks/db_write_guard.py" } ] } ] }
17
+
18
+ Input (stdin): Claude Code PreToolUse JSON { tool_name, tool_input:{command}, cwd, ... }
19
+ Output (stdout): on a destructive write, the permissionDecision JSON below; otherwise nothing.
20
+ Stdlib only. Always exits 0 — a guard must never crash a session (fail-open, but it
21
+ only ever *adds* a confirmation, never bypasses one).
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import os
27
+ import re
28
+ import sys
29
+ from pathlib import Path
30
+
31
+ # CLIs that talk to a data warehouse / database. Extended from stack.yaml if present.
32
+ DEFAULT_WAREHOUSE_CLIS = ["snow", "snowsql", "bq", "databricks", "dbsqlcli", "psql", "mysql", "sqlcmd", "duckdb", "redshift-data"]
33
+
34
+ DESTRUCTIVE = re.compile(
35
+ r"\b(CREATE\s+OR\s+REPLACE|CREATE|ALTER|DROP|DELETE|UPDATE|INSERT|TRUNCATE|MERGE|GRANT|REVOKE|REPLACE\s+INTO)\b",
36
+ re.IGNORECASE,
37
+ )
38
+
39
+
40
+ def find_stack_yaml(cwd: str) -> Path | None:
41
+ candidates = []
42
+ if os.environ.get("CLAUDE_PROJECT_DIR"):
43
+ candidates.append(Path(os.environ["CLAUDE_PROJECT_DIR"]) / ".claude/config/stack.yaml")
44
+ if cwd:
45
+ candidates.append(Path(cwd) / ".claude/config/stack.yaml")
46
+ # hook lives at <kit>/.claude/hooks/ → config is a sibling
47
+ candidates.append(Path(__file__).resolve().parent.parent / "config/stack.yaml")
48
+ for c in candidates:
49
+ if c.is_file():
50
+ return c
51
+ return None
52
+
53
+
54
+ def warehouse_clis(stack: Path | None) -> list[str]:
55
+ clis = list(DEFAULT_WAREHOUSE_CLIS)
56
+ if stack:
57
+ try:
58
+ text = stack.read_text(errors="replace")
59
+ # tiny scan: a `cli: <name>` line under the warehouse seam (no yaml dep)
60
+ m = re.search(r"warehouse:.*?(?:\n\s+cli:\s*([A-Za-z0-9_-]+))", text, re.DOTALL)
61
+ if m and m.group(1) not in clis:
62
+ clis.insert(0, m.group(1))
63
+ except OSError:
64
+ pass
65
+ return clis
66
+
67
+
68
+ def invokes_warehouse(command: str, clis: list[str]) -> str | None:
69
+ for cli in clis:
70
+ # word-boundary match so "show" doesn't match inside another word
71
+ if re.search(rf"(^|[\s;&|(]){re.escape(cli)}(\s|$)", command):
72
+ return cli
73
+ return None
74
+
75
+
76
+ # SQL can live in a file rather than the command line — via -f/--file/--filename, OR via a shell
77
+ # stdin redirect (`psql db < deploy.sql`). Scan both so a destructive statement can't slip past.
78
+ _FILE_FLAG = re.compile(r"(?:-f|-i|--file|--filename|--input-file)[=\s]+([^\s;|&]+)")
79
+ _STDIN_REDIR = re.compile(r"<\s*([^\s;|&<>]+)")
80
+
81
+
82
+ def referenced_sql(command: str, cwd: str) -> str:
83
+ """Concatenate the text of any SQL files the command runs via -f/--filename or `< file`, so the
84
+ destructive scan sees `snow sql -f deploy.sql` / `psql < deploy.sql` content too. Size-capped."""
85
+ text = ""
86
+ for raw in _FILE_FLAG.findall(command) + _STDIN_REDIR.findall(command):
87
+ p = Path(raw)
88
+ if not p.is_absolute() and cwd:
89
+ p = Path(cwd) / raw
90
+ try:
91
+ if p.is_file() and p.stat().st_size < 1_000_000:
92
+ text += "\n" + p.read_text(errors="replace")
93
+ except OSError:
94
+ continue
95
+ return text
96
+
97
+
98
+ def emit_ask(reason: str) -> None:
99
+ print(json.dumps({
100
+ "hookSpecificOutput": {
101
+ "hookEventName": "PreToolUse",
102
+ "permissionDecision": "ask",
103
+ "permissionDecisionReason": reason,
104
+ }
105
+ }))
106
+
107
+
108
+ def main() -> int:
109
+ try:
110
+ payload = json.load(sys.stdin)
111
+ except (json.JSONDecodeError, ValueError):
112
+ return 0 # not invoked as a hook / no payload — do nothing
113
+
114
+ if payload.get("tool_name") != "Bash":
115
+ return 0
116
+ command = (payload.get("tool_input") or {}).get("command", "") or ""
117
+ if not command.strip():
118
+ return 0
119
+
120
+ stack = find_stack_yaml(payload.get("cwd", ""))
121
+ clis = warehouse_clis(stack)
122
+
123
+ cli = invokes_warehouse(command, clis)
124
+ if not cli:
125
+ return 0 # not a warehouse command
126
+
127
+ # Scan the inline command AND any SQL files it runs via -f/--filename.
128
+ scan_text = command + referenced_sql(command, payload.get("cwd", ""))
129
+
130
+ verb_match = DESTRUCTIVE.search(scan_text)
131
+ if not verb_match:
132
+ return 0 # read-only / non-destructive — let it through
133
+
134
+ verb = verb_match.group(1).upper()
135
+ reason = (
136
+ f"db_write_requires_approval: this `{cli}` command contains a destructive statement "
137
+ f"({verb}). Per the kit's policy, confirm the exact SQL and target environment before "
138
+ f"running — show what it changes and proceed only on explicit approval."
139
+ )
140
+ emit_ask(reason)
141
+ return 0
142
+
143
+
144
+ if __name__ == "__main__":
145
+ sys.exit(main())