Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2,547 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

teatree logo

CI Python 3.13+ License

A personal workflow harness wrapped around an AI coding agent. Single-author tool; the code lives in a public repo in case any of it is useful to anyone else.

Experimental: APIs and config keys may change without backwards-compatibility guarantees — the only obligation is that all registered overlays are updated in the same change.

Teatree sits at the shell, alongside the editor. It turns a ticket URL into a merged pull request by creating synchronized worktrees across the repos a ticket touches, provisioning isolated databases and ports, driving the work through code → test → review → ship phases, and keeping enough durable state to survive context loss and long waits.

Under the hood it is a Django project with a plugin system (overlays) that adapts it to a given set of repos, CI, and services.

graph TB
  subgraph "t3 CLI"
    direction LR
    lifecycle["lifecycle<br/>worktree provisioning"]
    workspace["workspace<br/>multi-repo setup"]
    db["db<br/>database operations"]
    run["run<br/>service management"]
    pr["pr<br/>PR creation"]
    followup["followup<br/>code host sync"]
  end

  subgraph "Loop & Statusline"
    direction LR
    scanners["scanners<br/>my PRs, reviewer PRs,<br/>assigned issues, Slack, Notion"]
    statusline["statusline.txt<br/>3 zones: anchors / action / in flight"]
    dispatch["dispatch<br/>turns signals into agent actions"]
  end

  subgraph "Claude Plugin"
    direction LR
    skills["lifecycle skills"]
    hooks["hooks<br/>routing, guards, tracking"]
    required["required skills<br/>superpowers, ac-*"]
  end

  scanners -->|"feed signals to"| dispatch
  dispatch -->|"renders"| statusline
  scanners -->|"query"| pr & followup
  skills -->|"delegates to"| lifecycle & workspace & db & run & pr
  hooks -->|"enforces"| skills
Loading

Gaps it tries to fill

Each section below names a piece of friction the author kept hitting and what teatree does about it. None of these are framed as comparisons — other tools solve some of the same shapes well, and teatree borrows from them where it can.

A merge step that is neither a manual click nor a blind auto-merge

The author kept ending up at one of two extremes: either babysitting every merge by hand (every PR a separate context switch back to the browser), or flipping on full auto-merge and watching the agent merge PRs against branches the reviewer had moved underneath. The first wastes the day; the second loses trust in a single bad merge.

Teatree puts a two-step contract between "looks done" and "merged". An orchestrator pass issues a per-diff CLEAR after an independent cold review; a separate merge worker re-verifies the live HEAD SHA, the green checks, and the non-draft state at the moment of merge, and refuses raw merge commands by default. Two independent passes have to agree on the same SHA before the host API gets called.

Posting under your identity without losing track of what was posted

The author wanted the agent to handle Slack DMs, PR comments, MR approvals, Notion writes — anything that normally needs a hand on the keyboard — without the dread of finding out tomorrow that a message went to the wrong place or under the wrong voice. Logs in ~/.shell_history are not enough; the agent's own claim about what it posted is the thing that needs verifying.

Every on-behalf write records an OutboundClaim row, then re-reads the target surface (Slack permalink, GitLab note, Notion block) and compares the live content against the claim. A drift means the post landed wrong, was edited, or never made it; teatree surfaces those as actionable rows rather than silent failures. Combined with an approval gate (OnBehalfApproval), nothing colleague-facing ships unless the user has explicitly opted into either per-action approval or end-to-end autonomy for that overlay.

Workflow state that survives the session

The author kept losing the picture every time a conversation ended or context got compacted — what was in flight, which PR was waiting on what, which review was half-done. Rebuilding it from gh pr list and chat history every morning gets old.

Teatree puts the state into a Django-backed SQLite/Postgres database: Ticket, Worktree, Task, PullRequest, each with its own state machine. Transitions are guarded methods (Ticket.code(), Ticket.ship()), not free-form field writes — the predicates run, the dependent gates stay aligned, illegal moves raise InvalidTransitionError. Snapshots before context compaction get recovered automatically on the next session start. Not perfect, but more reliable than picking up the picture from scratch.

A chat-only operating model that does not block on a TTY

The author carries a laptop around and answers questions from a phone. Most agent harnesses assume a TTY: when the agent has a question, the run blocks until someone is in front of a terminal to answer. That makes long autonomous sessions impossible — every clarifying question becomes a hard stop.

Teatree resolves one operating mode whose posture says whether the user is reachable. Under a deferring posture, structured questions become durable DeferredQuestion rows instead of blocking; the user answers them later from Slack, via t3 teatree questions answer. The agent keeps working on whatever it can in the meantime. Replies and prompts the user receives all happen in Slack DMs; no shared dashboard, no shared SaaS, no DevOps onboarding.

Multi-repo, multi-overlay worktree provisioning

The author's typical ticket is not "edit one file"; it is "change the backend, the frontend, the translations bundle, and the CI config, all at once." Each repo needs its own isolated worktree, its own database, its own ports, so two tickets in flight do not collide on the same dev server.

t3 <overlay> workspace ticket <url> reads the ticket, decides which repos are affected, creates one git worktree per repo under a single ticket directory, allocates ports, provisions DBs, generates env files, and starts the services. Each ticket runs in isolation; multiple tickets in flight share no infrastructure. Overlay packages carry the project-specific glue (which repos, which CI, which probes); the core stays generic.

A long-running loop that turns signals into actions

The author kept noticing the agent only works while someone is actively prompting it. PRs sit waiting for review nudges, CI failures go unreviewed, ticket changes pile up in the inbox until someone glances at them.

A long-running /loop slot inside the interactive session ticks every ~12 minutes. Each tick fans out to scanners that watch assigned issues, open PRs, PRs assigned for review, Slack mentions, the Notion bridge, and the local task queue. Findings render to a statusline file the Claude Code statusline hook reads in under 10ms, so live status sits at the top of every session without polling. Lease-gated dispatch turns scanner findings into agent actions when there is something to do, and keeps quiet when there is not.

What teatree is NOT

A few honest scope statements, so anyone evaluating this knows what they are looking at:

  • Not a shared corporate platform. Single-author tool. No multi-tenant SaaS, no team dashboard, no SSO. It uses the user's own GitLab / GitHub / Slack credentials and runs on the user's laptop.
  • Not an IDE plugin. It lives at the shell. The editor stays whatever editor the user prefers.
  • Not a replacement for the agent CLI. Teatree wraps the agent CLI (currently Claude Code; the agent runtime is pluggable) with state, loops, integrations, and skills. The agent does the creative work; teatree does the mechanical work.
  • Not a stable, polished product. It is in motion, expected to break, expected to change shape. The author dogfoods it daily on real client work; bugs surface fast because every broken edge stops a real ticket.

Core concepts

Teatree coordinates work through four state machines — each transition is a typed code path with tests, not a prompt the model might skip. The models live in src/teatree/core/models/ (ticket.py, worktree.py, task.py, pull_request.py).

Ticket — tracks a unit of work from intake to delivery. The lifecycle phases (ticket → code → test → review → ship) drive corresponding ticket states. The full Ticket.State set is not_started → scoped → started → planned → coded → tested → reviewed → shipped → in_review → merged → retrospected → delivered, plus ignored for work that is consciously skipped. This diagram is generated from the Ticket model's @transition decorators; edit the model, not the diagram (scripts/hooks/generate_fsm_diagrams.py).

stateDiagram-v2
    [*] --> not_started
    not_started --> scoped : scope
    not_started --> coded : code_direct
    not_started --> reviewed : reconcile_reviewed
    not_started --> merged : reconcile_merged
    not_started --> review_posted : mark_review_no_action
    not_started --> review_posted : mark_reviewed_externally
    not_started --> ignored : ignore
    scoped --> started : start
    scoped --> coded : code_direct
    scoped --> reviewed : reconcile_reviewed
    scoped --> merged : reconcile_merged
    scoped --> review_posted : mark_review_no_action
    scoped --> review_posted : mark_reviewed_externally
    scoped --> ignored : ignore
    started --> started : start
    started --> planned : plan
    started --> coded : code_direct
    started --> reviewed : reconcile_reviewed
    started --> merged : reconcile_merged
    started --> review_posted : mark_review_no_action
    started --> review_posted : mark_reviewed_externally
    started --> ignored : ignore
    planned --> coded : code
    planned --> reviewed : reconcile_reviewed
    planned --> merged : reconcile_merged
    planned --> review_posted : mark_review_no_action
    planned --> review_posted : mark_reviewed_externally
    planned --> ignored : ignore
    coded --> started : rework
    coded --> tested : test
    coded --> reviewed : reconcile_reviewed
    coded --> merged : reconcile_merged
    coded --> review_posted : mark_review_no_action
    coded --> review_posted : mark_reviewed_externally
    coded --> ignored : ignore
    tested --> started : rework
    tested --> reviewed : reconcile_reviewed
    tested --> reviewed : review
    tested --> merged : reconcile_merged
    tested --> review_posted : mark_review_no_action
    tested --> review_posted : mark_reviewed_externally
    tested --> ignored : ignore
    reviewed --> started : rework
    reviewed --> reviewed : reconcile_reviewed
    reviewed --> shipped : ship
    reviewed --> merged : reconcile_merged
    reviewed --> review_posted : mark_review_no_action
    reviewed --> review_posted : mark_reviewed_externally
    reviewed --> ignored : ignore
    shipped --> started : reopen
    shipped --> shipped : ship
    shipped --> in_review : request_review
    shipped --> merged : reconcile_merged
    shipped --> ignored : ignore
    in_review --> started : reopen
    in_review --> reviewed : reconcile_reviewed
    in_review --> merged : mark_merged
    in_review --> merged : reconcile_merged
    in_review --> ignored : ignore
    merged --> started : reopen
    merged --> reviewed : reopen_for_followup
    merged --> merged : mark_merged
    merged --> merged : reconcile_merged
    merged --> retrospected : retrospect
    merged --> ignored : ignore
    retrospected --> started : reopen
    retrospected --> reviewed : reconcile_reviewed
    retrospected --> retrospected : retrospect
    retrospected --> delivered : mark_delivered
    retrospected --> ignored : ignore
    delivered --> started : reopen
    delivered --> reviewed : reopen_for_followup
    review_posted --> review_posted : mark_review_no_action
    review_posted --> review_posted : mark_reviewed_externally
Loading

Worktree — one repo checkout inside a ticket's workspace. This diagram is generated from the Worktree model's @transition decorators; edit the model, not the diagram (scripts/hooks/generate_fsm_diagrams.py).

stateDiagram-v2
    [*] --> created
    created --> created : teardown
    created --> provisioned : provision
    provisioned --> created : teardown
    provisioned --> provisioned : db_refresh
    provisioned --> provisioned : provision
    provisioned --> services_up : start_services
    services_up --> created : teardown
    services_up --> provisioned : db_refresh
    services_up --> provisioned : start_failed
    services_up --> provisioned : stop_services
    services_up --> services_up : start_services
    services_up --> ready : verify
    ready --> created : teardown
    ready --> provisioned : db_refresh
    ready --> provisioned : stop_services
    ready --> services_up : start_services
    ready --> ready : verify
Loading

Task — claimable work unit with lease and heartbeat. Unlike the others, Task advances through guarded methods (claim, complete, fail, reopen) that take a row lock and a lease rather than @transition decorators, so this diagram is illustrative and maintained by hand, not generated.

stateDiagram-v2
  [*] --> pending
  pending --> claimed: claim
  claimed --> completed: success
  claimed --> failed: error
  claimed --> pending: lease_expired
Loading

PullRequest — tracks delivery state on the code host. This diagram is generated from the PullRequest model's @transition decorators; edit the model, not the diagram (scripts/hooks/generate_fsm_diagrams.py).

stateDiagram-v2
    [*] --> open
    open --> review_requested : request_review
    open --> merged : mark_merged
    open --> closed : mark_closed
    review_requested --> approved : approve
    review_requested --> merged : mark_merged
    review_requested --> closed : mark_closed
    approved --> merged : mark_merged
    approved --> closed : mark_closed
Loading

These models are surfaced in a small Django admin dashboard. A rendered HTML snapshot of that dashboard is generated through Django's test client and drift-checked in CI, so it stays an always-fresh "screenshot": docs/generated/dashboard/admin-index.html (scripts/hooks/generate_dashboard_snapshot.py).

The CLI gets the same treatment: the rendered output of the canonical t3 commands (t3 --help, t3 loop --help) is captured deterministically and drift-checked, an always-fresh fixture that complements the exhaustive CLI reference: docs/generated/cli/representative-output.md (scripts/hooks/generate_cli_output_snapshot.py).

Every state change goes through a method with code behind it. Ticket, Worktree, and PullRequest use django-fsm-style @transition decorators that declare the legal source and target states; Ticket.code() requires state == STARTED, Ticket.ship() requires state == REVIEWED, and so on. Task status moves through guarded methods (claim, complete, fail, reopen) that take a row lock and a lease, raising InvalidTransitionError on an illegal move. Agents do not write to these fields directly; they call the transition, and the transition enforces its own preconditions. The same rule applies to the CLI: any command that affects a state machine calls into a transition, never mutates the field.

Agents read skills to do the creative work (writing code, reviewing a diff, choosing how to test); the CLI owns the mechanical work (branching, ports, DB refresh, pipeline waits, PR validation). Three interfaces sit on top:

  • CLI (t3 ...) — the source of truth. Everything else is a view on top.
  • Loop & Statusline — a long-running /loop slot scans signals, dispatches actions, renders a statusline file the Claude Code hook reads on every prompt.
  • Claude plugin — skills and hooks that teach an agent how to drive the CLI.

Three tiers

1. t3 CLI

The core of teatree. Django management commands handle everything deterministic: state machines, port allocation, database provisioning, worktree creation, PR validation, code host sync. Tested with >90% branch coverage — no prose, no model variance.

t3 teatree worktree provision   # provision worktrees, DBs, ports for a ticket
t3 teatree worktree start       # start all services
t3 teatree workspace ticket     # create multi-repo worktrees from a ticket URL
t3 teatree db refresh           # restore a database dump
t3 teatree pr create            # create a pull request with metadata validation
t3 teatree followup sync        # sync tickets and PRs from code host
t3 goal set/list/clear          # register / list / clear a standing verified-green goal (a Stop-gate blocks a loop turn ending "as done" while the goal's check command is red)
t3 cost                         # cycle-to-date SDK-equivalent spend + effective-token (ET) totals, split by subscription/metered lane
t3 capabilities --json          # machine-readable registry of which t3 commands emit JSON and their exit-code contract (a front-end drives teatree from this)
t3 speak                        # read text aloud on local speakers per [teatree.speak] (no-op unless local = all)
t3 recover                      # find/recover work stranded by a network-outage death (dry-run by default)
t3 push [--repo p] [--remote r] [--branch b] [--force-with-lease]  # the supported push path from the worker container: resolves the forge token (GH_TOKEN → TEATREE_GH_TOKEN → the overlay's pass store) and hands it to git as env only, disables every interactive credential prompt so a missing token fails fast instead of hanging, and refuses a remote whose URL embeds a credential. Never passes --no-verify — the pre-push hooks still run
t3 fast-push [-m msg] [--remaining txt]  # leak-gated escape hatch for session hand-offs: stage → in-process leak gates (banned-terms, secret-scan, overlay-leak, public-repo author-identity; fail-closed) → commit → push → create-or-update the PR; skips every non-leak gate; any finding refuses the push
t3 mutation run                 # scoped mutation testing — mutate only the high-value safety modules a diff touches
t3 hook run <name> [args...]    # run a packaged portable repo-quality gate by name (module-health, no-silent-skip, broad-except, test-shape, test-path-mirror, refuse-main-clone-commit) — no teatree.__file__ shim; `t3 hook list` names them. A consuming repo wires them via `entry: t3 hook run <name>` or pins `repo: <teatree-url>` against the root .pre-commit-hooks.yaml
t3 ui                           # browse and run the whole command tree in a terminal UI (needs `uv sync --group ui`)
t3 admin                        # run the Django admin for the teatree project under a local gunicorn server (WSGI, not runserver)
t3 mcp serve                    # serve teatree's structured search (tickets, worktrees, tasks, loop stats, incoming events) + gate-preserving writes as an MCP server over stdio
                                 # registered automatically via the plugin-bundled .mcp.json (surfaces as mcp__teatree__* tools) — `t3 setup`/`t3 doctor check` verify it
t3 notion whoami|doctor         # headless Notion access via an integration token (no interactive connector, so a scheduled run reaches a page at all): verify the token / triage one page (token valid, page shared, page still LIVE)
t3 notion fetch <page>          # read a page as Markdown (or raw blocks), optionally with its open comments; refuses an ARCHIVED page with its own exit code and names the successor, because a dead page renders exactly like a current one
t3 notion audit-fetch <page>    # read a DEAD page for a postmortem — deliberately its own command so it is not reachable by habit
t3 notion comments|append|query # list open comments; append at the end of a page; query a database/data source as JSON
t3 notion section show|replace  # the owned-section write primitive — block-scoped, so replacing one heading's body leaves every discussion on the rest of the page intact (there is deliberately no whole-page replace)
t3 notion comment post          # marker-keyed, so a caller that forgets the flag under-posts rather than double-posts
t3 notion property get|set      # read/write one page property — the poll a block-tree fetch cannot answer; every write re-reads and refuses to report success unless the change landed
t3 dream run [--since <iso>] [--dry-run]  # run one memory-consolidation pass NOW (ignores cadence)
t3 dream tick                   # cadence-gated pass (~04:00 slot); the worker's off-live-tick driver chain fires it, decoupled from the live loop
t3 outer status|history         # T4 autoresearch outer loop — guard-chain verdict + experiment ledger (read-only)
t3 outer tick                   # cadence-gated step the worker's off-live-tick driver chain fires (propose→ratify→measure→keep-only-if-better; ships quadruple-OFF)
t3 notion whoami|fetch|audit-fetch  # read a Notion work item headlessly through the overlay's routed token — `whoami` proves the token resolves, `fetch` pulls a page (optionally one section), `audit-fetch` records what was read so a review can show its retrieval
t3 directive capture "<text>" [--scope <overlay>]   # record a plain-language directive about teatree's own behaviour (verbatim, CAPTURED)
t3 directive list|status <id>|history               # inspect the directive ledger, one directive's sketch/state, decisions (read-only)
t3 directive tick               # cadence-gated step the worker's off-live-tick driver chain fires (implement→configure→verify→keep-or-revert; ships triple-OFF)
t3 directive resolve-revert <id> [--revert-sha <sha>]  # close a REVERT_PENDING directive to terminal REVERTED (config already rolled back)

Replace teatree with your overlay's name (t3 <overlay>) when working in another overlay.

t3 ui is a trogon-backed browser for the full t3 command tree (core plus every installed overlay). It is in the optional ui dependency group — install it with uv sync --group ui before the first run.

t3 admin runs the Django admin for the teatree project under a local gunicorn server (teatree.wsgi:application, a production WSGI server — not Django's dev runserver; http://127.0.0.1:8000/admin/ by default). It applies migrations, collects static into STATIC_ROOT (so WhiteNoise serves the admin and dashboard assets with DEBUG off), ensures a superuser exists — creating one non-interactively when absent and printing its generated password (override via T3_ADMIN_USER / T3_ADMIN_PASSWORD) — and opens the browser at /admin/ (--no-browser to skip; --host / --port to override). The admin binds to the same teatree database every other t3 command reads, so no overlay context is needed.

2. Loop & Statusline

The singleton t3 worker drives the day (#1796 / PR-28, default ON): it drains one self-rescheduling loop_timer chain per enabled DB Loop row, each firing t3 loops tick --loop <name> on its own cadence, so the loops run with no Claude Code session open. Those ticks fan out to scanners that watch assigned issues, open PRs, PRs assigned for review, Slack mentions, the Notion → GitLab bridge, and the local task queue. Findings render to ${XDG_DATA_HOME:-~/.local/share}/teatree/statusline.txt (three zones: anchors / action needed / in flight). The Claude Code statusline hook cats that file in <10ms, so live status sits at the top of every session without polling.

# Run the worker (the cadence owner). Bare `t3 worker` is the run alias:
t3 worker

# Check the worker: live flock holder, resolved loop_runner_enabled + source, timer counts:
t3 worker status            # --json for a machine-readable payload
# Ensure one is running (spawns a detached worker iff enabled AND the flock is free):
t3 worker ensure            # refuses (with the reason) when OFF or already running

# Spawn a Claude Code session (registers the reactive infra loops: self-improve/slack-answer/drain-queue):
t3 loop start

# Enable/disable an individual loop (the reconciler adds/prunes its timer at once):
t3 loop enable <name>
t3 loop disable <name>

# Out of band, run one by-hand full-scan tick or read the last-rendered statusline:
t3 loop tick
t3 loop status

# List the DB-configured autonomous loops (name, enabled, delay, last run, next due):
t3 loops list

The cadence is configurable via T3_LOOP_CADENCE (seconds), or by setting loop_cadence_seconds in the teatree DB (t3 <overlay> config_setting set loop_cadence_seconds 720; env wins; default 720). loop_runner_enabled is the kill-switch — set it false to stop the loops entirely (there is no fallback plane; PR-28 retired the native /loop cron mirror). On a headless box with no Claude session ever opening, start t3 worker once from a login profile.

Wire up the Claude Code statusline hook so the rendered file actually shows in the bottom bar. This is a top-level statusLine key in ~/.claude/settings.json — enabling the t3 plugin does not wire it for you: a plugin's settings.json only honours the agent and subagentStatusLine keys, so a statusLine declared there is silently ignored. Point the command at an absolute path to the script (the user-level settings file does not expand ${CLAUDE_PLUGIN_ROOT}):

{
  "statusLine": {
    "type": "command",
    "command": "bash /absolute/path/to/teatree/hooks/scripts/statusline.sh"
  }
}

3. Claude Plugin

Skills and hooks that drive AI-assisted development. Each skill covers one phase of the development lifecycle — ticket intake, coding, testing, review, shipping — and contains the methodology, guardrails, and domain knowledge the agent needs to do the work well: TDD discipline, debugging process, review checklists, retro learning, verification rules. Skills declare dependencies (requires:, transitive) — including methodology skills from third-party packages like superpowers. Hooks handle automatic skill routing, branch protection, and session tracking.

Skills use the CLI for infrastructure (worktrees, databases, ports, CI), but the actual development work — writing code, reasoning about architecture, reviewing diffs, running retros — is guided by skill content, not CLI commands.

Workflow guarantees

A few rules in the lifecycle skills are non-negotiable. They exist because each one prevents a specific class of failure that has bitten a real session:

  • PRs go through t3 <overlay> pr create. Raw gh pr create / glab mr create skips the shipping gate (testing + reviewing phases), the visual-QA gate, and the title/description validator. The CLI is the only path that runs every guard; using it is mandatory whenever the overlay exposes the subcommand.
  • The reviewing phase is satisfied by an independent sub-agent, not by self-review. Before push, the implementing conversation spawns the t3:reviewer sub-agent (read-only, no edits) and applies its findings. Self-review against repo rules is a complement, not a substitute — the implementer's context carries the same blind spots that allowed the gap.
  • State machine changes happen via transitions, never via direct field writes. This holds for both code and CLI: every command that affects a state machine must call into a transition (Ticket.code(), Ticket.review(), Ticket.ship(), etc.) so the predicates run and the dependent gates stay aligned.
  • Mass renames and cross-cutting refactors require an exhaustive sweep before "done". A single rg pass is not enough — the agent runs every surface form (plain, quoted, attribute access, subscript, CamelCase variants, sibling repos) and confirms zero hits before claiming the rename is complete.
  • A PUBLIC-repo PR never auto-merges unless its author is trusted. On a public repo anyone who is not the user is a potential malicious actor, so the merge keystone refuses to auto-merge a PR whose author is not one of the user's known identities (fail-closed: an unknown, empty, or unfetchable author is refused; an unresolvable repo visibility is treated as public). Private/internal repos skip the check entirely — the user owns access control there. The trusted set lives in the DB; manage it with t3 identities {seed,add,list,remove} (the configured user_identity_aliases is the fallback during the config-to-DB migration window). The same trust classifier flags an untrusted public-repo PR as adversarial across the reviewing scanners, so a malicious PR is never treated like a colleague's.

These rules live in the ship, review, code, and rules skills. The CLI enforces what it can mechanically (gate checks, transition predicates); the skills carry the rest.

Get Started

Prerequisites: Python 3.13+, uv.

For users

Teatree is not on PyPI. Install the t3 CLI straight from the repo:

uv tool install --from git+https://github.com/souliane/teatree.git teatree \
  --overrides https://raw.githubusercontent.com/souliane/teatree/main/uv-overrides.txt   # installs `t3` globally
apm install -g souliane/teatree   # installs skills + companion dependencies
t3 setup                          # links plugin, syncs skills, migrates self-DB
t3 startoverlay my-overlay ~/workspace/my-overlay

uv tool install puts t3 in ~/.local/bin/. If that directory is not on your PATH, add export PATH="$HOME/.local/bin:$PATH" to your shell rc.

--overrides is required, not optional: claude-agent-sdk declares an mcp bound broader than the surface it imports, and uv tool install does not read the [tool.uv] override-dependencies that corrects it — so without the flag the install fails with an unsatisfiable-requirements error. See uv-overrides.txt.

Installing the plugin does not force teatree on. By default a fresh Claude session does not auto-engage teatree — no skill auto-suggest, no load-block, no loop scheduling — and just shows a one-line how-to. Run /teatree (or load any t3: skill) to engage teatree for that session, or set autoload in the teatree DB (t3 <overlay> config_setting set autoload true; env T3_AUTOLOAD=1) to auto-engage every session.

For contributors

Fork the repo, then:

git clone git@github.com:YOUR_USERNAME/teatree.git ~/workspace/teatree
cd ~/workspace/teatree
uv tool install --editable . --overrides uv-overrides.txt   # global `t3`, live-reloaded from this clone
t3 setup                       # installs skills globally, respects local symlinks

New here? docs/MAP.md lists every package directory with a one-line purpose and links to the relevant BLUEPRINT.md section — read it first to find where something lives.

uv tool install --editable . --overrides uv-overrides.txt produces the same global ~/.local/bin/t3 as the user flow — edits in this clone take effect on the next invocation, no uv run prefix. t3 setup runs APM to install companion dependencies (superpowers, ac-django, etc.), symlinks teatree skills to ~/.claude/skills/, registers the Claude plugin in ~/.claude/plugins/installed_plugins.json with installPath pointing at the clone so hooks and agents always read from the live checkout, applies any pending self-DB migrations, and — if t3 is not on PATH — re-runs uv tool install --editable . (with the overrides file) to self-install. Must be run from the main clone, not a worktree.

t3 setup also self-heals when teatree adds a new dep: editable installs do not auto-resync their venv when pyproject.toml changes, so on every run t3 setup compares the declared [project].dependencies against the dists in the running interpreter and re-runs uv tool install --editable . --reinstall (with the overrides file) automatically when anything is missing. After the reinstall, setup re-execs itself against the refreshed venv. No manual --reinstall step is needed when pulling teatree updates.

Skills

Each skill teaches the agent one phase of development:

graph LR
  ticket["ticket<br/>(intake)"] --> code["code<br/>(implement)"]
  code --> test["test<br/>(verify)"]
  test --> review["review<br/>(inspect)"]
  review --> ship["ship<br/>(deliver)"]
  retro["retro<br/>(orchestrator-level)"] -.-> ticket

  ship --> rr["review-request<br/>(notify)"]
  debug["debug<br/>(troubleshoot)"] -.-> code
  debug -.-> test
  followup["followup<br/>(batch)"] -.-> ticket
  workspace["workspace<br/>(provision)"] -..-> code & test & review & ship
Loading
Skill Phase
ac-reviewing-codebase Periodic holistic architectural review — the third of teatree's three review tiers (design-time architecture-design, per-PR deterministic check_antipatterns.py, periodic holistic ac-reviewing-codebase). Walks the whole tree for judgement-tier anti-patterns and BLUEPRINT.md staleness that no single diff can catch, implements what it finds, and pushes one PR. Dispatched automatically by ArchitecturalReviewScanner on a time or merge-count cadence — not user-invoked.
answerer Draft a reply to an inbound question, DM the user for approval, post on confirmation
architecture-design Architecture pre-check companion. Loaded transitively by implementation skills (code, ticket-for-features, retro-for-skill-changes) to force an architecture pass — BLUEPRINT alignment, FSM phase boundaries, extension-point contracts, component boundaries, dependency direction, test surface, resilience invariants, removability — BEFORE any code is written.
checking The check-in surface — a SHORT "what did I miss" report, the session task/TODO lists, the pending deferred questions, and the daily follow-up routine (new tickets, ticket statuses, PR reminders)
code Writing code with TDD methodology
contribute Push retro improvements to a branch, open a PR, and optionally create upstream issues
debug Troubleshooting and fixing — something is broken, find and fix it
directive Submit a plain-English directive about how teatree itself should behave — captured verbatim, interpreted into a typed mechanism sketch, human-ratified via Slack/questions, then implemented through the gated pipeline
dogfooding Dogfooding teatree's own CLI, loop, and statusline — two modes sharing one mechanics section for reading a tick and the rendered statusline. "Verify a change" is the run-it-yourself checklist applied after modifying CLI/loop/statusline code, before declaring it done. "Hunt for bugs" is proactive self-QA — dogfood the deployed loop, find/dedupe/confirm real bugs, file them, then fix them in worktrees
dreaming Runs the idle-time "dreaming" memory-consolidation pipeline end to end with one command — replay recent transcripts + curated memories, distil drift into the ConsolidatedMemory ledger, cross-link / re-index / decay the memory files, run the §4 acceptance gates, triage each row into keep-as-memory vs core-gap → drive each core gap to a MERGED fix under the standing umbrella issue, and promote/stage eval candidates
e2e End-to-end testing with Playwright — writing tests, running them, visual snapshots, test-plan posting, and the pre-push visual QA gate
e2e-review Reviewer-side quality gate for Playwright end-to-end specs. Load when reviewing a new or changed E2E test, deciding whether a spec is ready to land, or adopting an outside Playwright suite. Judges specs against Playwright's published best practices — user-visible behaviour over implementation, resilient role/label/test-id locators, web-first auto-retrying assertions instead of hard waits, per-test isolation, page-object structure, and runnable evidence — and tells the implementer what to fix before approval.
handover Use when the user wants to hand all current work from one Claude session to another (or to a not-yet-existing session) with a single command, or to transfer an in-flight TeaTree task from Claude to another runtime, or asks whether it is time to switch because Claude usage is getting high.
health Read and act on the global operational-health chip — the green/yellow/red factory-health verdict and its known-issues registry
interactive ENGAGES TEATREE FOR THE SESSION, and holds the standing rule that no work-bearing state is terminal. Loading this skill — or any skill declaring requires: interactive — writes the .teatree-active marker, one of the two conditions in _loop_auto_load_active() that arm the loop and statusline (#256); a session that never loads it stays unengaged, by design. Also holds teatree's Claude Code harness wiring: how skills are selected, how plugin hooks are registered, and which output belongs to the headless pipeline. Load it when ending an interactive session, when a session-end report names stranded work, or when deciding what to do with uncommitted, unpushed, untracked or unmerged work. Teatree's own architecture and coding rules are /t3:internals; the dogfooding procedure is /t3:dogfooding.
internals How teatree is BUILT and how to change it safely — architecture, lifecycle phases, key models, the overlay API, the t3 CLI reference, and the management-command rules whose violation fails SILENTLY (a typer.Exit under call_command exits 0, so CI reports green on a real failure). Load it when writing or reviewing teatree's own code, or when building an overlay on it. Carries no Claude Code harness wiring — that is /t3:interactive — and no dogfooding procedure — that is /t3:dogfooding.
mode The operating mode — one named posture (reachable / unattended / holiday) that decides whether AskUserQuestion asks the user now or captures a durable DeferredQuestion row, and which loops run
next Wrap up the current session — retro, structured result, pipeline handoff.
platforms Platform-specific API recipes for GitLab, GitHub, Slack, and X (Twitter). Auto-loaded as a dependency by skills that interact with these platforms.
prompts Trigger and manage reusable prompts — list the prompts in the DB, render one by name with its templated params, and point to the admin for authoring + version history
retro Conversation retrospective and skill improvement
review Code review — self-review before finalization, giving review, receiving review feedback
review-request Batch review requests — discover open PRs, validate metadata, check for duplicates, post to review channels
rules Cross-cutting agent safety rules — clickable refs, temp files, sub-agent limits, UX preservation. Auto-loaded as a dependency by other skills.
running-evals Single in-session entrypoint that auto-orchestrates the whole eval picture — model-free deterministic lanes (the eval-coverage gate t3 eval coverage, pinned-regressions) plus the transcript AI/trajectory lane (prepare → produce transcripts in-session → grade) — and prints one unified results table
scanning-news Scans today's TLDR AI and The Rundown AI editions for ideas that could improve teatree, fetches the full article for promising items, and hands each concrete t3-improvement candidate back through the result envelope's article_suggestions field. The loop queues each behind the ask-gate (PendingArticleSuggestion) for per-article user approval before any souliane/teatree issue is filed, and DMs the batch to the user
setup Bootstrap and validate teatree for local use — prerequisites, config, skill symlinks, optional agent hooks, and Django project scaffolding
ship Delivery — committing, pushing, creating MR/PR, pipeline monitoring, review requests
slack-formatting Rendering tables and formatting messages for Slack — the native Block Kit table block, the monospace fence fallback, and the mrkdwn gotchas (no pipe tables, single-asterisk bold, angle-bracket links). Auto-loaded as an overlay companion for work that posts to Slack.
sweeping-prs Maintenance sweep across all your open PRs/PRs — merge the default branch, fix conflicts, monitor CI, push, and (per-repo policy) optionally squash-merge each PR before moving to the next. Never rebases
sweeping-tickets Evidence-gated ticket/issue consolidation and triage — classify every open issue against current main, then consolidate by merging related tickets INTO AN EXISTING ticket — never by minting a new umbrella row and never by discarding ideas — and close only what is demonstrably shipped or now folded into its host. Always asks the operator for the maximum number of tickets to keep before triaging — never assumes a number. Dry-run first; close only on user approval (or auto-close ONLY the high-confidence "shipped by merged PR #X" class), posting a one-line reason on every close
sweeping-worktrees Use when sweeping stale, lost, or abandoned worktrees, branches, or stashes that are NOT actively being worked — deciding per item whether to salvage unmerged work to a fresh PR, delete a shipped/superseded/redundant item, push post-merge commits to a new PR, or keep an uncertain one. The judgment layer over t3 <overlay> workspace emit / salvage / clean-all (the mechanical reaper is /t3:workspace)
test Testing, QA, and CI — running tests, analyzing failures, quality checks, CI interaction, test plans, and posting testing evidence
ticket Ticket intake and kickoff — from zero to ready-to-code
triaging-issues Review and act on the needs-triage assessor's queued recommendations — list PENDING PendingTriageRecommendation rows, approve or reject each, and on approval run gh issue close/edit/comment then stamp the row
update WHEN to bring teatree core and registered overlays up to date with their default branch, and the safety guarantees of doing so
wip The bounded-WIP throughput dial — slow / medium / full / boost — plus the WRITE-parallel / MERGE-serial phase split and the per-ticket unattended delivery cycle. boost keeps boost_concurrency = N workers live; full arms a self-sustaining boost loop; medium (baseline) and slow cap concurrency
workspace Environment and workspace lifecycle — worktree creation, setup, DB provisioning, dev servers, cleanup

Extended SKILL.md frontmatter

Teatree adds a small schema on top of Claude Code's standard SKILL.md frontmatter so a skill can declare what it needs loaded alongside it:

---
name: ship
requires: [rules, platforms, verification-before-completion]
---
  • requires — the single skill-dependency edge, resolved transitively in topological order with cycle detection. A required skill with no SKILL.md in this repo (an external methodology skill from obra/superpowers, installed via APM, never modified by teatree) passes through so the Skill tool still loads it.

Skill loading is fully explicit — slash commands (/t3:ship), phase mapping (t3 agent --phase shipping), ticket status, the requires chain, and cwd/overlay context. There is no free-text scan of the prompt: the UserPromptSubmit hook surfaces only the framework / overlay / companion skills a prompt's cwd context implies, and PreToolUse blocks Python code edits until those load.

See BLUEPRINT.md § 11.5 for the explicit-loading model and docs/claude-code-internals.md for how the hooks wire into Claude Code.

Overlays

Teatree's core is generic — it does not know about specific repos, CI, or environment defaults. Project-specific behaviour lives in a lightweight overlay package that subclasses OverlayBase and registers via the teatree.overlays entry point. The overlay carries the project's repos, provisioning steps, runtime metadata, and service hooks; the core stays the same across projects.

Create one with:

t3 startoverlay my-overlay ~/workspace/my-overlay

The overlay registers via a teatree.overlays entry point:

[project.entry-points."teatree.overlays"]
my-overlay = "myapp.overlay:MyOverlay"

Once installed (pip install -e .), the overlay is auto-discovered at startup. The overlay implements the narrow contract teatree needs: managed repos, provisioning steps, runtime metadata, and project-specific service hooks. See docs/overlay-api.md for the full API.

Overlays can live anywhere; they do not need to be vendored into this repo. The author dogfoods this on a private client-codebase overlay; the same extension point is what any other consumer would use.

Configuration

Teatree stores its config in the teatree DB — the ConfigSetting store, set with t3 <overlay> config_setting set <key> <json> (add --overlay <name> to scope a value to one overlay, omit it for the global default). Every key is optional; the table below lists the ones most users touch. The full set and their defaults live in UserSettings in src/teatree/config/settings.py. Overlays register via teatree.overlays entry points plus the DB overlays registry row.

t3 <overlay> config_setting set mode interactive                       # "auto" (default) | "interactive"
t3 <overlay> config_setting set privacy '""'                           # privacy-scan profile name
t3 <overlay> config_setting set contribute false                       # enable skill self-improvement
t3 <overlay> config_setting set excluded_skills '["my-custom-skill"]'  # extra skills to exclude
t3 <overlay> config_setting set loop_cadence_seconds 720               # loop tick interval (default 12 min)
t3 <overlay> config_setting set require_human_approval_to_merge true   # auto mode: still gate merge on a 👍 / /merge
t3 <overlay> config_setting set require_human_approval_to_answer true  # gate t3:answerer behind a DM confirmation
t3 <overlay> config_setting set agent_signature false                  # append an AI signature to posts (default off)
Key Default Effect
workspace_dir ~/workspace Root for per-ticket workspace directories
mode auto auto is end-to-end; interactive confirms before publishing
privacy "" Named privacy-scan profile applied before pushes
contribute false Allow t3:retro to write fixes into core skills
excluded_skills [] Skills excluded on top of the built-in exclusions
loop_cadence_seconds 720 Default cadence (seconds) for a loop's ticks
require_human_approval_to_merge true In auto mode, merge still needs a 👍 / /merge
require_human_approval_to_answer true, collapsed to false by the shipped autonomy = full t3:answerer drafts a reply and DMs for approval. The answer's own post is separately gated by on_behalf_post_mode, which no tier collapses
on_behalf_post_mode draft_or_ask Pre-gate on any post made under your identity to a colleague surface. Read unchanged by every autonomy tier — opening it is its own explicit immediate
agent_signature false Whether posts made on your behalf carry an AI signature

The t3:contribute skill's push gate is the T3_PUSH environment variable (default false), not a TOML key — it exists as a deliberate stop for privacy review before any skill improvement leaves the machine.

Run t3 setup after changing config to apply changes to skill symlinks and caches.

Operating mode

mode (a DB-home setting, or the T3_MODE env var) controls how much autonomy the agent has for publishing actions:

  • interactive (conservative on security) — the agent pauses for explicit approval before push, MR create, MR merge, Slack posts, or any other write that leaves the local machine.
  • auto (shipped default) — end-to-end autonomy. The agent ships complete features without confirm prompts: push → MR create → pipeline watch → merge → clean up remote branches. Quality gates (lint, tests, migrations check) still run; they just do not depend on user confirmation. A small always-gated list remains regardless of mode: force-push to default branches, history rewrites on shared defaults, destructive shared-DB operations, and external writes the active overlay has not authorised.

Unknown values raise an error — a typo in mode will never silently downgrade to a less-safe mode.

mode lives in the teatree DB ConfigSetting store — there is no TOML key for it (a [teatree] mode / [overlays.<name>] mode value is ignored on read and warned about). Set it globally or per-overlay, so you can run auto on a personal dogfooding overlay while keeping interactive on a client project:

t3 <overlay> config_setting set mode interactive                 # global default
t3 <overlay> config_setting set mode auto --overlay my-project   # per-overlay override

The resolution chain is, first match wins: T3_MODE env var → the active overlay's per-overlay DB row → the global DB row → the shipped default (auto). An autonomous autonomy tier also pins mode = auto unless a per-overlay or env mode says otherwise. mode is one of the per-overlay-overridable keys; the full registry is OVERLAY_OVERRIDABLE_SETTINGS in src/teatree/config/settings.py. See BLUEPRINT.md § 10.1.1 for the full details.

Contributing & Self-Improvement

After every non-trivial session, the retro skill runs a retrospective, extracts what went wrong, and writes fixes back into skill files. When contributors enable this (t3 <overlay> config_setting set contribute true), improvements flow back upstream through a fork-based model.

Where improvements go:

  • contribute = false (default): improvements go to the project overlay only
  • contribute = true: the agent also improves core skills, pushes to a branch, opens a PR

Nothing is ever pushed without explicit consent. The contribute skill shows exactly what will be pushed, runs privacy scans, and checks fork divergence before creating PRs.

# Run tests locally — the diff-scoped lane is the default; CI's sharded lane is the authority
bash dev/test-affected.sh   # only the tests the diff affects (`--full` for the whole suite)
bash dev/test-cov.sh        # coverage lane: --cov --doctest-modules, 93% floor (CI parity)

# Pre-commit checks
prek run --all-files         # ruff, codespell, banned-terms

E2E Tests

E2E tests run via t3 <overlay> e2e run, which dispatches to an in-repo pytest-playwright runner or an external playwright repo based on the overlay's overlay.metadata.get_e2e_config(). Overlays declare "runner": "project" or "runner": "external"; the runner is overlay-agnostic from the call site:

t3 <overlay> e2e run                          # CI default
t3 <overlay> e2e run --no-docker              # run against the local stack
t3 <overlay> e2e run --update-snapshots       # accept new snapshots

Teatree itself ships no in-repo E2E suite — the top-level e2e/ directory holds only the /t3:e2e-skill conventions doc. Each overlay owns its own specs, runner configuration, and failure-triage artifacts (Playwright videos, traces, server logs); where those artifacts land and how CI attaches them is the overlay runner's concern, driven by its own pytest-playwright / playwright config.

Security Considerations

Skills are prompt instructions — they control what your AI agent does. This makes the supply chain a security surface.

Safe defaults: self-improvement is off, pushing is disabled, and there is no auto-update mechanism. All pushes go to branches (never main) and require a PR. APM dependencies are pinned to specific commit SHAs in apm.yml.

Supply chain: t3 setup verifies that skills are loaded via symlinks to the local clone — not stale copies. If you use a fork from someone else, you are trusting that person's skill files as agent instructions. Review changes before pulling.

Leak backstop: the banned-terms gate scans diffs, commit messages, and publish-surface bodies — but a customer/tenant brand name already committed never appears in a later diff, so it would stay hidden. t3 banned-terms scan-tree is the full-tree backstop: it walks every git-tracked file (git ls-files) and scans its content for the high-confidence brand list, exiting non-zero with the offending file:line list. Its matcher is underscore-tolerant — wt_777_<brand> and <brand>_x are caught where the diff gate's word-boundary matcher misses them — while common-word entries keep strict boundaries (no substring noise) and the email carve-out is preserved. The brand list comes from the banned_brands setting in the teatree DB or the $TEATREE_BANNED_BRANDS environment variable; it is a curated high-confidence subset (brand-only — common words stay in banned_terms so the underscore-tolerant tree scan never substring-matches them). The public repo ships with none, so the brand scan is a no-op — but it reports a loud brand backstop INERT: banned_brands is unpopulated warning rather than a silent clean line, so an operator who expected the backstop to be active can tell it is not yet populated. A CI job runs the scan on push to main and on a daily schedule.

Project Structure

teatree/
  src/teatree/         # Django project (installed as `teatree`)
    cli/               #   Typer CLI package — bootstrap commands
    core/              #   Models, FSM transitions, management commands
    agents/            #   Agent runtime adapters (Claude Code, Codex)
    backends/          #   Code-host (GitHub, GitLab) + messaging (Slack, Notion) Protocols
    loop/              #   Fat /loop tick — scanners, dispatch, statusline render
    utils/             #   Internal helpers (ports, git, DB)
    templates/overlay/ #   `t3 startoverlay` scaffolding
  skills/              # AI agent skills (SKILL.md + references)
  hooks/               # Agent platform hooks (routing, guards, statusline)
  scripts/             # Pre-commit hooks, utility scripts
  tests/               # Unit tests (>90% branch coverage)
  docs/                # MkDocs documentation site

Where it is headed

Teatree stays a single-author tool for now. The plan is to keep dogfooding it on real client work, let the rough edges surface through daily use, and only broaden adoption once the patterns it relies on have been pushed through enough sessions to be trustworthy. The public repo is a side effect of that workflow — the code lives somewhere reachable in case any of the patterns help someone else, not as a finished product looking for users.

Why "teatree"?

TEA's Extensible Architecture for worktree management.

License

MIT

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages