ticketwright 2.0.0__py3-none-any.whl

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 (89) hide show
  1. ticketwright/__init__.py +9 -0
  2. ticketwright/_kit/.claude/agents/qc-reviewer.md +50 -0
  3. ticketwright/_kit/.claude/commands/build-context-pack.md +8 -0
  4. ticketwright/_kit/.claude/commands/build-ticket-index.md +8 -0
  5. ticketwright/_kit/.claude/commands/configure-workspace.md +8 -0
  6. ticketwright/_kit/.claude/commands/deliver-ticket.md +8 -0
  7. ticketwright/_kit/.claude/commands/onboard-teammate.md +8 -0
  8. ticketwright/_kit/.claude/commands/prime-domain.md +8 -0
  9. ticketwright/_kit/.claude/commands/prime-ticket.md +8 -0
  10. ticketwright/_kit/.claude/commands/prime-warehouse.md +8 -0
  11. ticketwright/_kit/.claude/commands/productize-workflow.md +8 -0
  12. ticketwright/_kit/.claude/commands/qc-review.md +8 -0
  13. ticketwright/_kit/.claude/commands/recall.md +8 -0
  14. ticketwright/_kit/.claude/commands/start-ticket.md +8 -0
  15. ticketwright/_kit/.claude/config/stack.example.asana-bq.yaml +67 -0
  16. ticketwright/_kit/.claude/config/stack.example.azure.yaml +73 -0
  17. ticketwright/_kit/.claude/config/stack.schema.md +76 -0
  18. ticketwright/_kit/.claude/config/stack.yaml +76 -0
  19. ticketwright/_kit/.claude/hooks/db_write_guard.py +145 -0
  20. ticketwright/_kit/.claude/hooks/regenerate_ticket_index.py +74 -0
  21. ticketwright/_kit/.claude/hooks/session_context.py +78 -0
  22. ticketwright/_kit/.claude/hooks/ticket_index_context.py +99 -0
  23. ticketwright/_kit/.claude/settings.json.tmpl +59 -0
  24. ticketwright/_kit/.claude/skills/productize/SKILL.md +58 -0
  25. ticketwright/_kit/.claude/skills/productize/authoring.md +36 -0
  26. ticketwright/_kit/.claude/skills/refresh/SKILL.md +40 -0
  27. ticketwright/_kit/.claude/skills/refresh/context-pack.md +34 -0
  28. ticketwright/_kit/.claude/skills/refresh/index.md +45 -0
  29. ticketwright/_kit/.claude/skills/review/SKILL.md +76 -0
  30. ticketwright/_kit/.claude/skills/setup/SKILL.md +59 -0
  31. ticketwright/_kit/.claude/skills/setup/adopt.md +43 -0
  32. ticketwright/_kit/.claude/skills/setup/scaffold.md +38 -0
  33. ticketwright/_kit/.claude/skills/setup/teammate.md +33 -0
  34. ticketwright/_kit/.claude/skills/ship/SKILL.md +53 -0
  35. ticketwright/_kit/.claude/skills/spec-and-build/SKILL.md +54 -0
  36. ticketwright/_kit/.claude/skills/ticket/SKILL.md +59 -0
  37. ticketwright/_kit/.claude/skills/ticket/priming.md +47 -0
  38. ticketwright/_kit/.claude/statusline.sh +22 -0
  39. ticketwright/_kit/adapters/README.md +103 -0
  40. ticketwright/_kit/adapters/chat/slack.md +46 -0
  41. ticketwright/_kit/adapters/chat/teams.md +38 -0
  42. ticketwright/_kit/adapters/docstore/gdrive.md +39 -0
  43. ticketwright/_kit/adapters/docstore/sharepoint.md +41 -0
  44. ticketwright/_kit/adapters/tracker/asana.md +55 -0
  45. ticketwright/_kit/adapters/tracker/azure-devops.md +68 -0
  46. ticketwright/_kit/adapters/tracker/github-issues.md +59 -0
  47. ticketwright/_kit/adapters/tracker/jira.md +71 -0
  48. ticketwright/_kit/adapters/tracker/linear.md +56 -0
  49. ticketwright/_kit/adapters/tracker/monday.md +62 -0
  50. ticketwright/_kit/adapters/vcs/azure-repos.md +58 -0
  51. ticketwright/_kit/adapters/vcs/github.md +55 -0
  52. ticketwright/_kit/adapters/vcs/gitlab.md +52 -0
  53. ticketwright/_kit/adapters/warehouse/bigquery.md +44 -0
  54. ticketwright/_kit/adapters/warehouse/databricks.md +58 -0
  55. ticketwright/_kit/adapters/warehouse/postgres.md +44 -0
  56. ticketwright/_kit/adapters/warehouse/redshift.md +49 -0
  57. ticketwright/_kit/adapters/warehouse/snowflake.md +54 -0
  58. ticketwright/_kit/adapters/warehouse/synapse.md +52 -0
  59. ticketwright/_kit/bin/build_ticket_index.py +498 -0
  60. ticketwright/_kit/bin/enrich_ticket.py +138 -0
  61. ticketwright/_kit/bin/ingest_index_records.py +149 -0
  62. ticketwright/_kit/bin/recall.py +268 -0
  63. ticketwright/_kit/bin/render.sh +54 -0
  64. ticketwright/_kit/bin/render_and_validate.sh +139 -0
  65. ticketwright/_kit/bin/selftest.sh +408 -0
  66. ticketwright/_kit/bin/split_and_export.sh +138 -0
  67. ticketwright/_kit/bin/verify_stack.sh +74 -0
  68. ticketwright/_kit/templates/AGENTS.md.tmpl +74 -0
  69. ticketwright/_kit/templates/gitignore.tmpl +35 -0
  70. ticketwright/_kit/templates/plan.md.tmpl +22 -0
  71. ticketwright/_kit/templates/productized-skill/SKILL.md.tmpl +67 -0
  72. ticketwright/_kit/templates/productized-skill/bin/drift_check.sh +30 -0
  73. ticketwright/_kit/templates/productized-skill/golden/example.json +13 -0
  74. ticketwright/_kit/templates/productized-skill/sql/qc.sql.tmpl +21 -0
  75. ticketwright/_kit/templates/productized-skill/sql/step.sql.tmpl +20 -0
  76. ticketwright/_kit/templates/productized-skill/templates/README.md.tmpl +20 -0
  77. ticketwright/_kit/templates/productized-skill/templates/tracker_comment.txt.tmpl +14 -0
  78. ticketwright/_kit/templates/roles/analyst.md +6 -0
  79. ticketwright/_kit/templates/roles/engineer.md +6 -0
  80. ticketwright/_kit/templates/roles/generalist.md +3 -0
  81. ticketwright/_kit/templates/roles/scientist.md +5 -0
  82. ticketwright/_kit/templates/spec.md.tmpl +34 -0
  83. ticketwright/_kit/templates/ticket-README.md.tmpl +29 -0
  84. ticketwright/cli.py +115 -0
  85. ticketwright-2.0.0.dist-info/METADATA +139 -0
  86. ticketwright-2.0.0.dist-info/RECORD +89 -0
  87. ticketwright-2.0.0.dist-info/WHEEL +4 -0
  88. ticketwright-2.0.0.dist-info/entry_points.txt +2 -0
  89. ticketwright-2.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ """PostToolUse hook — auto-regenerate tickets/INDEX.md when a ticket folder changes.
3
+
4
+ Fires after Write/Edit. If the edited file lives under tickets/ (and isn't the index
5
+ artifacts themselves), it re-renders tickets/INDEX.md so the catalog always reflects
6
+ reality: a new ticket appears immediately (marked ▱ until curated), and an edited README
7
+ flags its row ⚠ (summary may be stale). LLM-authored summaries are *not* regenerated here
8
+ (a hook can't run the model synchronously) — those refresh at ticket close via
9
+ bin/ingest_index_records.py.
10
+
11
+ Deterministic + fast (re-renders from disk + index_data.json). Writes only when the
12
+ rendered output actually changes, so irrelevant edits produce no churn. Fails open.
13
+
14
+ Wire in .claude/settings.json:
15
+ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [
16
+ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/regenerate_ticket_index.py\"" } ] } ] }
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import os
22
+ import sys
23
+ from pathlib import Path
24
+
25
+
26
+ def main() -> int:
27
+ try:
28
+ payload = json.load(sys.stdin)
29
+ except (json.JSONDecodeError, ValueError):
30
+ return 0 # nothing to do
31
+
32
+ tool_input = payload.get("tool_input") or {}
33
+ fp = tool_input.get("file_path") or tool_input.get("path") or ""
34
+ if not fp:
35
+ return 0
36
+
37
+ root = Path(os.environ.get("CLAUDE_PROJECT_DIR") or payload.get("cwd") or ".").resolve()
38
+ tickets_dir = root / "tickets"
39
+ try:
40
+ changed = Path(fp).resolve()
41
+ changed.relative_to(tickets_dir) # raises if not under tickets/
42
+ except (ValueError, OSError):
43
+ return 0 # edit was not under tickets/
44
+
45
+ # Don't react to the *generated* artifacts (else the hook's own write re-triggers it).
46
+ # index_data.json is source, NOT generated — editing the store SHOULD re-render.
47
+ if changed.name in ("INDEX.md", "OBJECTS.md"):
48
+ return 0
49
+
50
+ sys.path.insert(0, str(root / "bin"))
51
+ try:
52
+ from build_ticket_index import build_rows, render, render_objects # type: ignore
53
+ rows = build_rows(root)
54
+ fresh = {tickets_dir / "INDEX.md": render(rows), tickets_dir / "OBJECTS.md": render_objects(rows)}
55
+ except SystemExit:
56
+ return 0 # malformed index_data.json — surfaced when the agent runs the renderer
57
+ except Exception:
58
+ return 0 # fail open
59
+
60
+ try:
61
+ changed_any = False
62
+ for p, txt in fresh.items():
63
+ if (p.read_text() if p.is_file() else None) != txt:
64
+ p.write_bytes(txt.encode("utf-8")); changed_any = True
65
+ if changed_any:
66
+ print("Auto-regenerated tickets/INDEX.md + OBJECTS.md (ticket folder changed). "
67
+ "Run bin/ingest_index_records.py to refresh a ticket's curated summary.")
68
+ except OSError:
69
+ pass
70
+ return 0
71
+
72
+
73
+ if __name__ == "__main__":
74
+ raise SystemExit(main())
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python3
2
+ """SessionStart hook — prime every session with the configured stack + AI-layer index.
3
+
4
+ Prints a compact summary (becomes session additionalContext) so the agent always knows
5
+ which tools are wired, which skills/commands exist, and the lifecycle — without anyone
6
+ having to load all of AGENTS.md. This is the always-on, *tiny* slice of context; the
7
+ `/prime-*` commands load the rest on demand.
8
+
9
+ Wire it in settings.json:
10
+ "hooks": { "SessionStart": [ { "hooks": [
11
+ { "type": "command", "command": "python3 .claude/hooks/session_context.py" } ] } ] }
12
+
13
+ Stdlib only. Fails open (prints nothing) if the kit isn't configured yet.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import re
19
+ import sys
20
+ from pathlib import Path
21
+
22
+
23
+ def project_root() -> Path:
24
+ if os.environ.get("CLAUDE_PROJECT_DIR"):
25
+ return Path(os.environ["CLAUDE_PROJECT_DIR"])
26
+ # hook lives at <root>/.claude/hooks/
27
+ return Path(__file__).resolve().parent.parent.parent
28
+
29
+
30
+ def scan_stack(stack: Path) -> dict:
31
+ text = stack.read_text(errors="replace")
32
+ out = {}
33
+ m = re.search(r"^\s*key_prefix:\s*([A-Za-z0-9_-]+)", text, re.MULTILINE)
34
+ out["key_prefix"] = m.group(1) if m else "?"
35
+ # tool per seam: find " <seam>:" then the nested " tool: <x>"
36
+ for seam in ("tracker", "warehouse", "chat", "docstore", "vcs"):
37
+ m = re.search(rf"^\s{{2}}{seam}:\s*\n(?:\s+.*\n)*?\s+tool:\s*([A-Za-z0-9_-]+)", text, re.MULTILINE)
38
+ out[seam] = m.group(1) if m else "—"
39
+ return out
40
+
41
+
42
+ def main() -> int:
43
+ root = project_root()
44
+ stack = root / ".claude/config/stack.yaml"
45
+ if not stack.is_file():
46
+ return 0 # not configured — say nothing
47
+
48
+ try:
49
+ s = scan_stack(stack)
50
+ except OSError:
51
+ return 0
52
+
53
+ skills = sorted(p.parent.name for p in (root / ".claude/skills").glob("*/SKILL.md")) \
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
+ commands = sorted(
57
+ p.stem for p in (root / ".claude/commands").glob("*.md")
58
+ if "Deprecated" not in p.read_text(errors="replace")[:200]
59
+ ) if (root / ".claude/commands").is_dir() else []
60
+
61
+ lines = [
62
+ "## Ticketwright — session context",
63
+ f"Stack ({s['key_prefix']}-tickets): tracker={s['tracker']} · warehouse={s['warehouse']} · "
64
+ f"chat={s['chat']} · docstore={s['docstore']} · vcs={s['vcs']}.",
65
+ "Lifecycle: /ticket (opens + auto-primes context) → /spec-and-build → /review → /ship.",
66
+ ]
67
+ if skills:
68
+ lines.append("Skills: " + ", ".join(skills) + ".")
69
+ if commands:
70
+ lines.append("Commands: " + ", ".join(commands) + ".")
71
+ lines.append("Policies enforced: DB writes & external posts require approval (db_write_guard hook + "
72
+ "skill hard-halts); chat defaults to draft; outputs deterministic. See AGENTS.md.")
73
+ print("\n".join(lines))
74
+ return 0
75
+
76
+
77
+ if __name__ == "__main__":
78
+ sys.exit(main())
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ """SessionStart hook — surface the ticket index so Claude recalls prior work.
3
+
4
+ Prints a compact block (becomes session additionalContext): the catalog size, a
5
+ status breakdown, and the most recent tickets, plus a pointer to grep the full
6
+ `tickets/INDEX.md` before starting related work. The authoritative *count* comes from
7
+ discovering ticket folders on disk (so a newly-added, not-yet-enriched ticket still
8
+ counts); the per-ticket detail comes from the enriched `tickets/index_data.json`.
9
+ Stdlib only; fails open (prints nothing) on any error.
10
+
11
+ Wire in .claude/settings.json:
12
+ "hooks": { "SessionStart": [ { "hooks": [
13
+ { "type": "command", "command": "python3 .claude/hooks/ticket_index_context.py" } ] } ] }
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import os
19
+ import re
20
+ from pathlib import Path
21
+
22
+ RECENT = 12
23
+ STATUS_ORDER = ["Deployed", "Completed", "In Review", "In Progress", "Blocked", "Unknown"]
24
+
25
+
26
+ def project_root() -> Path:
27
+ if os.environ.get("CLAUDE_PROJECT_DIR"):
28
+ return Path(os.environ["CLAUDE_PROJECT_DIR"])
29
+ return Path(__file__).resolve().parent.parent.parent # .claude/hooks/ -> root
30
+
31
+
32
+ def ticket_number(tid: str) -> int:
33
+ m = re.search(r"-(\d+)", tid)
34
+ return int(m.group(1)) if m else 0
35
+
36
+
37
+ def discovered_total(root: Path) -> int | None:
38
+ """True ticket count from the renderer's discovery (cheap globs, no README reads)."""
39
+ try:
40
+ import sys
41
+ sys.path.insert(0, str(root / "bin"))
42
+ from build_ticket_index import discover # type: ignore
43
+ return len(discover(root))
44
+ except Exception:
45
+ return None
46
+
47
+
48
+ def main() -> int:
49
+ root = project_root()
50
+ data_file = root / "tickets" / "index_data.json"
51
+ index_file = root / "tickets" / "INDEX.md"
52
+
53
+ tickets = []
54
+ if data_file.is_file():
55
+ try:
56
+ loaded = json.loads(data_file.read_text())
57
+ if isinstance(loaded, dict) and isinstance(loaded.get("tickets"), list):
58
+ tickets = [t for t in loaded["tickets"] if isinstance(t, dict)]
59
+ except (OSError, json.JSONDecodeError):
60
+ tickets = []
61
+
62
+ if not tickets:
63
+ # Catalog may exist but not be enriched yet — emit a bare pointer if INDEX.md is there.
64
+ if index_file.is_file():
65
+ print("## Ticket index\nCatalog of prior ticket work: `tickets/INDEX.md` — grep it before "
66
+ "starting related work (same object / stakeholder / report).")
67
+ return 0
68
+
69
+ total = discovered_total(root) or len(tickets)
70
+ by_status: dict[str, int] = {}
71
+ for t in tickets:
72
+ s = t.get("status") or "Unknown"
73
+ by_status[s] = by_status.get(s, 0) + 1
74
+ extra = sorted(s for s in by_status if s not in STATUS_ORDER)
75
+ status_line = " · ".join(f"{s} {by_status[s]}" for s in (STATUS_ORDER + extra) if by_status.get(s))
76
+
77
+ recent = sorted(tickets, key=lambda t: (t.get("date") or "0000-00-00", ticket_number(t.get("id", ""))),
78
+ reverse=True)[:RECENT]
79
+
80
+ lines = [
81
+ "## Ticket index — recall before starting work",
82
+ f"{total} tickets indexed ({status_line}). Full catalog: `tickets/INDEX.md` — "
83
+ "**grep it for prior work on the same object / stakeholder / report before starting a ticket, and reuse it.**",
84
+ f"Most recent {len(recent)}:",
85
+ ]
86
+ for t in recent:
87
+ title = (t.get("title") or "").strip()
88
+ if len(title) > 72:
89
+ title = title[:71].rstrip() + "…"
90
+ d = t.get("date") or "—"
91
+ lines.append(f"- {t.get('owner')}/{t.get('id')} ({d}) — {title}")
92
+ if total > len(tickets):
93
+ lines.append(f"({total - len(tickets)} newer ticket(s) on disk not yet enriched — run the index workflow.)")
94
+ print("\n".join(lines))
95
+ return 0
96
+
97
+
98
+ if __name__ == "__main__":
99
+ raise SystemExit(main())
@@ -0,0 +1,59 @@
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.",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Bash",
7
+ "hooks": [
8
+ { "type": "command", "command": "python3 .claude/hooks/db_write_guard.py" }
9
+ ]
10
+ }
11
+ ],
12
+ "SessionStart": [
13
+ {
14
+ "hooks": [
15
+ { "type": "command", "command": "python3 .claude/hooks/session_context.py" },
16
+ { "type": "command", "command": "python3 .claude/hooks/ticket_index_context.py" }
17
+ ]
18
+ }
19
+ ],
20
+ "PostToolUse": [
21
+ {
22
+ "matcher": "Write|Edit",
23
+ "hooks": [
24
+ { "type": "command", "command": "python3 .claude/hooks/regenerate_ticket_index.py" }
25
+ ]
26
+ }
27
+ ]
28
+ },
29
+ "permissions": {
30
+ "_README": "Read-only baseline that is safe in any stack. setup appends the chosen warehouse/tracker/vcs read verbs (e.g. Bash(snow sql:*)) here; the db_write_guard hook still gates destructive statements even when the CLI is allowed.",
31
+ "allow": [
32
+ "Bash(git status:*)",
33
+ "Bash(git diff:*)",
34
+ "Bash(git log:*)",
35
+ "Bash(git branch:*)",
36
+ "Bash(ls:*)",
37
+ "Bash(cat:*)",
38
+ "Bash(tree:*)",
39
+ "Bash(rg:*)",
40
+ "Bash(grep:*)",
41
+ "Bash(fd:*)",
42
+ "Bash(find:*)",
43
+ "Bash(jq:*)",
44
+ "Bash(yq:*)",
45
+ "Bash(python3 bin/build_ticket_index.py:*)",
46
+ "Bash(python3 bin/ingest_index_records.py:*)",
47
+ "Bash(python3 bin/recall.py:*)",
48
+ "Bash(python3 bin/enrich_ticket.py:*)",
49
+ "Bash(bash bin/verify_stack.sh:*)",
50
+ "Bash(bash bin/render.sh:*)",
51
+ "Bash(bash bin/selftest.sh:*)"
52
+ ],
53
+ "deny": []
54
+ },
55
+ "statusLine": {
56
+ "type": "command",
57
+ "command": ".claude/statusline.sh"
58
+ }
59
+ }
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: productize
3
+ description: Turn a recurring "clone-the-last-ticket" workflow into a parameterized skill of its own — phased pipeline, QC checkpoints, golden-replay test, hard-halt before external posts.
4
+ argument-hint: "<workflow name> (e.g. monthly vendor reconciliation)"
5
+ allowed-tools: [Read, Write, Edit, Bash, Glob, AskUserQuestion]
6
+ disable-model-invocation: true
7
+ ---
8
+
9
+ # /productize
10
+
11
+ The meta-skill (`commandify_everything`). When a task recurs (quarterly sale, monthly report,
12
+ periodic reconciliation), this stamps out a new **folder skill** with the productized skeleton:
13
+ a phased pipeline with hard halts, parameterized SQL/steps, a QC checkpoint, a golden-replay
14
+ regression test, and a hard halt before any external post. `/refresh index` + the `--recurring`
15
+ flag of `bin/build_ticket_index.py` surface candidates ("this object shows up in 7 tickets").
16
+
17
+ ## Phase 1 — Interview the workflow (reduce_assumptions)
18
+ Ask (AskUserQuestion) for:
19
+ - **Name & cadence** (→ kebab skill name) and the **parameters** that change each run (e.g.
20
+ `--period`, `--as-of-date`, `--ticket`, an input file) — with formats + validation rules.
21
+ - **Steps**: the ordered phases. For each, what it reads, what it writes, and whether it has
22
+ external side effects or DB writes (those become hard-halt / approval gates).
23
+ - **QC checks**: per check — what it tests, and its tier (hard-halt / warn / info) + remediation hint.
24
+ - **Determinism anchor**: the known-good prior run to use as the **golden fixture** (counts/totals
25
+ to assert; the byte-identical output file to diff against).
26
+
27
+ ## Phase 2 — Stamp from the template
28
+ 1. Copy `templates/productized-skill/` → `.claude/skills/<name>/` (SKILL.md + `sql/ templates/
29
+ bin/ golden/`).
30
+ 2. Render `SKILL.md.tmpl` with the interview answers into the canonical phase shape:
31
+ **Phase 0** pre-flight (validate params + `verify_stack` for touched seams + an object drift
32
+ check → halt-on-fail) → **render & run** parameterized steps → **QC** atomic checkpoint
33
+ (hard-halt tiers) → **export** (deterministic `ORDER BY`) → **render docs** (README +
34
+ tracker-comment draft) → **HARD HALT for human review** → **post-review** (the external posts,
35
+ in a separate invocation).
36
+ 3. Write the `{{token}}` step files into `sql/` (or `bin/` for non-SQL), the output templates into
37
+ `templates/`, and seed `golden/<run>.json` with the determinism anchor. **Follow the two SQL
38
+ authoring rules in [authoring.md](authoring.md)** — a render gate enforces both.
39
+ 4. Scaffold `bin/drift_check.sh` (confirms every object the workflow reads is still reachable —
40
+ the Phase-0 catch for relocations).
41
+
42
+ ## Phase 3 — Wire to the stack & self-test
43
+ 5. The stamped skill reads `.claude/config/stack.yaml` and resolves all I/O through adapters (no
44
+ hardcoded tools in the orchestration). Confirm the SKILL.md prose is tool-neutral:
45
+ `!grep -REn "acli|snow |slack|gh " .claude/skills/<name>/SKILL.md || echo OK`. (The leaf
46
+ `bin/drift_check.sh` is allowed to name your warehouse CLI — it's the tool-bound probe.)
47
+ 6. Wire the **render gate** (`bin/render_and_validate.sh`) and **export helpers**
48
+ (`bin/split_and_export.sh`) into the stamped phases so authoring defects fail *closed*, not
49
+ silently — details and rationale in [authoring.md](authoring.md).
50
+ 7. Document the **golden test** + **failure-mode tests** in the new SKILL.md (how to replay the
51
+ known run and assert the fixture; how each Phase-0/QC/render-gate halt is expected to trigger).
52
+ 8. **Report** the new skill path, its parameters, and how to run its golden test.
53
+
54
+ ## Output shape (what every stamped skill guarantees)
55
+ Parameterized · phased · QC-gated · golden-tested · hard-halts before external posts · idempotent
56
+ where possible (re-run converges) · records a rollback key for any DB write. Heavy pulls run in the
57
+ **background**, not the foreground — mark slow phases at stamp time so the operator launches them
58
+ detached from the start.
@@ -0,0 +1,36 @@
1
+ # SQL-template authoring rules + the render gate
2
+
3
+ Two hard rules for every `sql/*.tmpl` in a productized skill. Both have burned real runs; the
4
+ render gate (`bin/render_and_validate.sh`) enforces both mechanically.
5
+
6
+ ## Rule 1 — Never put a token in a SQL comment
7
+ The renderer expands tokens everywhere, **including inside a `--` comment**. A multi-line value
8
+ (e.g. a long `VALUES` list) then spills past the `--` and the continuation rows are parsed as bare
9
+ SQL → compile error. Describe parameters in *prose*, not tokens:
10
+
11
+ ```sql
12
+ -- step SQL — params described in prose, never as tokens ✓
13
+ -- NOTE: substitutes {{vals}} before the run ✗ (gate rejects this)
14
+ ```
15
+
16
+ ## Rule 2 — Quote any token used as a SQL string/date literal
17
+ `SET d = '{{asof}}'::DATE`, not `= {{asof}}`. Unquoted, `= 2026-06-30` is read as *arithmetic*
18
+ (= 1990), not a date → **silent wrong results**. The quotes live in the template, never injected at
19
+ call time. (The gate warns by default, fails under `--strict`.)
20
+
21
+ ## The render gate — `bin/render_and_validate.sh`
22
+ Run it on every render (in the stamped skill's Phase 0/1, and once at stamp time). Stdlib-only, no
23
+ warehouse round-trip — cheap insurance. It asserts: zero leftover tokens · no token in a comment ·
24
+ balanced single-quotes / parens · flags an unquoted SQL literal. Halt on any error.
25
+
26
+ ```bash
27
+ bash bin/render_and_validate.sh sql/step.sql.tmpl asof=2026-06-30 src=T [--strict]
28
+ ```
29
+
30
+ ## Export helpers — `bin/split_and_export.sh`
31
+ Keeps multi-deliverable skills from re-improvising CSV plumbing:
32
+ - `--strip-only <file>` robustly drops the multi-statement CLI preamble (through the last
33
+ `Statement executed successfully.`, then leading blanks) so the header is row 1;
34
+ - split mode turns one multi-`SELECT` file (delimited by `-- Query N` markers) into N runnable
35
+ files — each carrying the shared preamble — and `--run` executes each via *your* warehouse verb
36
+ and strips its CSV.
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: refresh
3
+ description: Maintain the repo's knowledge — `index` rebuilds the ticket catalog (INDEX.md), `context` rebuilds the domain knowledge pack (documentation/). Day-to-day, hooks keep these fresh automatically.
4
+ argument-hint: index [--all | TICKET-ID ...] | context [--refresh] [schema-or-scope] | all
5
+ allowed-tools: [Read, Write, Bash, Glob, Grep]
6
+ ---
7
+
8
+ # /refresh
9
+
10
+ One maintenance skill for the two knowledge stores the other skills read:
11
+
12
+ - **`/refresh index`** — the ticket catalog: `tickets/INDEX.md` (every ticket, status, one-line
13
+ summary, tags, cross-refs) + `tickets/OBJECTS.md` (object → tickets reverse map). This is what
14
+ `/ticket`'s recall mines. Details: [index.md](index.md).
15
+ - **`/refresh context`** — the domain knowledge pack in `documentation/`: data catalog, DDL, ERD,
16
+ dependency graph, business glossary. This is what `/ticket`'s domain + warehouse priming reads.
17
+ Details: [context-pack.md](context-pack.md).
18
+ - **`/refresh all`** — both.
19
+
20
+ ## You rarely need this
21
+ Day-to-day the index maintains itself: the PostToolUse hook re-renders `INDEX.md` on every ticket
22
+ folder change, the SessionStart hook surfaces the catalog, and `/ship` curates each ticket's
23
+ summary at close. Invoke `/refresh` to:
24
+ - **bootstrap** an existing backlog (`/refresh index --all` — render every ticket, then enrich);
25
+ - **re-enrich** a batch after bulk edits (`/refresh index ID1 ID2 …`);
26
+ - **build or update the knowledge pack** (`/refresh context`, scoped to a schema if given;
27
+ `--refresh` updates in place — overwrite, don't sprawl).
28
+
29
+ ## Mode: `index`
30
+ Follow [index.md](index.md): render deterministically (`bin/build_ticket_index.py` — stdlib, no
31
+ model), enrich the un-enriched/stale set (the model writes one curated record per ticket, ingested
32
+ via `bin/ingest_index_records.py`), re-render, then verify with `--check` and commit all three
33
+ index files together (`INDEX.md`, `OBJECTS.md`, `index_data.json`).
34
+
35
+ ## Mode: `context`
36
+ Follow [context-pack.md](context-pack.md): verify the warehouse seam, introspect via the adapter
37
+ (inventory → DDL → dependencies → usage ranking), generate `data_catalog.md` / `erd.md` /
38
+ `glossary.md` into `documentation/` with freshness stamps, and update
39
+ `documentation/AI_LAYER_INDEX.md`. Don't invent business meaning — flag glossary gaps for a human
40
+ (`reduce_assumptions`).
@@ -0,0 +1,34 @@
1
+ # `/refresh context` — the domain knowledge pack in detail
2
+
3
+ Builds the on-demand knowledge base that `/ticket`'s domain + warehouse priming reads, so
4
+ day-to-day tickets prime *slices* instead of re-discovering the warehouse every time. Reads
5
+ `.claude/config/stack.yaml`; introspects via the warehouse adapter, so it works on Snowflake,
6
+ BigQuery, Databricks, etc.
7
+
8
+ ## Phase 0 — Setup
9
+ 1. Read `stack.yaml`; verify the warehouse seam (halt with auth notes if unreachable). Scope to
10
+ the given schema/dataset or the whole configured warehouse. `--refresh` updates an existing
11
+ pack in place (overwrite, don't sprawl).
12
+
13
+ ## Phase 1 — Introspect (read-only, via adapter)
14
+ 2. **Object inventory** — list tables/views/dynamic-tables in scope, using whatever
15
+ object-inventory idiom the adapter's `dialect_notes` specifies (e.g. an
16
+ `INFORMATION_SCHEMA`/`system` query).
17
+ 3. **DDL dump** — `warehouse.describe` each object; write authoritative DDL to
18
+ `documentation/erd/ddl_<schema>_<object>.sql`.
19
+ 4. **Dependency graph** — derive what-reads-what (lineage views) → an object→object edge list.
20
+ 5. **Usage ranking** — rank objects by how often they appear across `tickets/**` (`grep -roh …`)
21
+ so the catalog leads with what's actually used.
22
+
23
+ ## Phase 2 — Generate the pack (into documentation/)
24
+ 6. **`data_catalog.md`** — per object: purpose, grain, key columns, join keys + cast/filter rules
25
+ (from `dialect_notes`), source layer, usage rank.
26
+ 7. **`erd.md`** — the relationships + a high-level diagram; per-object DDL lives under `erd/`.
27
+ 8. **`glossary.md`** — business terms, status taxonomies, calculation rules, known data-quality
28
+ caveats. Seed from existing READMEs/runbooks; **flag gaps for a human to fill** — don't invent
29
+ business meaning (`reduce_assumptions`).
30
+ 9. Add a freshness stamp + scope to each file (re-check if >30 days old).
31
+
32
+ ## Phase 3 — Index & report
33
+ 10. Update `documentation/AI_LAYER_INDEX.md` with the pack's contents. Report what was generated,
34
+ the top-N most-used objects, and which glossary sections need human input.
@@ -0,0 +1,45 @@
1
+ # `/refresh index` — the ticket catalog in detail
2
+
3
+ Two layers, kept separate so the catalog is reproducible and CI/pre-commit-safe:
4
+
5
+ - **`bin/build_ticket_index.py`** — deterministic, LLM-free renderer. Discovers every ticket folder
6
+ (tracker keys from `stack.yaml` `key_prefixes`/`key_prefix`), merges curated fields from
7
+ `tickets/index_data.json`, and writes `INDEX.md` + `OBJECTS.md` (object → tickets reverse index;
8
+ objects = enrichment ∪ a deterministic grep of each ticket's SQL). `--check` (staleness gate,
9
+ covers both files) · `--stats` (coverage) · `--recurring` (frequently-touched objects —
10
+ productization candidates).
11
+ - **`tickets/index_data.json`** — the curated store (title/status/date/summary/tags/cross_refs/
12
+ objects + each README's content hash). This skill writes it.
13
+
14
+ ## Phase 0 — Preflight
15
+ 1. Confirm `stack.yaml` has `key_prefix`/`key_prefixes` and (optionally) `ticket_url_template`.
16
+ 2. `python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/build_ticket_index.py" --stats` — how
17
+ many tickets are discovered, enriched, un-enriched (`▱`), stale (`⚠`).
18
+
19
+ ## Phase 1 — Render (always cheap, no model)
20
+ 3. `python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/build_ticket_index.py"` — every ticket
21
+ on disk now has a row (un-enriched ones get a deterministic title + first-paragraph summary,
22
+ marked `▱`).
23
+
24
+ ## Phase 2 — Enrich (the model half)
25
+ 4. Decide scope: the given ticket id(s); `--all` for the whole backlog; or default to the
26
+ un-enriched/stale set from `--stats`.
27
+ 5. For each target ticket, read its `README.md` and write ONE record:
28
+ `{id, owner, title, status, date, summary (<=180 chars, lead with what was delivered + key
29
+ numbers), tags (1-4 kebab-case), cross_refs (other ticket ids), objects (qualified data objects
30
+ the ticket read/wrote, e.g. SCHEMA.VIEW; [] if none)}`. **id/owner come from the folder, not
31
+ your judgment.** Status vocab: Completed · Deployed · In Review · In Progress · Blocked · Unknown.
32
+ 6. Collect them and upsert + re-render:
33
+ ```bash
34
+ echo '{"records":[ ... ]}' | python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/ingest_index_records.py" --from-json -
35
+ python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/build_ticket_index.py"
36
+ ```
37
+ (Claude-Code convenience: `python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/enrich_ticket.py" <ID>`
38
+ does steps 5–6 for one ticket via `claude -p`. The inline path above is agent-agnostic.)
39
+
40
+ ## Phase 3 — Verify
41
+ 7. `python3 "${CLAUDE_PLUGIN_ROOT:-$CLAUDE_PROJECT_DIR}/bin/build_ticket_index.py" --check` must
42
+ pass (INDEX.md + OBJECTS.md == fresh render).
43
+ 8. Report: total tickets, status breakdown, any still un-enriched. Commit `tickets/INDEX.md` +
44
+ `tickets/OBJECTS.md` + `tickets/index_data.json` (all three — `--check` gates the two generated
45
+ files).
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: review
3
+ description: Independent quality review of a ticket's deliverables — re-runs queries and walks a tiered validation pyramid to an APPROVE / REQUEST-CHANGES verdict. Run before shipping.
4
+ argument-hint: <ticket-id> [--deep]
5
+ allowed-tools: [Read, Bash, Glob, Grep, Agent]
6
+ ---
7
+
8
+ # /review
9
+
10
+ The **check** step of the lifecycle. An *independent* second pass over a ticket's deliverables —
11
+ re-runs queries, walks a tiered validation pyramid, sweeps for anti-patterns, and returns an
12
+ APPROVE / REQUEST-CHANGES verdict. Reads `.claude/config/stack.yaml`; warehouse specifics come from
13
+ the adapter's `dialect_notes`, so the same review runs on Snowflake, BigQuery, or Databricks.
14
+ Read-only: it reviews and re-runs, it does not edit code (the build owns fixes).
15
+
16
+ ## Phase 0 — Setup
17
+ 1. Read `stack.yaml`; verify the warehouse seam (halt with auth notes if unreachable). Load
18
+ `seams.warehouse.dialect_notes` from the adapter — it parameterizes the lint layer.
19
+ 2. Read the ticket README, the spec (if any), and list `final_deliverables/` + `qc_queries/`.
20
+
21
+ ## The validation pyramid (bottom = cheap/automated, top = human)
22
+
23
+ **① Dialect lint** (static, per `dialect_notes`)
24
+ - `= NULL` → must be `IS NULL`; missing div-by-zero guard; `SELECT *` in deliverables; functions on
25
+ filtered columns; implicit cross-source type mismatches (missing `CAST`); hardcoded values that
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`.
28
+
29
+ **② Counts & dedup** (re-run independently)
30
+ - Re-run the row-count; compare to the documented count. **Duplicate detection is the primary test:**
31
+ `COUNT(*)` vs `COUNT(DISTINCT <grain key>)` — any gap is a halt until explained.
32
+ - NULL-rate on critical/key columns; value-range sanity (no negative amounts where impossible).
33
+
34
+ **③ Cross-source reconciliation**
35
+ - Join-match rates (unmatched `LEFT JOIN` rows quantified, not hidden). Date ranges within scope.
36
+ - Reconcile totals against the source of truth (e.g. SUM vs the input file/feed) within tolerance.
37
+
38
+ **④ Independent re-run + anti-pattern sweep**
39
+ - Re-execute the main deliverable query end-to-end; diff results to the committed output (byte-level
40
+ for CSVs — `deterministic_outputs` requires explicit `ORDER BY`).
41
+ - Sweep the full anti-pattern set (correctness, performance, data-quality, dialect-specific,
42
+ maintainability). Classify each finding Critical / Should-fix / Review.
43
+
44
+ **⑤ Human sign-off**
45
+ - Output format check (CSV headers row 1, no preamble/blank rows; filenames carry record counts).
46
+ - README completeness (assumptions enumerated, QC results, business context). Flag for the human.
47
+
48
+ ## Tiers & halting
49
+ - **Hard-halt** findings (Critical, count mismatch, dup gap, reconciliation break) → verdict
50
+ REQUEST-CHANGES with structured remediation.
51
+ - **Warn** findings (perf, style) → list but don't block.
52
+ - **Info** (distributions) → record.
53
+
54
+ ## Deep mode (`--deep`) — adversarial panel
55
+ For high-blast-radius work (compliance/regulatory pulls, irreversible writes), replace the single
56
+ pass with a panel, using the host agent's own subagents (the `Agent` tool):
57
+ 1. **Fan out** one independent `qc-reviewer` subagent per pyramid layer (① dialect-lint ·
58
+ ② counts&dedup · ③ cross-source reconcile · ④ re-run + anti-pattern sweep) — each scoped to its
59
+ layer, re-running its own queries, returning findings only. Run them in parallel.
60
+ 2. **Adversarially verify** every reported finding before it counts: a second pass re-reads the
61
+ cited `file:line` / re-runs the query and rules each finding confirmed / false-positive /
62
+ **uncertain**. A finding it cannot reproduce is **uncertain, not dismissed** — silently dropping
63
+ a Critical / count / reconcile finding is the expensive failure. Only a clear, demonstrated
64
+ non-issue is ruled false-positive.
65
+ 3. **Synthesize** confirmed findings (deduped across layers) into one verdict — and **carry
66
+ uncertain findings into the verdict too**: any uncertain Critical / count / reconcile finding
67
+ forces REQUEST-CHANGES (or explicit human sign-off), never a silent APPROVE.
68
+
69
+ Default (no flag) is the single `qc-reviewer` pass. `/ticket` recommends `--deep` automatically for
70
+ high-risk tickets (external/compliance deliverables, irreversible writes) — reach for it whenever
71
+ the cost of a missed defect is high.
72
+
73
+ ## Phase N — Verdict
74
+ Emit the structured report (Summary · pyramid results per layer · findings by severity ·
75
+ verification queries run · **APPROVE** or **REQUEST-CHANGES**). Save it into the ticket's
76
+ `qc_queries/` for the audit trail. APPROVE ⇒ recommend `/ship <id>`.