jobwright 0.0.1__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 (72) hide show
  1. jobwright-0.0.1/.claude-plugin/marketplace.json +12 -0
  2. jobwright-0.0.1/.claude-plugin/plugin.json +12 -0
  3. jobwright-0.0.1/.github/workflows/ci.yml +36 -0
  4. jobwright-0.0.1/.github/workflows/publish.yml +32 -0
  5. jobwright-0.0.1/.gitignore +23 -0
  6. jobwright-0.0.1/CHANGELOG.md +38 -0
  7. jobwright-0.0.1/CONTRIBUTING.md +31 -0
  8. jobwright-0.0.1/LICENSE +21 -0
  9. jobwright-0.0.1/PKG-INFO +110 -0
  10. jobwright-0.0.1/README.md +80 -0
  11. jobwright-0.0.1/adapters/platform/airflow.md +45 -0
  12. jobwright-0.0.1/adapters/platform/databricks.md +72 -0
  13. jobwright-0.0.1/adapters/platform/dbt.md +40 -0
  14. jobwright-0.0.1/adapters/platform/snowflake_tasks.md +51 -0
  15. jobwright-0.0.1/bin/selftest.sh +29 -0
  16. jobwright-0.0.1/docs/PUBLISHING.md +54 -0
  17. jobwright-0.0.1/examples/sample-airflow/dags/demo_dag.py +8 -0
  18. jobwright-0.0.1/examples/sample-airflow/jobwright.config.yaml +18 -0
  19. jobwright-0.0.1/examples/sample-databricks/databricks/job_definitions/prod/JOB-1_Demo_Report.json +3 -0
  20. jobwright-0.0.1/examples/sample-databricks/jobs/JOB-1_Demo_Report/claude.md +5 -0
  21. jobwright-0.0.1/examples/sample-databricks/jobs/JOB-1_Demo_Report/demo.py +11 -0
  22. jobwright-0.0.1/examples/sample-databricks/jobs/JOBS.md +14 -0
  23. jobwright-0.0.1/examples/sample-databricks/jobs/OBJECTS.md +13 -0
  24. jobwright-0.0.1/examples/sample-databricks/jobwright.config.yaml +48 -0
  25. jobwright-0.0.1/hooks/deploy_safety.py +242 -0
  26. jobwright-0.0.1/hooks/hooks.json +28 -0
  27. jobwright-0.0.1/hooks/regenerate_jobs_index.py +104 -0
  28. jobwright-0.0.1/hooks/session_start.sh +24 -0
  29. jobwright-0.0.1/jobwright/__init__.py +8 -0
  30. jobwright-0.0.1/jobwright/_templates/job/claude.md.j2 +25 -0
  31. jobwright-0.0.1/jobwright/_templates/job/job_definition.json.j2 +18 -0
  32. jobwright-0.0.1/jobwright/_templates/job/notebook_header.py.j2 +17 -0
  33. jobwright-0.0.1/jobwright/_templates/job/task_definition.sql.j2 +9 -0
  34. jobwright-0.0.1/jobwright/_templates/repo/AGENTS.md.j2 +43 -0
  35. jobwright-0.0.1/jobwright/cli.py +336 -0
  36. jobwright-0.0.1/jobwright/config.py +304 -0
  37. jobwright-0.0.1/jobwright/jobsindex.py +349 -0
  38. jobwright-0.0.1/jobwright/platforms/__init__.py +57 -0
  39. jobwright-0.0.1/jobwright/platforms/airflow.py +118 -0
  40. jobwright-0.0.1/jobwright/platforms/base.py +170 -0
  41. jobwright-0.0.1/jobwright/platforms/databricks.py +293 -0
  42. jobwright-0.0.1/jobwright/platforms/dbt.py +84 -0
  43. jobwright-0.0.1/jobwright/platforms/snowflake_tasks.py +151 -0
  44. jobwright-0.0.1/jobwright/policy.py +109 -0
  45. jobwright-0.0.1/jobwright/scaffolder.py +96 -0
  46. jobwright-0.0.1/jobwright/tools/__init__.py +5 -0
  47. jobwright-0.0.1/jobwright/tools/check_dependency_vulns.py +154 -0
  48. jobwright-0.0.1/jobwright/tools/check_notebook_syntax.py +71 -0
  49. jobwright-0.0.1/jobwright/tools/job_doc_lint.py +88 -0
  50. jobwright-0.0.1/jobwright/tools/schema_compliance.py +85 -0
  51. jobwright-0.0.1/jobwright/tools/validate_job.py +160 -0
  52. jobwright-0.0.1/jobwright/tools/validate_job_definitions.py +62 -0
  53. jobwright-0.0.1/jobwright.config.example.yaml +48 -0
  54. jobwright-0.0.1/pyproject.toml +67 -0
  55. jobwright-0.0.1/skills/architecture-audit/SKILL.md +27 -0
  56. jobwright-0.0.1/skills/build-jobs-index/SKILL.md +24 -0
  57. jobwright-0.0.1/skills/configure-workspace/SKILL.md +26 -0
  58. jobwright-0.0.1/skills/document-job/SKILL.md +23 -0
  59. jobwright-0.0.1/skills/onboard/SKILL.md +20 -0
  60. jobwright-0.0.1/skills/safe-deploy/SKILL.md +27 -0
  61. jobwright-0.0.1/skills/scaffold-job/SKILL.md +26 -0
  62. jobwright-0.0.1/skills/start-job/SKILL.md +20 -0
  63. jobwright-0.0.1/skills/triage-failure/SKILL.md +21 -0
  64. jobwright-0.0.1/skills/validate-job/SKILL.md +31 -0
  65. jobwright-0.0.1/tests/test_phase0.py +132 -0
  66. jobwright-0.0.1/tests/test_phase1.py +102 -0
  67. jobwright-0.0.1/tests/test_phase2.py +95 -0
  68. jobwright-0.0.1/tests/test_phase3.py +75 -0
  69. jobwright-0.0.1/tests/test_review_fixes.py +140 -0
  70. jobwright-0.0.1/tests/test_review_fixes_overall.py +91 -0
  71. jobwright-0.0.1/tests/test_review_fixes_p2.py +96 -0
  72. jobwright-0.0.1/tests/test_review_fixes_p3.py +107 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "jobwright",
3
+ "owner": { "name": "Kyle Chalmers" },
4
+ "description": "jobwright — govern, validate, and safely ship data-orchestration jobs with Claude Code.",
5
+ "plugins": [
6
+ {
7
+ "name": "jobwright",
8
+ "source": "./",
9
+ "description": "A jobs index, architecture-compliance scanning, a live-vs-repo deploy-safety guard, and per-job validation for Databricks / Airflow / dbt / Snowflake Tasks job repos."
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "jobwright",
3
+ "displayName": "jobwright",
4
+ "version": "0.0.1",
5
+ "description": "Govern, validate, and safely ship data-orchestration jobs (Databricks, Airflow, dbt, Snowflake Tasks) with Claude Code: a jobs index, architecture-compliance scanning, a live-vs-repo deploy-safety guard, and per-job validation.",
6
+ "author": { "name": "Kyle Chalmers" },
7
+ "homepage": "https://github.com/kyle-chalmers/jobwright",
8
+ "repository": "https://github.com/kyle-chalmers/jobwright",
9
+ "license": "MIT",
10
+ "keywords": ["databricks", "airflow", "dbt", "snowflake", "orchestration", "governance", "data-engineering"],
11
+ "hooks": "./hooks/hooks.json"
12
+ }
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ["3.10", "3.11", "3.12"]
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - name: Set up Python ${{ matrix.python-version }}
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+
31
+ - name: Install (editable, with dev extras)
32
+ run: pip install -e ".[dev]"
33
+
34
+ # bin/selftest.sh runs ruff + pytest + the adapter-contract + skill-leak checks.
35
+ - name: Selftest
36
+ run: bash bin/selftest.sh
@@ -0,0 +1,32 @@
1
+ name: Publish to PyPI
2
+
3
+ # Manual trigger so cutting a GitHub release never auto-fails before PyPI is configured.
4
+ # After registering the PyPI Trusted Publisher (see docs/PUBLISHING.md), run this workflow
5
+ # from the Actions tab (or switch the trigger to `release: [published]`).
6
+ on:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build-and-publish:
14
+ runs-on: ubuntu-latest
15
+ environment: pypi
16
+ permissions:
17
+ id-token: write # OIDC for PyPI Trusted Publishing — no API token stored
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.11"
25
+
26
+ - name: Build sdist + wheel
27
+ run: |
28
+ pip install build
29
+ python -m build
30
+
31
+ - name: Publish to PyPI (Trusted Publishing)
32
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,23 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ .env
11
+
12
+ # Tooling caches
13
+ .ruff_cache/
14
+ .pytest_cache/
15
+ .mypy_cache/
16
+
17
+ # OS
18
+ .DS_Store
19
+
20
+ # Never commit a real, org-specific config at the repo root.
21
+ # Root-anchored so the example fixtures under examples/ ARE tracked.
22
+ # The documented template lives at jobwright.config.example.yaml.
23
+ /jobwright.config.yaml
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ All notable changes to jobwright are documented here. Format loosely follows
4
+ [Keep a Changelog](https://keepachangelog.com/); versions follow semver.
5
+
6
+ ## [0.0.1] — 2026-06-27
7
+
8
+ First public alpha. Generalized from a production Databricks data-jobs repo and
9
+ stripped of all org-specific values.
10
+
11
+ ### Added
12
+
13
+ - **Two-seam architecture** — a `platform` seam (orchestrator lifecycle) and a
14
+ `warehouse`/`architecture` seam (static schema policy), expressed in a typed,
15
+ validated `jobwright.config.yaml`.
16
+ - **Platform adapters** across all three deploy models via a `JobPlatformAdapter`
17
+ contract: Databricks Jobs (`api-reset`), Snowflake Tasks (`sql-ddl`), Apache
18
+ Airflow (`git-sync`), and dbt (`git-sync`). Each ships a markdown playbook.
19
+ - **Deploy-safety guard** (`PreToolUse` hook) — asks before destructive job/SQL
20
+ commands (e.g. `databricks jobs reset/update/delete`, `DROP TASK`, `dbt … --target
21
+ prod`, destructive warehouse SQL incl. SQL in `-f` files). Stdlib-only, fail-open,
22
+ zero-cost outside a jobwright repo; defends against shell-quote and full-path evasion.
23
+ - **Generic checks** (`check syntax | job-defs | deps | architecture | docs`) plus a
24
+ composite `validate-job` PASS/FAIL gate scoped to one job.
25
+ - **Deterministic jobs index** — `jobs-index` renders `JOBS.md` + `OBJECTS.md`
26
+ (recall-before-rebuild; surfaces deprecated-schema migration debt), with a `--check`
27
+ CI gate.
28
+ - **Scaffolder** — `new-job` creates a governed job folder (claude.md + notebook
29
+ header + a deploy-model-appropriate definition stub); `gen-agents` renders an
30
+ `AGENTS.md` rulebook from config.
31
+ - **Hooks** — SessionStart skill/catalog pointer + PostToolUse jobs-index regen.
32
+ - **10 tool-agnostic skills** (onboard, start-job, scaffold-job, document-job,
33
+ validate-job, architecture-audit, build-jobs-index, safe-deploy, triage-failure,
34
+ configure-workspace).
35
+ - **Claude Code plugin** manifests (`.claude-plugin/`), `bin/selftest.sh` (lint +
36
+ tests + adapter-contract + skill-leak checks), and a CI workflow.
37
+
38
+ [0.0.1]: https://github.com/kyle-chalmers/jobwright/releases/tag/v0.0.1
@@ -0,0 +1,31 @@
1
+ # Contributing to jobwright
2
+
3
+ ## Ground rules
4
+
5
+ 1. **One implementation, many consumers.** Logic lives in the `jobwright` package. The CLI, the Claude Code skills, the hooks, and CI all call it — they never re-implement a check. A skill is a thin playbook that shells out to `jobwright ...`; it never embeds the check logic in prose.
6
+ 2. **Skills call verbs, never tools.** Skills and commands use the abstract platform verbs (`diff_live_vs_repo`, `list_active_runs`, …) and the generic checks. They must not hardcode `databricks` / `airflow` / `dbt` by name — that belongs in an adapter. `bin/selftest.sh` greps for platform-name leaks.
7
+ 3. **No org-specific values in code.** Schema names, profiles, account locators, channels — everything that varies by repo — goes in `jobwright.config.yaml`. The package ships only the *schema* and generic rules.
8
+ 4. **Secrets never in the repo.** Not in config, not in tests, not in docs. Config holds names/profiles only.
9
+
10
+ ## Adding a platform adapter
11
+
12
+ A platform adapter is two files:
13
+
14
+ - `jobwright/platforms/<kind>.py` — a `JobPlatformAdapter` subclass implementing the mandatory verbs (see `jobwright/platforms/base.py`, `MANDATORY_VERBS`). Set `kind`, `deploy_model`, and `destructive_patterns`. Register it in `jobwright/platforms/__init__.py`. **This is the single source of truth for `destructive_patterns`.**
15
+ - `adapters/platform/<kind>.md` — a markdown playbook: frontmatter (`seam`, `kind`, `transport`, `deploy_model`, `requires`, `auth`, `destructive_patterns`) + a `## verb:` section per verb. The `destructive_patterns` here must mirror the Python class; `bin/selftest.sh` checks they agree.
16
+
17
+ Verbs that don't apply to a platform (e.g. `get_live_definition` / `diff_live_vs_repo` on a `git-sync` platform where git is the source of truth) should raise `ManualFallback` with a message telling the human what to do instead.
18
+
19
+ ## Dev setup
20
+
21
+ ```bash
22
+ python3 -m venv .venv && . .venv/bin/activate
23
+ pip install -e ".[dev]"
24
+ ruff check . && ruff format --check .
25
+ pytest
26
+ bash bin/selftest.sh
27
+ ```
28
+
29
+ ## Provenance
30
+
31
+ jobwright was generalized from a production Databricks data-jobs repo. Every check and safety rule was earned against real jobs and real failures before being lifted here — and stripped of all org-specific values on the way out.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kyle Chalmers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: jobwright
3
+ Version: 0.0.1
4
+ Summary: Open-source AI layer for governing, validating, and safely shipping data-orchestration jobs (Databricks, Airflow, dbt, Snowflake Tasks) with Claude Code
5
+ Project-URL: Homepage, https://github.com/kyle-chalmers/jobwright
6
+ Project-URL: Repository, https://github.com/kyle-chalmers/jobwright
7
+ Project-URL: Issues, https://github.com/kyle-chalmers/jobwright/issues
8
+ Author: Kyle Chalmers
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: airflow,ci,claude-code,data-engineering,databricks,dbt,governance,orchestration,snowflake
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: jinja2>=3.1
22
+ Requires-Dist: pyyaml>=6
23
+ Requires-Dist: rich>=13
24
+ Requires-Dist: typer>=0.12
25
+ Provides-Extra: dev
26
+ Requires-Dist: pre-commit>=4; extra == 'dev'
27
+ Requires-Dist: pytest>=8; extra == 'dev'
28
+ Requires-Dist: ruff>=0.6; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # jobwright
32
+
33
+ [![CI](https://github.com/kyle-chalmers/jobwright/actions/workflows/ci.yml/badge.svg)](https://github.com/kyle-chalmers/jobwright/actions/workflows/ci.yml)
34
+ [![PyPI](https://img.shields.io/pypi/v/jobwright.svg)](https://pypi.org/project/jobwright/)
35
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
36
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
37
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
38
+
39
+ **An open-source AI layer for governing, validating, and safely shipping data-orchestration jobs with Claude Code.**
40
+
41
+ jobwright treats your *jobs* — Databricks Jobs, Airflow DAGs, dbt jobs, Snowflake Tasks — as deployable artifacts that deserve a governed lifecycle: a catalog you can recall before you rebuild, architecture-compliance scanning, a per-job validation gate, and a deploy-safety guard that prompts for confirmation before known destructive commands (so a stale-definition overwrite can't happen unattended).
42
+
43
+ It is the third in a family of Claude Code "AI layer" kits:
44
+
45
+ | Kit | Domain |
46
+ |---|---|
47
+ | [ticketwright](https://github.com/kyle-chalmers/ticketwright) | ticket-driven data work (plan → implement → validate) |
48
+ | [streamsnow](https://github.com/kyle-chalmers/streamsnow) | Streamlit-in-Snowflake apps (build → govern → ship) |
49
+ | **jobwright** | **data-orchestration jobs (govern → validate → safely ship)** |
50
+
51
+ > **Complementary, not overlapping with ticketwright.** ticketwright's Databricks adapter is about *querying* a warehouse while doing a ticket. jobwright is about the *jobs themselves* — their definitions, architecture compliance, and safe deploy. ticketwright's "pause before any prod job deploy" gotcha is exactly the hand-off point to jobwright's `diff-job` + deploy-safety guard.
52
+
53
+ ## Why
54
+
55
+ - **A deploy-safety guard.** A `PreToolUse` hook asks for confirmation before destructive orchestration commands (`databricks jobs reset`/`delete`, `airflow dags delete`, `DROP TASK`, …) and before destructive warehouse SQL — including SQL hidden in a `-f` file. On `api-reset` platforms it specifically blocks a reset from a possibly-stale repo definition until you've run a live-vs-repo diff. This turns a prose rule into a runtime gate.
56
+ - **A jobs index.** A deterministic `JOBS.md` + `OBJECTS.md` over every job in the repo — ticket, purpose, schedule, owner, status, and architecture-compliance flags — so you (and the agent) recall prior work before rebuilding, and see migration debt at a glance.
57
+ - **Architecture compliance.** A static scan of job code for deprecated-schema references and layer-referencing violations, driven by config — no database connection.
58
+ - **Per-job validation.** A local PASS/FAIL gate that runs the same checks your CI does, scoped to one job.
59
+
60
+ ## Two seams
61
+
62
+ jobwright has two orthogonal abstraction axes, expressed as two config blocks:
63
+
64
+ - **platform** — the orchestrator a job *runs on* (`databricks` / `airflow` / `dbt` / `snowflake_tasks` / …). Owns the lifecycle verbs. Its `deploy_model` (`api-reset` | `git-sync` | `sql-ddl`) decides whether live-vs-repo drift even applies.
65
+ - **warehouse / architecture** — the store a job *reads/writes* and the static schema-reference rules. Policy only; jobwright never opens a database connection.
66
+
67
+ The platform seam is pluggable via thin adapters (a Python class implementing the verb contract + a markdown playbook). Shipped adapters span all three deploy models:
68
+
69
+ | Platform | `deploy_model` | Drift detection |
70
+ |---|---|---|
71
+ | Databricks Jobs (reference) | `api-reset` | yes — live can drift from repo |
72
+ | Snowflake Tasks | `sql-ddl` | yes — live DDL vs repo DDL |
73
+ | Apache Airflow | `git-sync` | n/a — git is the source of truth |
74
+ | dbt | `git-sync` | n/a — project is the source of truth |
75
+
76
+ The generic checks (`syntax`, `deps`, `architecture`, `docs`) and the deploy-safety guard are platform-agnostic — see [`examples/`](examples/) for a Databricks repo and an Airflow + BigQuery repo. Adding a platform is two files (a `JobPlatformAdapter` subclass + a markdown playbook); see [`CONTRIBUTING.md`](CONTRIBUTING.md).
77
+
78
+ ## Install
79
+
80
+ ```bash
81
+ pip install jobwright # or: uvx jobwright
82
+ # Claude Code plugin (skills + hooks):
83
+ # /plugin marketplace add kyle-chalmers/jobwright
84
+ # /plugin install jobwright@jobwright
85
+ ```
86
+
87
+ ## Quickstart
88
+
89
+ ```bash
90
+ cp jobwright.config.example.yaml jobwright.config.yaml # edit for your repo
91
+ jobwright doctor # check config + environment
92
+ jobwright jobs-index # render jobs/JOBS.md + OBJECTS.md
93
+ jobwright diff-job BI-813 # live-vs-repo drift for one job
94
+ ```
95
+
96
+ See [`jobwright.config.example.yaml`](jobwright.config.example.yaml) for the full, documented config (including an Airflow + BigQuery variant) and [`examples/`](examples/) for a runnable sample repo.
97
+
98
+ ## Status
99
+
100
+ Alpha. Shipped: the deploy-safety guard, the jobs index, four platform adapters (Databricks, Snowflake Tasks, Airflow, dbt) across all three deploy models, the generic checks (`syntax` / `job-defs` / `deps` / `architecture` / `docs`) + composite `validate-job`, the job scaffolder + generated `AGENTS.md`, the SessionStart + index-regen hooks, and 10 lifecycle skills. Publishing is gated on a security/leak review — see [`docs/PUBLISHING.md`](docs/PUBLISHING.md).
101
+
102
+ ### CLI
103
+
104
+ ```
105
+ jobwright doctor | init | jobs-index [--check] | validate-job <folder> [--offline]
106
+ check {syntax|job-defs|deps|architecture|docs} <paths>
107
+ new-job <ticket> "<name>" | gen-agents | diff-job <job>
108
+ ```
109
+
110
+ MIT licensed.
@@ -0,0 +1,80 @@
1
+ # jobwright
2
+
3
+ [![CI](https://github.com/kyle-chalmers/jobwright/actions/workflows/ci.yml/badge.svg)](https://github.com/kyle-chalmers/jobwright/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/jobwright.svg)](https://pypi.org/project/jobwright/)
5
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
8
+
9
+ **An open-source AI layer for governing, validating, and safely shipping data-orchestration jobs with Claude Code.**
10
+
11
+ jobwright treats your *jobs* — Databricks Jobs, Airflow DAGs, dbt jobs, Snowflake Tasks — as deployable artifacts that deserve a governed lifecycle: a catalog you can recall before you rebuild, architecture-compliance scanning, a per-job validation gate, and a deploy-safety guard that prompts for confirmation before known destructive commands (so a stale-definition overwrite can't happen unattended).
12
+
13
+ It is the third in a family of Claude Code "AI layer" kits:
14
+
15
+ | Kit | Domain |
16
+ |---|---|
17
+ | [ticketwright](https://github.com/kyle-chalmers/ticketwright) | ticket-driven data work (plan → implement → validate) |
18
+ | [streamsnow](https://github.com/kyle-chalmers/streamsnow) | Streamlit-in-Snowflake apps (build → govern → ship) |
19
+ | **jobwright** | **data-orchestration jobs (govern → validate → safely ship)** |
20
+
21
+ > **Complementary, not overlapping with ticketwright.** ticketwright's Databricks adapter is about *querying* a warehouse while doing a ticket. jobwright is about the *jobs themselves* — their definitions, architecture compliance, and safe deploy. ticketwright's "pause before any prod job deploy" gotcha is exactly the hand-off point to jobwright's `diff-job` + deploy-safety guard.
22
+
23
+ ## Why
24
+
25
+ - **A deploy-safety guard.** A `PreToolUse` hook asks for confirmation before destructive orchestration commands (`databricks jobs reset`/`delete`, `airflow dags delete`, `DROP TASK`, …) and before destructive warehouse SQL — including SQL hidden in a `-f` file. On `api-reset` platforms it specifically blocks a reset from a possibly-stale repo definition until you've run a live-vs-repo diff. This turns a prose rule into a runtime gate.
26
+ - **A jobs index.** A deterministic `JOBS.md` + `OBJECTS.md` over every job in the repo — ticket, purpose, schedule, owner, status, and architecture-compliance flags — so you (and the agent) recall prior work before rebuilding, and see migration debt at a glance.
27
+ - **Architecture compliance.** A static scan of job code for deprecated-schema references and layer-referencing violations, driven by config — no database connection.
28
+ - **Per-job validation.** A local PASS/FAIL gate that runs the same checks your CI does, scoped to one job.
29
+
30
+ ## Two seams
31
+
32
+ jobwright has two orthogonal abstraction axes, expressed as two config blocks:
33
+
34
+ - **platform** — the orchestrator a job *runs on* (`databricks` / `airflow` / `dbt` / `snowflake_tasks` / …). Owns the lifecycle verbs. Its `deploy_model` (`api-reset` | `git-sync` | `sql-ddl`) decides whether live-vs-repo drift even applies.
35
+ - **warehouse / architecture** — the store a job *reads/writes* and the static schema-reference rules. Policy only; jobwright never opens a database connection.
36
+
37
+ The platform seam is pluggable via thin adapters (a Python class implementing the verb contract + a markdown playbook). Shipped adapters span all three deploy models:
38
+
39
+ | Platform | `deploy_model` | Drift detection |
40
+ |---|---|---|
41
+ | Databricks Jobs (reference) | `api-reset` | yes — live can drift from repo |
42
+ | Snowflake Tasks | `sql-ddl` | yes — live DDL vs repo DDL |
43
+ | Apache Airflow | `git-sync` | n/a — git is the source of truth |
44
+ | dbt | `git-sync` | n/a — project is the source of truth |
45
+
46
+ The generic checks (`syntax`, `deps`, `architecture`, `docs`) and the deploy-safety guard are platform-agnostic — see [`examples/`](examples/) for a Databricks repo and an Airflow + BigQuery repo. Adding a platform is two files (a `JobPlatformAdapter` subclass + a markdown playbook); see [`CONTRIBUTING.md`](CONTRIBUTING.md).
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install jobwright # or: uvx jobwright
52
+ # Claude Code plugin (skills + hooks):
53
+ # /plugin marketplace add kyle-chalmers/jobwright
54
+ # /plugin install jobwright@jobwright
55
+ ```
56
+
57
+ ## Quickstart
58
+
59
+ ```bash
60
+ cp jobwright.config.example.yaml jobwright.config.yaml # edit for your repo
61
+ jobwright doctor # check config + environment
62
+ jobwright jobs-index # render jobs/JOBS.md + OBJECTS.md
63
+ jobwright diff-job BI-813 # live-vs-repo drift for one job
64
+ ```
65
+
66
+ See [`jobwright.config.example.yaml`](jobwright.config.example.yaml) for the full, documented config (including an Airflow + BigQuery variant) and [`examples/`](examples/) for a runnable sample repo.
67
+
68
+ ## Status
69
+
70
+ Alpha. Shipped: the deploy-safety guard, the jobs index, four platform adapters (Databricks, Snowflake Tasks, Airflow, dbt) across all three deploy models, the generic checks (`syntax` / `job-defs` / `deps` / `architecture` / `docs`) + composite `validate-job`, the job scaffolder + generated `AGENTS.md`, the SessionStart + index-regen hooks, and 10 lifecycle skills. Publishing is gated on a security/leak review — see [`docs/PUBLISHING.md`](docs/PUBLISHING.md).
71
+
72
+ ### CLI
73
+
74
+ ```
75
+ jobwright doctor | init | jobs-index [--check] | validate-job <folder> [--offline]
76
+ check {syntax|job-defs|deps|architecture|docs} <paths>
77
+ new-job <ticket> "<name>" | gen-agents | diff-job <job>
78
+ ```
79
+
80
+ MIT licensed.
@@ -0,0 +1,45 @@
1
+ ---
2
+ seam: platform
3
+ kind: airflow
4
+ transport: cli
5
+ deploy_model: git-sync
6
+ requires: []
7
+ auth: |
8
+ Airflow CLI talks to the configured Airflow environment (AIRFLOW_HOME / connection).
9
+ Verify: `airflow dags list`.
10
+ destructive_patterns:
11
+ # MUST stay in sync with AirflowAdapter.destructive_patterns (jobwright/platforms/airflow.py).
12
+ - 'airflow\s+dags\s+delete\b'
13
+ - 'airflow\s+db\s+(reset|clean|downgrade)\b'
14
+ ---
15
+
16
+ # Apache Airflow adapter
17
+
18
+ Deploy model **git-sync**: DAGs are code, so the repo file *is* the definition. There is
19
+ no live-vs-repo drift — `get_live_definition` / `diff_live_vs_repo` raise ManualFallback
20
+ and point at `git diff`. "Deploy" is a git push / DAG-folder sync, not an API call.
21
+
22
+ ## verb: list_jobs
23
+ ```bash
24
+ airflow dags list -o json
25
+ ```
26
+
27
+ ## verb: get_job_definition
28
+ Reads the DAG `.py` from the configured `dags_dir`.
29
+
30
+ ## verb: get_live_definition / diff_live_vs_repo
31
+ N/A (git-sync) — use `git status` / `git diff`.
32
+
33
+ ## verb: list_active_runs
34
+ ```bash
35
+ airflow dags list-runs -d <dag_id> --state running -o json
36
+ ```
37
+
38
+ ## verb: trigger_run
39
+ Checks active runs first, then `airflow dags trigger <dag_id>`.
40
+
41
+ ## verb: get_run / get_run_output
42
+ Need dag_id (+ task_id): `airflow dags list-runs -d <dag_id>`, `airflow tasks logs <dag_id> <task_id> <run_id>`.
43
+
44
+ ## Gotchas
45
+ - Airflow's REST API has no delete-DAG endpoint by design — deletion is a code/file operation.
@@ -0,0 +1,72 @@
1
+ ---
2
+ seam: platform
3
+ kind: databricks
4
+ transport: cli
5
+ deploy_model: api-reset
6
+ requires: [profile]
7
+ auth: |
8
+ Databricks CLI profile in ~/.databrickscfg (host + token), selected by
9
+ `platform.profile` in jobwright.config.yaml. Verify: `databricks jobs list --profile <p>`.
10
+ jobwright never stores the token — only the profile NAME.
11
+ destructive_patterns:
12
+ # MUST stay in sync with DatabricksAdapter.destructive_patterns (jobwright/platforms/databricks.py).
13
+ # selftest.sh checks both lists agree.
14
+ - 'databricks\s+jobs\s+reset\b'
15
+ - 'databricks\s+jobs\s+delete\b'
16
+ - 'databricks\s+jobs\s+update\b'
17
+ - 'databricks\s+jobs\s+(run-now|submit)\b'
18
+ ---
19
+
20
+ # Databricks Jobs adapter
21
+
22
+ Reference implementation of the jobwright platform verb contract. Deploy model is
23
+ **api-reset**: the live job definition can drift from the repo JSON, so drift
24
+ detection is mandatory and `databricks jobs reset` is guarded.
25
+
26
+ > **The incident this guards against:** `databricks jobs reset` is a *full replace*
27
+ > of a job from the file you pass. Repo JSONs can be stale (jobs are sometimes
28
+ > edited in the UI). Resetting from a stale JSON has overwritten correct live state
29
+ > and broken production jobs. Always `diff-job` before a reset.
30
+
31
+ ## verb: list_jobs
32
+ **Out:** `[{job_id, name, paused, schedule}]`
33
+ ```bash
34
+ databricks jobs list -o json --profile <p>
35
+ ```
36
+
37
+ ## verb: get_job_definition
38
+ **In:** ref (ticket / job name / filename stem) · **Out:** repo-side definition
39
+ Resolves the JSON under `platform.job_def_dirs` by filename stem, ticket-key prefix, or `name` field.
40
+
41
+ ## verb: get_live_definition
42
+ **In:** ref · **Out:** live definition from the workspace
43
+ ```bash
44
+ databricks jobs get <job_id> -o json --profile <p>
45
+ ```
46
+
47
+ ## verb: diff_live_vs_repo
48
+ **In:** ref (+ optional repo_path) · **Out:** `{drift, added, removed, changed, detail}`
49
+ Normalizes both sides (unwraps `settings`, drops volatile keys: job_id, created_time, creator_user_name, run_as, …) then compares dotted paths.
50
+
51
+ ## verb: list_active_runs
52
+ **In:** ref · **Out:** `[{run_id, state, started}]`
53
+ ```bash
54
+ databricks jobs list-runs --job-id <id> --active-only -o json --profile <p>
55
+ ```
56
+
57
+ ## verb: deploy
58
+ Withheld as a one-shot — use the `/safe-deploy` skill (diff → confirm → reset). The guard hook backstops any raw `databricks jobs reset`.
59
+
60
+ ## verb: trigger_run
61
+ Checks `list_active_runs` first (refuses if a run is active), then `databricks jobs run-now <id>`.
62
+
63
+ ## verb: get_run / get_run_output
64
+ ```bash
65
+ databricks jobs get-run <run_id> -o json --profile <p>
66
+ databricks jobs get-run-output <run_id> -o json --profile <p>
67
+ ```
68
+
69
+ ## Gotchas
70
+ - Repo JSONs are the *unwrapped* settings (top-level `name`, `schedule`, `tasks`); the API returns them under `settings`.
71
+ - Repo JSONs have **no** `job_id`; jobwright resolves the live job by matching `name`.
72
+ - A `run-now` timeout does **not** mean the run failed to start — check `list-runs --active-only` before retrying.
@@ -0,0 +1,40 @@
1
+ ---
2
+ seam: platform
3
+ kind: dbt
4
+ transport: cli
5
+ deploy_model: git-sync
6
+ requires: []
7
+ auth: |
8
+ `dbt` CLI with a configured profiles.yml. Verify: `dbt debug`.
9
+ destructive_patterns:
10
+ # MUST stay in sync with DbtAdapter.destructive_patterns (jobwright/platforms/dbt.py).
11
+ - 'dbt\b(?=.*\b(?:run|build|seed|snapshot)\b)(?=.*(?:--target|-t)[=\s]+prod(?![\w-]))'
12
+ - 'dbt\b(?=.*\brun-operation\b)'
13
+ ---
14
+
15
+ # dbt adapter
16
+
17
+ Deploy model **git-sync**: the dbt project is the source of truth. dbt's unit is a model,
18
+ not a standalone job, so discovery/run map best-effort to the CLI and drift verbs raise
19
+ ManualFallback. The guard is the high-value piece: a `--target prod` run/build, or any
20
+ `run-operation`, mutates the warehouse and must be confirmed.
21
+
22
+ ## verb: list_jobs
23
+ ```bash
24
+ dbt ls --resource-type model --output name
25
+ ```
26
+
27
+ ## verb: get_job_definition
28
+ Reads the model `.sql` from the models dir (configured via `dags_dir`).
29
+
30
+ ## verb: get_live_definition / diff_live_vs_repo
31
+ N/A (git-sync) — use `git diff`.
32
+
33
+ ## verb: trigger_run
34
+ `dbt build --select <model>` (default target; a prod target is caught by the guard).
35
+
36
+ ## verb: list_active_runs / get_run / get_run_output
37
+ dbt core has no run registry — read `target/run_results.json`, or use the dbt Cloud Runs API.
38
+
39
+ ## Gotchas
40
+ - A bare `dbt run` uses the default target; only `--target prod` (or `-t prod`) trips the guard.
@@ -0,0 +1,51 @@
1
+ ---
2
+ seam: platform
3
+ kind: snowflake_tasks
4
+ transport: cli
5
+ deploy_model: sql-ddl
6
+ requires: [profile]
7
+ auth: |
8
+ `snow` CLI connection (named by platform.profile). Verify: `snow sql -q "SHOW TASKS"`.
9
+ destructive_patterns:
10
+ # MUST stay in sync with SnowflakeTasksAdapter.destructive_patterns (jobwright/platforms/snowflake_tasks.py).
11
+ - '\bDROP\s+TASK\b'
12
+ - '\bCREATE\s+OR\s+REPLACE\s+TASK\b'
13
+ - '\bALTER\s+TASK\b'
14
+ ---
15
+
16
+ # Snowflake Tasks adapter
17
+
18
+ Deploy model **sql-ddl**: a task is defined by DDL and the live object can drift from
19
+ the repo DDL — so drift detection applies and replace/drop/alter are guarded.
20
+
21
+ ## verb: list_jobs
22
+ ```sql
23
+ SHOW TASKS
24
+ ```
25
+
26
+ ## verb: get_job_definition
27
+ Reads the task DDL `.sql` from the configured `job_def_dirs`.
28
+
29
+ ## verb: get_live_definition
30
+ ```sql
31
+ SELECT GET_DDL('TASK', '<task_name>')
32
+ ```
33
+
34
+ ## verb: diff_live_vs_repo
35
+ Normalizes whitespace/case of repo DDL vs live `GET_DDL` and reports drift.
36
+
37
+ ## verb: list_active_runs
38
+ ```sql
39
+ SELECT QUERY_ID, STATE FROM TABLE(INFORMATION_SCHEMA.TASK_HISTORY(TASK_NAME => '<task>')) WHERE STATE = 'EXECUTING'
40
+ ```
41
+
42
+ ## verb: trigger_run
43
+ ```sql
44
+ EXECUTE TASK <task_name>
45
+ ```
46
+
47
+ ## verb: get_run / get_run_output
48
+ Query `TASK_HISTORY` / `QUERY_HISTORY` by `QUERY_ID`.
49
+
50
+ ## Gotchas
51
+ - `CREATE OR REPLACE TASK` silently overwrites the live definition — always `diff-job` first.
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ # jobwright kit self-test — run before committing / publishing.
3
+ # Verifies: lint, the Phase 0 contract tests (adapter verb coverage,
4
+ # md/py destructive-pattern sync, the deploy-safety guard, index determinism),
5
+ # and that no platform names leak into the skills (skills call verbs, not tools).
6
+ set -euo pipefail
7
+
8
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
9
+ cd "$ROOT"
10
+
11
+ PY="${PYTHON:-python3}"
12
+ if [ -x .venv/bin/python ]; then PY=".venv/bin/python"; fi
13
+
14
+ echo "==> ruff"
15
+ "$PY" -m ruff check . || { echo "FAIL: ruff"; exit 1; }
16
+
17
+ echo "==> pytest (Phase 0 contract)"
18
+ "$PY" -m pytest -q || { echo "FAIL: pytest"; exit 1; }
19
+
20
+ echo "==> skill leak check"
21
+ if [ -d skills ] && [ -n "$(find skills -name '*.md' -print -quit 2>/dev/null)" ]; then
22
+ if grep -rEli '\b(databricks|airflow|dbt|dagster|prefect|snowflake_tasks|glue)\b' skills/ >/dev/null 2>&1; then
23
+ echo "FAIL: a platform name leaked into skills/ — skills must call abstract verbs, not name tools"
24
+ grep -rEli '\b(databricks|airflow|dbt|dagster|prefect|snowflake_tasks|glue)\b' skills/ || true
25
+ exit 1
26
+ fi
27
+ fi
28
+
29
+ echo "OK: jobwright selftest passed"