gardener is a safety-gated Python CLI that dispatches Claude Code against
a fleet of software repos, in two distinct ways:
alignchecks one target repo at a time against a conventions repo you supply — your own engineering conventions and alignment checklists, in a git repo gardener only ever reads — and, if authorized, fixes what's missing.tend/garden/overnightmake real, broader progress on a repo (or a whole opt-in list of them, unattended overnight) by dispatching that repo's own<slug>-dev-loopClaude Code skill — triage, implement, test, PR — never merging without an explicit, separate per-repo opt-in. Those skills are generated bycreate-dev-loop, gardener's open-source companion project;tendbootstraps one automatically if the target repo doesn't have one yet.
gardener's own job is orchestration and safety-gating, in plain Python. The
actual reading/analysis/implementation judgment is delegated to a
dispatched, safety-gated claude CLI invocation in every mode — gardener
never itself decides what "aligned" means or what a fix should look like;
it only decides how much a dispatched Claude run is allowed to do about
it. See Usage below for the full command set.
gardener align --repo <owner/repo>: clones the target repo read-only, clones (or refreshes) a local cache of your conventions repo (the source of truth for what "aligned" means — gardener is only the tool that consumes it), builds a prompt combining that repo'sALIGNMENT_PROMPT.mdwith the target repo's identity and the requested mode's constraints, and dispatches one headlessclaude -prun to produce a gap checklist — or, if explicitly authorized, to act on it.gardener tend --repo <owner/repo>: dispatches the target repo's own<slug>-dev-loopskill instead — real triage/implement/test/PR work, not a conventions gap-check. Seegardener tendin the usage docs.gardener garden+gardener overnight: an opt-in list of repos and the unattended batch dispatcher that tends them one after another overnight. See docs/OVERNIGHT.md.gardener dashboard: a local, read-only web UI overgardener status's own run history plus everytend/overnightlog still being written to, so an unattended overnight run doesn't require polling the CLI by hand to see what it's doing. See docs/DASHBOARD.md.gardener update: fast-forwards gardener's own checkout toorigin—gardener overnightdoes this automatically before each run (opt out with--no-self-update), so a box running it unattended stays current without anyone needing to notice new commits andgit pullby hand. See Self-update.
Requires Python 3.10+, and the git, gh, and claude CLIs already
installed and authenticated (gh auth status, and a working claude
login) — gardener shells out to all three rather than reimplementing git
hosting, auth, or the agent loop itself.
git clone https://github.com/dmccoystephenson/gardener.git
cd gardener
pip install -e .This installs the gardener console script (via pyproject.toml's
[project.scripts] entry point) and leaves the source editable — which is
also what makes gardener update/overnight's self-update work at all
(see Self-update); a non-editable install has
no .git checkout to fast-forward, so it degrades to a no-op.
Only align needs this. tend, garden, and overnight dispatch each
repo's own dev-loop skill and work without one.
align audits a target repo against your engineering conventions, so you
have to tell it where those live. gardener deliberately ships no default
— a built-in one would mean silently auditing your repos against somebody
else's opinions — so align fails fast with setup instructions until one is
configured:
# Either, for every run:
export GARDENER_CONVENTIONS_URL=https://github.com/you/your-conventions.git
# Or, per invocation:
gardener align --repo <owner/repo> --conventions-repo <git-url>Any git repo works as long as it contains these files. gardener checks only that they exist — the contents are entirely yours:
| File | What it's for |
|---|---|
README.md |
Orientation for a reader of the conventions repo itself |
ALIGNMENT_PROMPT.md |
What a run should audit for — the substance of your conventions |
ALIGNMENT_CHECKLIST.md |
The checklist shape a run reports its findings back in |
docs/CLAUDE_MD_STRUCTURE.md |
What a CLAUDE.md should contain |
docs/README_STRUCTURE.md |
What a README.md should contain |
docs/CONTRIBUTING_STANDARDS.md |
What a CONTRIBUTING.md should contain |
docs/ISSUE_TEMPLATES.md |
Expected .github/ISSUE_TEMPLATE/ shape |
docs/CODEOWNERS.md |
Expected CODEOWNERS conventions |
docs/CI_STRUCTURE.md |
Expected CI workflow shape |
docs/COMMIT_PR_CONVENTIONS.md |
Branch naming, commit style, PR conventions |
docs/REVIEW_PROMPTING.md |
How code review should be requested and conducted |
docs/DEV_LOOP_PATTERNS.md |
Dev-loop conventions for repos that have one |
The dispatched run reads every one of them before forming a judgment, which is why all twelve are required: a missing file means auditing against an incomplete rubric, so gardener refuses rather than reporting a confidently-wrong result. Stub files are fine while you're building the set out.
The checkout is cached at ~/.cache/gardener/conventions (override with
$GARDENER_CACHE_DIR) and refreshed each run — --no-refresh-conventions
reuses it as-is. Pointing gardener at a different conventions repo
re-points and refreshes that cache even under --no-refresh-conventions,
since reusing the previous repo's checkout would produce a wrong answer
rather than a stale one. gardener never commits or pushes into this cache.
By default gardener alerts nowhere except its own local run history — you
have to run gardener status or watch terminal output to see how a run
went. To get a Discord notification on every run's outcome instead,
configure a webhook one of two ways (checked in this order):
# 1. Environment variable (simplest — set it wherever gardener runs)
export GARDENER_DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/XXX/YYY"
# 2. A gitignored config file, for a persistent/cron context where
# exporting an env var per-invocation isn't practical — a plain
# KEY=VALUE dotenv file, mode 600):
mkdir -p ~/.local/state/gardener # or $GARDENER_STATE_DIR if overridden
umask 077
echo 'DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/XXX/YYY' \
> ~/.local/state/gardener/notify.envNo webhook configured (neither of the above) means notifications are a
clean no-op — gardener align still works exactly the same, nothing
prints or fails because of it. See docs/ALERTING.md
for how this is implemented.
gardener align --repo <owner/repo> [--implement] [--file-issue] [--conventions-repo <git-url>]
gardener tend --repo <owner/repo> [--allow-merge]
gardener allowlist list | add --repo <owner/repo> | remove --repo <owner/repo>
gardener garden list | add --repo <owner/repo> | remove --repo <owner/repo>
gardener overnight [--hours N] [--concurrency N] [--strategy round-robin|issue-count|random] [--no-self-update]
gardener status [--repo <owner/repo>]
gardener tail-transcript <path> [-f]
gardener dashboard [--port N]
gardener update [--check]
See docs/USAGE.md for the full command reference:
every flag (--implement, --file-issue, --model, --timeout,
--conventions-repo, --no-refresh-*), how tend bootstraps and
dispatches a target repo's own dev-loop skill, orphaned-work recovery,
concurrent-dispatch safety, the merge allow-list, live session/transcript
visibility, and run logs.
For the unattended "tend to my garden while I sleep" flow (the garden
opt-in list, overnight's batching/budget/resume-cursor design, and the
per-device wiring recipes it's actually been deployed with), see
docs/OVERNIGHT.md.
For the dashboard's garden view (the table/plant-plot of every repo's health), see docs/DASHBOARD.md.
Please file a bug report here.
See CONTRIBUTING.md for how to propose a change, branch naming, and running the test suite. Community participation is governed by the Code of Conduct.
PYTHONPATH=. python3 -m unittest discover -s tests -v
A passing run ends with OK. None of the automated tests hit the network,
invoke a real claude/git/gh process, or mutate a real repo. See
docs/TESTING.md for exactly what each test module
covers and for the manual/end-to-end verification steps required before
trusting a change to the dispatch layer.
gardener never invokes claude with bypassPermissions or any equivalent
auto-approve-everything mode, for any mode, under any flag combination —
enforced in dispatch.py, which raises rather than silently proceeding if
it's ever reached. See docs/SAFETY.md for the full
three-layer tool-scoping model, how headless tend dispatch handles the
"ask the user before merging" problem with nobody there to ask, and the
merge allow-list mechanics. See SECURITY.md to report a
vulnerability or to review the trust model before pointing gardener at a
repo you don't fully trust.
See docs/ALERTING.md for the Notifier
abstraction, DiscordNotifier/NullNotifier/CompositeNotifier, and how
_notify_run maps a run's outcome to a severity.
No build step — this is a stdlib-only Python CLI (see
Architecture below). Clone it, pip install -e ., edit,
re-run the tests.
Working end to end, real-verified against live repos in every dispatch
mode (align report-only, tend with and without --allow-merge,
overnight including its resume cursor and --concurrency, live
transcript visibility, and the create-dev-loop bootstrap path). See
docs/PROJECT_STATUS.md for the full history
of what was run, when, and what was confirmed afterward.
Stdlib-only Python — no third-party pip dependencies; gardener shells out
to git, gh, and claude rather than reimplementing git hosting,
GitHub API auth, or an agent loop. See docs/ARCHITECTURE.md
for the full module tree and gardener's relationship to the conventions
repo it consumes.
create-dev-loop— the open-source Claude Code skill that generates the per-repo<slug>-dev-loopskillsgardener tenddispatches. gardener orchestrates and safety-gates when those skills run across a fleet; create-dev-loop decides what each one knows about its repo. Either is usable without the other: create-dev-loop's skills run standalone as a/<slug>-dev-loopslash command, and gardener will bootstrap one on demand for any repo in its garden that lacks one.