Skip to content

Repository files navigation

gardener

CI

gardener is a safety-gated Python CLI that dispatches Claude Code against a fleet of software repos, in two distinct ways:

  • align checks 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/overnight make real, broader progress on a repo (or a whole opt-in list of them, unattended overnight) by dispatching that repo's own <slug>-dev-loop Claude Code skill — triage, implement, test, PR — never merging without an explicit, separate per-repo opt-in. Those skills are generated by create-dev-loop, gardener's open-source companion project; tend bootstraps 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.

Description

  • 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's ALIGNMENT_PROMPT.md with the target repo's identity and the requested mode's constraints, and dispatches one headless claude -p run 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-loop skill instead — real triage/implement/test/PR work, not a conventions gap-check. See gardener tend in 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 over gardener status's own run history plus every tend/overnight log 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 doctor: a read-only pre-flight check over gardener's own local state — the CLIs it shells out to, the state directory, every cache clone's refresh-readiness, and whether each garden/allow-list entry still resolves to itself on GitHub. Every check comes from a failure that really did consume a garden slot on a real overnight run (a clone left dirty by a killed dev-loop run fails tend every night afterward; a renamed target repo fails in a way that looks fixed for exactly one run after you clear the cache). It reports findings and the exact command to fix each, and repairs nothing itself. Exits 1 on any error, so it works as a gate before the nightly run. See gardener doctor.
  • gardener update: fast-forwards gardener's own checkout to origingardener overnight does 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 and git pull by hand. See Self-update.

Installation

First Time Setup

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.

Conventions repo

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 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.

$GARDENER_CACHE_DIR overrides the cache root — gardener's whole cache, not just the conventions checkout. Setting it to /somewhere/else moves both conventions/ above and the target-repo clones at ~/.cache/gardener/repos/<owner>__<repo> (see docs/USAGE.md) to /somewhere/else/conventions and /somewhere/else/repos. Worth setting if ~/.cache isn't where this machine's free space is: a garden of any size accumulates a full checkout per repo, dependency caches included (see cli.py's PRESERVED_DEPENDENCY_DIRS).

Alerting (optional)

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.env

No 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.

Every alert carries the name of the device that sent it in the embed's footer, so two machines tending overlapping gardens into the same channel stay distinguishable. It defaults to the host's socket.gethostname(), which is often not a name anyone recognizes (a UserLand session's hostname, for instance) — override it the same two ways:

export GARDENER_DEVICE_NAME="pixel-userland"
# or, in the same notify.env file as above:
echo 'GARDENER_DEVICE_NAME=pixel-userland' >> ~/.local/state/gardener/notify.env

Usage

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 ps [-a] [-q]
gardener stop <session>... | --all [-t SECONDS]
gardener kill <session>... | --all [-s SIGNAL]
gardener doctor [-v] [--offline]
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, run logs, and the docker-shaped ps/stop/kill commands for listing a running session and stopping it together with everything it dispatched.

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), the three progress bars an overnight run drives (garden cycle, time budget, current batch), and how the page reports that it has stopped being live, see docs/DASHBOARD.md.

Support

Experiencing a bug?

Please file a bug report here.

Contributing

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.

Testing

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.

Safety model

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.

Alerting design

See docs/ALERTING.md for the Notifier abstraction, DiscordNotifier/NullNotifier/CompositeNotifier, and how _notify_run maps a run's outcome to a severity.

Development

No build step — this is a stdlib-only Python CLI (see Architecture below). Clone it, pip install -e ., edit, re-run the tests.

Project Status

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.

Architecture

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.

Related projects

  • create-dev-loop — the open-source Claude Code skill that generates the per-repo <slug>-dev-loop skills gardener tend dispatches. 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-loop slash command, and gardener will bootstrap one on demand for any repo in its garden that lacks one.

About

Safety-gated CLI that dispatches Claude Code to align repos against conventions, or tend a whole fleet of repos unattended overnight via each one's own dev-loop skill.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages