<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Adrien Cossa</title>
    <description>The latest articles on Forem by Adrien Cossa (@souliane).</description>
    <link>https://forem.com/souliane</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3816852%2Fe449a0fc-14fe-4d60-994a-ffa47063be46.jpg</url>
      <title>Forem: Adrien Cossa</title>
      <link>https://forem.com/souliane</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/souliane"/>
    <language>en</language>
    <item>
      <title>Installing OpenClaw the Easy Way</title>
      <dc:creator>Adrien Cossa</dc:creator>
      <pubDate>Mon, 16 Mar 2026 16:34:05 +0000</pubDate>
      <link>https://forem.com/souliane/installing-openclaw-the-easy-way-5733</link>
      <guid>https://forem.com/souliane/installing-openclaw-the-easy-way-5733</guid>
      <description>&lt;p&gt;I started installing &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; manually — reading through the docs, figuring out each step, hitting the usual walls. It was taking too long, so I stopped and decided to try a different approach. So instead of repeating the process, I wrote a &lt;a href="https://github.com/souliane/skills/tree/main/ac-openclaw" rel="noopener noreferrer"&gt;skill&lt;/a&gt; for it: a set of instructions and references that lets an AI coding agent handle the whole thing. This is the &lt;a href="https://dev.to/souliane/skill-driven-development-transferring-your-craft-to-ai-agents"&gt;skill-driven development&lt;/a&gt; approach I described in a previous post — let the agent do the work, fix the skill when it gets something wrong, repeat until it gets it right.&lt;/p&gt;

&lt;p&gt;The result is a skill that handles the full installation and configuration of OpenClaw on a VPS. You tell your agent "set up OpenClaw on my server" and it walks through everything: provisioning, hardening, messaging channels, backups. The skill encodes the gotchas so you don't have to hit them yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;With the skill loaded, setting up OpenClaw goes roughly like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Set up OpenClaw on my new Hetzner server"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent asks a few questions — SSH access details, which messaging channels you want, which model providers you have API keys for — then works through the phases in order. It handles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Server provisioning&lt;/strong&gt; — SSH setup, initial access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS hardening&lt;/strong&gt; — UFW firewall, Fail2Ban, SSH lockdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk encryption&lt;/strong&gt; — LUKS full-disk or encrypted block storage volumes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime installation&lt;/strong&gt; — Node.js, Python, build tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenClaw installation&lt;/strong&gt; — clone, configure, systemd service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model configuration&lt;/strong&gt; — API keys (Anthropic, OpenAI, etc.) or local Ollama&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messaging channels&lt;/strong&gt; — Signal, Telegram, WhatsApp, Discord, and others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-agent routing&lt;/strong&gt; — different agents for different contacts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote access&lt;/strong&gt; — Cloudflare Tunnel, Tailscale, or Caddy reverse proxy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker sandboxing&lt;/strong&gt; — container isolation with proper firewall rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups&lt;/strong&gt; — local snapshots, GitHub push, cloud provider images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Social media integration&lt;/strong&gt; — optional, third-party schedulers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ongoing maintenance&lt;/strong&gt; — updates, log rotation, health checks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At each step, the agent adapts to your choices. Pick Telegram instead of Signal? The Signal-specific build steps get skipped. Want Tailscale instead of Cloudflare Tunnel? Different config, different verification. The skill describes the trade-offs; the agent presents them and moves on.&lt;/p&gt;

&lt;p&gt;Without the skill, an agent can still attempt all of this — the information exists in OpenClaw's docs and platform-specific guides. But it takes hours of trial and error, and several of the issues below are genuinely hard to figure out from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Issues the skill handles for you
&lt;/h2&gt;

&lt;p&gt;These are the problems that came up during the SDD loop — things the agent got wrong, that I fixed in the skill, and that you won't have to deal with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker bypasses UFW
&lt;/h3&gt;

&lt;p&gt;This one is not well-known. When Docker publishes a port, it writes its own iptables rules that bypass UFW entirely. Your firewall says "deny all incoming," but Docker's containers are wide open anyway.&lt;/p&gt;

&lt;p&gt;The skill includes explicit &lt;code&gt;DOCKER-USER&lt;/code&gt; chain rules that block all inbound connections to containers unless they come from loopback (&lt;code&gt;127.0.0.1&lt;/code&gt;) or your Tailscale CGNAT range (&lt;code&gt;100.64.0.0/10&lt;/code&gt;). Without this, any container you run is exposed to the internet regardless of your UFW config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signal on ARM64 has no pre-built binary
&lt;/h3&gt;

&lt;p&gt;If you're running on an ARM64 VPS (Hetzner CAX series, for example), signal-cli's libsignal JNI binding doesn't have a pre-built binary. You have to clone the signal-cli repo and build libsignal from Rust source yourself. This requires &lt;code&gt;openjdk-25-jre-headless&lt;/code&gt;, &lt;code&gt;build-essential&lt;/code&gt;, &lt;code&gt;cmake&lt;/code&gt;, &lt;code&gt;libclang-dev&lt;/code&gt;, &lt;code&gt;protobuf-compiler&lt;/code&gt;, and &lt;code&gt;rustc&lt;/code&gt; — a dependency chain that takes a while to sort out.&lt;/p&gt;

&lt;p&gt;The skill documents the exact build steps and dependencies. Without it, the agent installs signal-cli, it silently fails at runtime, and you spend a while figuring out why.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secrets end up in config files
&lt;/h3&gt;

&lt;p&gt;Left to its own devices, the agent will store API keys in environment files or config files because it's the fastest path. The skill enforces storing all secrets in &lt;code&gt;pass&lt;/code&gt; (the standard Unix password manager) and reading them at runtime. No API keys in plain text, ever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Services "started" but not actually running
&lt;/h3&gt;

&lt;p&gt;A common agent failure: it runs &lt;code&gt;systemctl start openclaw&lt;/code&gt;, gets no error, and declares the phase complete. But the service might have crashed immediately after starting, or it might be listening on the wrong port, or a dependency might be missing.&lt;/p&gt;

&lt;p&gt;The skill marks verification as &lt;code&gt;(Non-Negotiable)&lt;/code&gt; — the agent must confirm each service actually responds via HTTP before moving on. This single rule prevented most of the false-completion issues during the SDD loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disk encryption on a cloud VPS
&lt;/h3&gt;

&lt;p&gt;LUKS full-disk encryption on a cloud VPS isn't straightforward. You need to boot into rescue mode, set up &lt;code&gt;cryptsetup&lt;/code&gt;, and install &lt;code&gt;dropbear-initramfs&lt;/code&gt; so you can unlock the disk remotely after every reboot. The skill documents this path but also offers a simpler alternative: Hetzner encrypted block storage volumes for sensitive data only, which avoids the rescue-boot complexity for most use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remote access done wrong
&lt;/h3&gt;

&lt;p&gt;Exposing ports directly to the internet is the default instinct, but it's the wrong one. The skill requires one of three approaches: Cloudflare Tunnel (outbound-only, Zero Trust), Tailscale Serve (private mesh), or Caddy reverse proxy with password auth. Each has documented trade-offs. The agent asks which one you prefer and configures it accordingly.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the skill is structured
&lt;/h2&gt;

&lt;p&gt;The main &lt;code&gt;SKILL.md&lt;/code&gt; file is around 160 lines and covers the decision flow and ordering constraints. Detailed procedures live in reference files that the agent pulls in as needed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reference&lt;/th&gt;
&lt;th&gt;What it covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security hardening&lt;/td&gt;
&lt;td&gt;SSH config, UFW rules, Fail2Ban jails, Docker iptables bypass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local models&lt;/td&gt;
&lt;td&gt;Ollama installation, model selection by RAM, GPU passthrough&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Messaging channels&lt;/td&gt;
&lt;td&gt;Per-channel setup (Signal ARM64 build, WhatsApp QR pairing, Telegram BotFather)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-agent routing&lt;/td&gt;
&lt;td&gt;Contact-to-agent bindings, DM access policies, agent personalities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote access&lt;/td&gt;
&lt;td&gt;Cloudflare Tunnel vs Tailscale vs Caddy — trade-offs and setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Social media&lt;/td&gt;
&lt;td&gt;Third-party schedulers, risk considerations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backups&lt;/td&gt;
&lt;td&gt;Local snapshots, GitHub push with deploy key, cloud provider images&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This progressive disclosure keeps context usage reasonable. The full Signal ARM64 build instructions only get loaded if you're actually setting up Signal on ARM64.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;The skill isn't tied to a specific setup. It asks what you're working with — VPS or local machine, which OS, which architecture, which provider — and adapts accordingly. It can fetch current VPS pricing (Hetzner CAX series, DigitalOcean, etc.) so you can compare options before committing. If you want to run OpenClaw on a spare laptop instead of a cloud server, it adjusts the flow — no VPS provisioning, different networking, local Ollama instead of cloud API keys.&lt;/p&gt;

&lt;p&gt;That said, my own setup (Hetzner ARM64, Ubuntu 24.04) is the only tested path so far. Other combinations should work but may have gaps in the gotcha coverage.&lt;/p&gt;

&lt;p&gt;The skill is also a snapshot as of early 2026. OpenClaw is actively developed. Signal might ship ARM64 binaries eventually. Docker might fix the UFW bypass. Treat specific version numbers with appropriate skepticism.&lt;/p&gt;

&lt;p&gt;And while the skill handles the setup, it doesn't replace understanding what's running on your server. If something breaks outside the skill's playbook, you'll need basic comfort with SSH, firewalls, and systemd to debug it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/souliane/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; ac-openclaw &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works with any AI agent that can read files and run commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; | &lt;a href="https://github.com/souliane/skills/blob/main/LICENSE" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>selfhosted</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Skill-Driven Development: Transferring your Craft to AI Agents</title>
      <dc:creator>Adrien Cossa</dc:creator>
      <pubDate>Fri, 13 Mar 2026 16:40:28 +0000</pubDate>
      <link>https://forem.com/souliane/skill-driven-development-transferring-your-craft-to-ai-agents-15an</link>
      <guid>https://forem.com/souliane/skill-driven-development-transferring-your-craft-to-ai-agents-15an</guid>
      <description>&lt;p&gt;Every project has its own way of doing things — migration patterns, transaction handling, deployment quirks, that one PDF template workflow nobody wants to touch. I've been writing this stuff down as markdown files with helper scripts so my AI agent can follow along. I've taken to calling this &lt;strong&gt;skill-driven development&lt;/strong&gt; (building on &lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;agent skills&lt;/a&gt;, a convention that started with Anthropic's Claude Code and has since been adopted by several other AI coding agents).&lt;/p&gt;

&lt;p&gt;This post walks through some skills I've put together and shows the feedback loop that makes them worth maintaining.&lt;/p&gt;




&lt;h2&gt;
  
  
  What skills add over a README
&lt;/h2&gt;

&lt;p&gt;A project README or an AGENTS.md can capture conventions. The skills I've been writing try to go a bit further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Progressive disclosure&lt;/strong&gt; — a slim main file loads first, with detailed references pulled in only when needed. This keeps the agent's context window focused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composability&lt;/strong&gt; — skills declare dependencies and load together. A Django skill + a project overlay skill + a TDD skill compose into a complete workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scripts&lt;/strong&gt; — skills can ship executable scripts alongside the instructions. A script the agent calls is more reliable than a 15-step procedure the agent interprets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-improvement&lt;/strong&gt; — when something goes wrong, the fix goes into the skill. Next session, the agent follows the updated instructions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is what makes it worth maintaining: every correction you make goes into the skill, and the agent doesn't make the same mistake twice.&lt;/p&gt;




&lt;h2&gt;
  
  
  The SDD loop
&lt;/h2&gt;

&lt;p&gt;There's a useful parallel with Test-Driven Development:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1967r2k74xarrz9mosl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1967r2k74xarrz9mosl.png" alt="diagram" width="784" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;TDD&lt;/th&gt;
&lt;th&gt;SDD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Write the test first&lt;/td&gt;
&lt;td&gt;Write the skill first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Run&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Run the code&lt;/td&gt;
&lt;td&gt;Let the agent produce the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Evaluate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Test fails → fix the &lt;strong&gt;code&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Output is wrong → fix the &lt;strong&gt;skill&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Loop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Until green&lt;/td&gt;
&lt;td&gt;Until the agent gets it right&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In TDD, you iterate on code until the tests pass. Here, you iterate on skills until the agent's output is what you wanted. The skill isn't a one-shot handoff — you keep tweaking it as you find gaps.&lt;/p&gt;

&lt;p&gt;In practice, the two aren't separate activities — skills encode TDD as part of the workflow. The implementation skill says "write a failing test first, then implement," and the agent does both. Nobody writes tests by hand and then separately invokes a skill; the skill &lt;em&gt;is&lt;/em&gt; what makes the agent follow TDD.&lt;/p&gt;

&lt;p&gt;When tests fail, it's worth asking whether the code is wrong or the skill is incomplete. Fix the skill, re-run. Over time it adds up — each fix prevents one class of mistake, and after enough sessions you've built up a decent set of guardrails just from things that went wrong.&lt;/p&gt;

&lt;p&gt;If you use &lt;a href="https://github.com/souliane/teatree" rel="noopener noreferrer"&gt;teatree&lt;/a&gt; (a set of lifecycle skills I put together for multi-repo development), this loop can be automated: &lt;code&gt;t3-retro&lt;/code&gt; runs a retrospective after each session and writes fixes into the skill files. But it works just as well manually — whenever you correct the agent, you can put that correction in a skill so it sticks.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a skill looks like
&lt;/h2&gt;

&lt;p&gt;A skill is a markdown file (&lt;code&gt;SKILL.md&lt;/code&gt;) with YAML frontmatter, often accompanied by scripts for the mechanical parts. Here's a simplified example of the markdown side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ac-django&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Django coding conventions and best practices.&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.2.0&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;# Django Conventions&lt;/span&gt;

&lt;span class="c1"&gt;## Models&lt;/span&gt;

&lt;span class="c1"&gt;### Fat Models Doctrine&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Business logic belongs in models, not views or serializers.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Use model managers for complex queries.&lt;/span&gt;

&lt;span class="c1"&gt;### Migrations&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Always use `apps.get_model()` in data migrations — never import directly.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Set `elidable=True` on data-only migrations.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Include both `forwards` and `backwards` functions.&lt;/span&gt;

&lt;span class="c1"&gt;## Settings&lt;/span&gt;

&lt;span class="c1"&gt;### Storage Configuration (Non-Negotiable)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Use `STORAGES` dict (Django 4.2+), not `DEFAULT_FILE_STORAGE`.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;The deprecated setting causes silent failures on deployment.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules marked &lt;code&gt;(Non-Negotiable)&lt;/code&gt; are things I've learned the hard way. "Always verify services respond via HTTP before declaring running" sounds obvious, but without it, the agent will say "servers started" without checking whether anything actually came up.&lt;/p&gt;

&lt;p&gt;These work with any agent that can read files — Claude Code, Codex, Cursor, whatever. The agent reads the skill and follows the instructions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's in the repo
&lt;/h2&gt;

&lt;p&gt;I've put the generic ones in a &lt;a href="https://github.com/souliane/skills" rel="noopener noreferrer"&gt;public repository&lt;/a&gt; in case any of them are useful to others. Here are the ones I'd recommend looking at first.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ac-reviewing-skills&lt;/code&gt; — keep your skills in shape
&lt;/h3&gt;

&lt;p&gt;This is probably the most broadly useful one. It does a deep audit of your skill files — architecture, content quality, script correctness, stale cross-references, duplicated guidance. I run it periodically and it consistently finds things I missed: rules that drifted between files, references pointing at renamed sections, scripts with missing error handling. If you maintain more than a handful of skills, it's worth running periodically.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ac-django&lt;/code&gt; — Django conventions that models already "know" but get wrong
&lt;/h3&gt;

&lt;p&gt;The agent knows Django. It doesn't know how &lt;em&gt;you&lt;/em&gt; use Django. This skill covers the mistakes I kept correcting: outdated migration patterns (&lt;code&gt;apps.get_model()&lt;/code&gt; vs direct imports), unsafe transaction handling, the &lt;code&gt;STORAGES&lt;/code&gt; dict vs deprecated &lt;code&gt;DEFAULT_FILE_STORAGE&lt;/code&gt;, &lt;code&gt;post_migrate&lt;/code&gt; signal timing for permission assignments. It's a reference, not a tutorial — it assumes the agent already understands the framework and just needs guardrails for the non-obvious parts.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ac-python&lt;/code&gt; is its companion for generic Python: style, typing, OOP patterns, testing conventions. Less opinionated, but useful as a baseline.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ac-adopting-ruff&lt;/code&gt; — structured linter migration
&lt;/h3&gt;

&lt;p&gt;A step-by-step playbook for replacing black + isort + flake8 with ruff, one rule category per MR. It handles the things I got stuck on — conflicting formatter settings, rule equivalences between linters, the &lt;code&gt;unfixable&lt;/code&gt; vs &lt;code&gt;ignore&lt;/code&gt; distinction. Doing it in one big MR is painful; the skill breaks it into reviewable increments.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ac-openclaw&lt;/code&gt; — self-hosted AI assistant setup
&lt;/h3&gt;

&lt;p&gt;An interactive guide to install &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; on a VPS or local machine. Covers server provisioning, OS hardening, model configuration (BYOK or local Ollama), messaging channel integration (Signal, WhatsApp, Telegram, etc.), and secure remote access (Cloudflare Tunnel, Tailscale, or Caddy). It walks through every decision point — useful if you want a self-hosted personal AI assistant without piecing together a dozen tutorials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything else
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;What it covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ac-python&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generic Python: style, typing, OOP design, testing, tooling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ac-editing-acroforms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AcroForm PDF templates: widget geometry, content streams, font subsetting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ac-auditing-repos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cross-repo infrastructure audit: harmonize pre-commit, linter, and editor configs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ac-writing-blog-posts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Article writing + social media promotion + dev.to publishing pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ac-generating-slides&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Markdown to presentation slides via Marp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ac-scaffolding-skill-repos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scaffold new skill repos with correct config and structure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ac-editing-acroforms&lt;/code&gt; deserves a mention — it came out of editing PDF form templates by hand. The internals (annotation dictionaries, appearance stream generation, widget flags) are barely documented. The agent can't figure this out from training data alone, so the skill ships with Python scripts that handle the tricky bits.&lt;/p&gt;

&lt;p&gt;This blog post was written with &lt;code&gt;ac-writing-blog-posts&lt;/code&gt;, for what it's worth.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to use them
&lt;/h2&gt;

&lt;p&gt;Install with &lt;a href="https://github.com/nichochar/skills" rel="noopener noreferrer"&gt;npx skills&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/souliane/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs all skills globally for your default agent. To install for multiple agents at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/souliane/skills &lt;span class="nt"&gt;--skill&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--agent&lt;/span&gt; claude-code codex cursor github-copilot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want the SDD feedback loop — where retrospective fixes land in files you can commit — clone the repo and symlink it into your agent's skills directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:souliane/skills.git ~/workspace/souliane/skills

&lt;span class="c"&gt;# Example for Claude Code — adjust the target for your agent runtime&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;skill &lt;span class="k"&gt;in&lt;/span&gt; ~/workspace/souliane/skills/ac-&lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$skill&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; ~/.claude/skills/&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$skill&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This points your agent at the live git checkout directly. When the agent (or you) updates a skill file, the change is immediately available in the next session and can be committed. Don't use &lt;code&gt;npx skills add&lt;/code&gt; for this — it creates a managed copy that doesn't point back to your clone.&lt;/p&gt;

&lt;p&gt;If you use &lt;a href="https://github.com/souliane/teatree" rel="noopener noreferrer"&gt;teatree&lt;/a&gt;, its setup wizard can suggest these as companion skills for your project overlay — they're loaded automatically when you work in matching repos.&lt;/p&gt;




&lt;h2&gt;
  
  
  When it helps
&lt;/h2&gt;

&lt;p&gt;Skills work best when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You correct the agent for the same kind of mistake more than once&lt;/li&gt;
&lt;li&gt;Your project has conventions that diverge from common patterns&lt;/li&gt;
&lt;li&gt;You work across sessions and the agent keeps losing context&lt;/li&gt;
&lt;li&gt;You use deterministic tools (PDF editors, linters, deployment scripts) where the agent needs exact steps&lt;/li&gt;
&lt;li&gt;You want to share a recipe with others — a skill is a portable, self-contained package that anyone can install and use with their own agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They're less useful for one-off tasks or when the model's defaults already match your preferences. But even something you only do once yourself might be worth writing as a skill if it's useful to someone else.&lt;/p&gt;

&lt;p&gt;These skills reflect my own workflow — Django, Python, PDF templates, multi-repo infrastructure. They might not match yours at all. The most useful skills are probably ones you'd write yourself for your own project's conventions. These are just examples of what worked for me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/souliane/skills" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://github.com/souliane/skills/blob/main/LICENSE" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Introducing Teatree: Parallel Multi-Repo Development with AI Agents</title>
      <dc:creator>Adrien Cossa</dc:creator>
      <pubDate>Thu, 12 Mar 2026 13:16:50 +0000</pubDate>
      <link>https://forem.com/souliane/introducing-teatree-parallel-multi-repo-development-with-ai-agents-cjp</link>
      <guid>https://forem.com/souliane/introducing-teatree-parallel-multi-repo-development-with-ai-agents-cjp</guid>
      <description>&lt;p&gt;I'm a Customer Success Engineer at &lt;a href="https://www.opercredits.com/" rel="noopener noreferrer"&gt;Oper Credits&lt;/a&gt;. My daily work involves a multi-repo project — backend, frontend, translations, configuration — and I use AI coding agents constantly. The friction isn't writing code; agents handle that well. It's everything surrounding it: following different conventions across codebases, coordinating changes across services, managing local environments that diverge from what's in git, and encoding the workflow patterns we could all benefit from.&lt;/p&gt;

&lt;p&gt;The agent can figure out most of these things, but it struggles with the specifics — it loops on troubleshooting, tries approaches that don't match the project's actual setup, and burns tokens on trial and error. I started putting together teatree to write down that knowledge so the agent doesn't have to rediscover it every session. It's also a way to define and automate your personal workflow without adding friction with your team — build it on your own, then push for adoption once it works.&lt;/p&gt;

&lt;p&gt;This post walks through the architecture, the design choices I landed on, and how the pieces fit together. It's long because there's a lot of ground to cover. If you just want the quick pitch, the &lt;a href="https://github.com/souliane/teatree" rel="noopener noreferrer"&gt;README&lt;/a&gt; has that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What it looks like&lt;/li&gt;
&lt;li&gt;The problem&lt;/li&gt;
&lt;li&gt;Skills as markdown and scripts&lt;/li&gt;
&lt;li&gt;The lifecycle graph&lt;/li&gt;
&lt;li&gt;Multi-repo worktree management&lt;/li&gt;
&lt;li&gt;The overlay and extension system&lt;/li&gt;
&lt;li&gt;Auto-loading hooks&lt;/li&gt;
&lt;li&gt;The retrospective loop&lt;/li&gt;
&lt;li&gt;Companion skills&lt;/li&gt;
&lt;li&gt;Getting started&lt;/li&gt;
&lt;li&gt;When it helps (and when it doesn't)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;Tell your AI agent what you want. Teatree skills guide it through the entire lifecycle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;https://gitlab.com/org/repo/-/issues/1234&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent fetches the ticket, creates synchronized worktrees, provisions isolated databases and ports, implements the feature with TDD, writes a test plan, runs E2E tests, self-reviews, then pushes and creates the merge request.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Fix PROJ-5678&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent fetches the failed test report from CI, reproduces locally, fixes, pushes, and monitors the pipeline until green.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Review https://gitlab.com/org/repo/-/merge_requests/456&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent fetches the ticket for context, inspects every commit individually, and posts draft review comments inline on the correct file and line.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Run the test plan for !789&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent generates a test plan from the MR changes, runs E2E tests, and posts evidence screenshots on the MR.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Follow up on my open tickets&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent batch-processes your assigned tickets, checks CI statuses, nudges stale MRs, and starts work on anything that's ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;AI coding agents can do a lot — reason about architecture, run tests, create merge requests. But without your project's specific context, they spend tokens and time rediscovering things you already know. Your repo layout, your CI conventions, your team's practices, your local tooling — none of that is in training data.&lt;/p&gt;

&lt;p&gt;The friction is especially pronounced with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-repo setups&lt;/strong&gt; — creating branches across 3+ repos for a single ticket, provisioning isolated databases, allocating non-conflicting ports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atypical local environments&lt;/strong&gt; — personal tooling that differs from what's in git, dev configurations the team hasn't adopted yet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational workflows&lt;/strong&gt; — self-reviewing before pushing, creating properly formatted merge requests, monitoring pipelines, running retrospectives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent can attempt all of these. But without explicit guidance, it either asks twenty questions or confidently does the wrong thing — and when something fails, it loops instead of applying the fix you already know.&lt;/p&gt;

&lt;p&gt;I tried shell scripts and aliases first, sometimes Python scripts too. They worked for the happy path but couldn't handle edge cases — the database import that fails because VPN is down, the port conflict because another worktree is still running, the CI format check that rejects your MR title. A shell script can't say "if the test fails, check if it's a known flake — here are the patterns." An AI agent can.&lt;/p&gt;

&lt;p&gt;So I started writing this stuff down — as markdown instructions with tested Python and shell scripts for the mechanical parts. The markdown gives the agent enough context to handle edge cases; the scripts handle deterministic operations where you don't want the agent improvising.&lt;/p&gt;




&lt;h2&gt;
  
  
  Skills as markdown and scripts
&lt;/h2&gt;

&lt;p&gt;A teatree skill starts with a markdown file (&lt;code&gt;SKILL.md&lt;/code&gt;) with YAML frontmatter, but the heavy lifting often happens in scripts that ship alongside it. Teatree currently has 15 Python executables, 9 library modules, and 3 shell scripts — backed by 26 test files. Here's a simplified example of the markdown side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;t3-code&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Writing code with TDD methodology.&lt;/span&gt;
&lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;t3-workspace&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.1&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;# Writing Code (TDD)&lt;/span&gt;

&lt;span class="c1"&gt;## Dependencies&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="nv"&gt;*t3-workspace&lt;/span&gt;&lt;span class="err"&gt;**&lt;/span&gt; &lt;span class="s"&gt;(required) — provides dev servers for live reload.&lt;/span&gt;

&lt;span class="c1"&gt;## Workflow&lt;/span&gt;

&lt;span class="c1"&gt;### 1. Plan First (Non-Negotiable)&lt;/span&gt;

&lt;span class="s"&gt;Always make a plan before writing code. Never jump straight to coding.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Identify scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;which files, modules, and repos are affected.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Review existing patterns in the codebase before writing new code.&lt;/span&gt;

&lt;span class="c1"&gt;### 2. TDD Cycle&lt;/span&gt;

&lt;span class="s"&gt;Write failing test → Implement → Green → Refactor&lt;/span&gt;

&lt;span class="c1"&gt;### 3. Follow Conventions&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Language/framework conventions from the project's convention skills.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Repository-specific patterns take precedence over generic guidance.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills contain both instructions and scripts.&lt;/strong&gt; The markdown tells the agent &lt;em&gt;when&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt; to do things. The Python scripts handle deterministic operations: worktree creation, port allocation, database provisioning, branch finalization. A script the agent calls is more robust than a 15-step procedure in a markdown file. Instructions for judgment calls, scripts for mechanical work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills declare dependencies.&lt;/strong&gt; The &lt;code&gt;requires:&lt;/code&gt; field in the frontmatter tells the loading system which other skills need to be present. When &lt;code&gt;t3-code&lt;/code&gt; is loaded, &lt;code&gt;t3-workspace&lt;/code&gt; comes along automatically. This eliminates wasted round-trips where the agent reads a skill, sees "Load &lt;code&gt;/t3-workspace&lt;/code&gt; now", and then has to make a second call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills use progressive disclosure.&lt;/strong&gt; Most &lt;code&gt;SKILL.md&lt;/code&gt; files are 80–160 lines, with detailed procedures in &lt;code&gt;references/&lt;/code&gt; files that the agent reads on demand. This keeps the typical skill set well within a reasonable context budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills have rules marked &lt;code&gt;(Non-Negotiable)&lt;/code&gt;.&lt;/strong&gt; These are things I've had to learn the hard way. "Always verify services respond via HTTP before declaring running" sounds obvious, but without it, the agent will say "servers started" without checking whether anything actually came up.&lt;/p&gt;




&lt;h2&gt;
  
  
  The lifecycle graph
&lt;/h2&gt;

&lt;p&gt;Teatree organizes development into phases, each handled by a dedicated skill:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkldryhcyx4niugm82im.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkldryhcyx4niugm82im.png" alt="diagram" width="784" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow is: &lt;strong&gt;ticket → code → test → review → ship → retro&lt;/strong&gt;, with &lt;code&gt;t3-workspace&lt;/code&gt; providing infrastructure to all phases and &lt;code&gt;t3-debug&lt;/code&gt; available whenever something breaks.&lt;/p&gt;

&lt;p&gt;Here's what each skill does:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What it handles&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-setup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bootstrapping&lt;/td&gt;
&lt;td&gt;Interactive setup wizard, health checks, overlay scaffolding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-workspace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;multi-repo worktrees, port allocation, DB provisioning, env files, dev servers, cleanup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-ticket&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Intake&lt;/td&gt;
&lt;td&gt;Fetch the issue, extract acceptance criteria, detect affected repos, detect tenant/variant, create worktrees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Implementation&lt;/td&gt;
&lt;td&gt;Plan-first workflow, TDD cycle, convention enforcement, feature flag checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;td&gt;Test execution, CI interaction, E2E test plans, quality gates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-debug&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Troubleshooting&lt;/td&gt;
&lt;td&gt;Systematic 5-phase debugging protocol, user-hint-first investigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Code review&lt;/td&gt;
&lt;td&gt;Self-review checklist, giving review, receiving feedback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-ship&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delivery&lt;/td&gt;
&lt;td&gt;Commit formatting, branch finalization, MR creation, pipeline monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-review-request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Notifications&lt;/td&gt;
&lt;td&gt;Post MR links to review channels, check for duplicate requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-retro&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Improvement&lt;/td&gt;
&lt;td&gt;Conversation audit, root cause analysis, skill updates, privacy scans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-contribute&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contribution&lt;/td&gt;
&lt;td&gt;Push skill improvements to fork, open upstream issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t3-followup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Batch ops&lt;/td&gt;
&lt;td&gt;Process assigned tickets, check CI statuses, nudge stale MRs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The skills mirror how development actually works. Implementing a ticket touches intake, coding, testing, review, and delivery — often across multiple repos. Making the skills fully independent would mean duplicating knowledge across every one of them, which always diverges over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  The follow-up dashboard
&lt;/h3&gt;

&lt;p&gt;One skill worth highlighting is &lt;code&gt;t3-followup&lt;/code&gt;. It runs your daily routine: batch-processing new tickets, checking CI statuses, advancing tickets through their lifecycle, and nudging reviewers about stale MRs.&lt;/p&gt;

&lt;p&gt;As it works, it builds a persistent cache (&lt;code&gt;followup.json&lt;/code&gt;) of all in-flight work — tickets, merge requests, pipeline statuses, review request states, and review comment tracking. From that cache, it generates an HTML dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jgptus1dyri2l28thro.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jgptus1dyri2l28thro.png" alt="t3-followup dashboard" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The dashboard gives you a single view of everything that's in flight: ticket lifecycle status, pipeline results (color-coded pills), review request state, and tracked review comments. Everything is a clickable link — tickets, MRs, CI pipelines, Slack messages — so you can jump directly into any conversation.&lt;/p&gt;

&lt;p&gt;The cache is a plain JSON file, so project overlays can inject extra fields (external tracker status, deployment state, tenant info) via the &lt;code&gt;followup_enrich_data&lt;/code&gt; extension point. Stale tickets are purged automatically after their MRs have been merged for 14 days (configurable via &lt;code&gt;T3_FOLLOWUP_PURGE_DAYS&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-repo worktree management
&lt;/h2&gt;

&lt;p&gt;This is where I started, and it's the feature I use most.&lt;/p&gt;

&lt;p&gt;Suppose your project has three repos: &lt;code&gt;acme-backend&lt;/code&gt;, &lt;code&gt;acme-frontend&lt;/code&gt;, and &lt;code&gt;acme-translations&lt;/code&gt;. You're about to work on ticket PROJ-1234. Running &lt;code&gt;t3_ticket PROJ-1234&lt;/code&gt; creates this structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flt9n5aucch3fz0ia5404.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flt9n5aucch3fz0ia5404.png" alt="diagram" width="784" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each ticket gets its own directory containing one git worktree per affected repo — lightweight checkouts that share the &lt;code&gt;.git&lt;/code&gt; directory with the main clone but have their own branch and working tree. A shared &lt;code&gt;.env.worktree&lt;/code&gt; file provides allocated ports, database name, and variant configuration.&lt;/p&gt;

&lt;p&gt;After creating the worktrees, &lt;code&gt;t3_setup&lt;/code&gt; provisions the environment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Symlinks&lt;/strong&gt; — &lt;code&gt;.venv&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.python-version&lt;/code&gt;, and configurable shared directories are symlinked from the main repo (so you don't reinstall dependencies for every worktree)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment files&lt;/strong&gt; — &lt;code&gt;.env.worktree&lt;/code&gt; with unique ports, database URL, variant-specific overrides&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt; — creates an isolated DB, imports from a snapshot or dump, runs migrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;direnv&lt;/strong&gt; — auto-loads environment variables when you &lt;code&gt;cd&lt;/code&gt; into the worktree&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend dependencies&lt;/strong&gt; — installs if the lockfile changed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then &lt;code&gt;t3_start&lt;/code&gt; brings everything up: Docker services, migrations, backend server, frontend dev server. Each worktree is fully isolated — its own database, its own ports, its own services. You can have ticket 1234 and ticket 5678 running simultaneously without conflicts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters
&lt;/h3&gt;

&lt;p&gt;Without isolation, the most common failure is contamination between tickets. You're working on ticket A, make a database change, then switch to ticket B which expected the old schema — migrations fail, the frontend shows stale data, and you spend time figuring out what went wrong. Worktree isolation avoids this. Each ticket is a clean room.&lt;/p&gt;

&lt;p&gt;The other benefit is parallelism. While waiting for CI on ticket A, start working on ticket B in a completely separate environment. No branch switching, no stashing, no "wait, which database am I pointing at?"&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-tenant awareness
&lt;/h3&gt;

&lt;p&gt;If your project serves multiple tenants — each with their own configuration, feature flags, and sometimes database — teatree handles that too. The variant system (&lt;code&gt;wt_detect_variant&lt;/code&gt;) auto-detects the target tenant from ticket labels, descriptions, or external trackers, then provisions tenant-specific databases, environment variables, and configuration. Feature flag checks during code review ensure changes are properly scoped per tenant.&lt;/p&gt;

&lt;p&gt;The project overlay wires in your tenant-to-variant mapping; teatree handles the rest. This means "set up a worktree for ticket X" automatically produces an environment configured for the correct tenant — no manual env file editing, no guesswork about which tenant you're in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;t3_ticket&lt;/code&gt; instead of raw git commands
&lt;/h3&gt;

&lt;p&gt;The convention is &lt;code&gt;&amp;lt;ticket&amp;gt;/&amp;lt;repo&amp;gt;/&lt;/code&gt; — a ticket directory containing worktrees. Raw &lt;code&gt;git worktree add&lt;/code&gt; creates flat worktrees at whatever path you give it, which breaks the ticket-directory structure that every other tool expects. &lt;code&gt;t3_ticket&lt;/code&gt; enforces the convention, handles branch naming (with your prefix), and creates worktrees across all affected repos in one call. The skill file marks this as &lt;code&gt;(Non-Negotiable)&lt;/code&gt; because flat worktrees cause subtle breakage downstream.&lt;/p&gt;




&lt;h2&gt;
  
  
  The overlay and extension system
&lt;/h2&gt;

&lt;p&gt;Teatree knows how to create worktrees, allocate ports, and orchestrate a development lifecycle. It doesn't know how to start &lt;em&gt;your&lt;/em&gt; backend, import &lt;em&gt;your&lt;/em&gt; database, or create &lt;em&gt;your&lt;/em&gt; merge requests. That project-specific knowledge lives in a &lt;strong&gt;project overlay&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The three-layer architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnylhll95t2svhgbya9us.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnylhll95t2svhgbya9us.png" alt="diagram" width="784" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When teatree needs to do something project-specific (start the backend, import a database, create an MR), it calls an &lt;strong&gt;extension point&lt;/strong&gt; through a registry. The registry resolves the implementation using a 3-layer priority:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Highest&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Project&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your overlay's &lt;code&gt;project_hooks.py&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;t3_start&lt;/code&gt; that runs Docker + Django + Angular&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Middle&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Framework integration (e.g., Django)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wt_post_db&lt;/code&gt; that runs &lt;code&gt;manage.py migrate&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lowest&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Default&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Teatree core fallback&lt;/td&gt;
&lt;td&gt;Usually a no-op or "not configured" message&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The registry itself is simple — 45 lines of Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_LAYERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;framework&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_LAYER_RANK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_LAYERS&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="n"&gt;_registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;]]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;lyr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;lyr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lyr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_LAYER_RANK&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# highest priority = last entry
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No handler registered for extension point &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registering a handler at the &lt;code&gt;"project"&lt;/code&gt; layer automatically overrides anything at &lt;code&gt;"framework"&lt;/code&gt; or &lt;code&gt;"default"&lt;/code&gt;. The framework layer is there so teatree can ship framework integrations (Django is the first) that work out of the box but can still be overridden by project-specific needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What an overlay looks like
&lt;/h3&gt;

&lt;p&gt;A project overlay is a directory with this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acme-overlay/
├── SKILL.md                    # Skill description + loading order
├── scripts/
│   └── lib/
│       ├── bootstrap.sh        # Shell wrappers (sourced after teatree)
│       ├── shell_helpers.sh    # Env loading, variant detection
│       └── project_hooks.py    # Extension point overrides
├── hook-config/
│   ├── context-match.yml       # Patterns that trigger this overlay
│   └── reference-injections.yml # References to load per lifecycle phase
└── references/
    ├── prerequisites-and-setup.md
    ├── troubleshooting.md
    └── playbooks/
        └── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;project_hooks.py&lt;/code&gt; file registers your overrides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;lib.registry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_acme&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wt_env_extra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;envfile&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;envfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACME_API_KEY=dev-key&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wt_db_import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;main_repo&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Import from your team's shared dump
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;lib.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;db_restore&lt;/span&gt;
        &lt;span class="nf"&gt;db_restore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;main_repo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/dumps/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_latest.sql&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wt_run_backend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manage.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;runserver&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                      &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wt_env_extra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wt_env_extra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wt_db_import&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wt_db_import&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wt_run_backend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wt_run_backend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The teatree core scripts call &lt;code&gt;registry.call("wt_run_backend")&lt;/code&gt;, and your project handler runs instead of the default "not configured" stub. You only override what you need — everything else falls through to the framework or default layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  There are 25 extension points
&lt;/h3&gt;

&lt;p&gt;They cover the full lifecycle:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Extension Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Workspace setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wt_symlinks&lt;/code&gt;, &lt;code&gt;wt_env_extra&lt;/code&gt;, &lt;code&gt;wt_services&lt;/code&gt;, &lt;code&gt;wt_detect_variant&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wt_db_import&lt;/code&gt;, &lt;code&gt;wt_post_db&lt;/code&gt;, &lt;code&gt;wt_restore_ci_db&lt;/code&gt;, &lt;code&gt;wt_reset_passwords&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dev servers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wt_run_backend&lt;/code&gt;, &lt;code&gt;wt_run_frontend&lt;/code&gt;, &lt;code&gt;wt_build_frontend&lt;/code&gt;, &lt;code&gt;wt_start_session&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wt_run_tests&lt;/code&gt;, &lt;code&gt;wt_trigger_e2e&lt;/code&gt;, &lt;code&gt;wt_quality_check&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delivery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wt_create_mr&lt;/code&gt;, &lt;code&gt;wt_monitor_pipeline&lt;/code&gt;, &lt;code&gt;wt_send_review_request&lt;/code&gt;, &lt;code&gt;wt_fetch_failed_tests&lt;/code&gt;, &lt;code&gt;wt_fetch_ci_errors&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ticket management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ticket_check_deployed&lt;/code&gt;, &lt;code&gt;ticket_update_external_tracker&lt;/code&gt;, &lt;code&gt;ticket_get_mrs&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Follow-up&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;followup_enrich_data&lt;/code&gt;, &lt;code&gt;followup_enrich_dashboard&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;/t3-setup&lt;/code&gt; wizard can scaffold an overlay for you. Tell it your repos, your backend framework, and your database, and it generates the skeleton with commented-out examples for each relevant extension point. From there, fill in the blanks — or ask your AI agent to fill them in if it already knows your codebase (e.g., after working in the repos for a while).&lt;/p&gt;

&lt;h3&gt;
  
  
  The sourcing chain
&lt;/h3&gt;

&lt;p&gt;Shell functions are loaded in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In .zshrc:&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.teatree                                     &lt;span class="c"&gt;# load config&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$T3_REPO&lt;/span&gt;&lt;span class="s2"&gt;/scripts/lib/bootstrap.sh"&lt;/span&gt;            &lt;span class="c"&gt;# teatree core functions&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$T3_OVERLAY&lt;/span&gt;&lt;span class="s2"&gt;/scripts/lib/bootstrap.sh"&lt;/span&gt;         &lt;span class="c"&gt;# project overlay overrides&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The overlay's bootstrap has a guard — it checks that teatree was sourced first (&lt;code&gt;_T3_SCRIPTS_DIR&lt;/code&gt; must be set). This prevents confusing errors from running the overlay standalone.&lt;/p&gt;

&lt;p&gt;Inside Python scripts, the pattern is similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lib.init&lt;/span&gt;
&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                 &lt;span class="c1"&gt;# registers defaults + auto-detects framework
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;lib.project_hooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;register_project&lt;/span&gt;
&lt;span class="nf"&gt;register_project&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;              &lt;span class="c1"&gt;# registers project overrides at 'project' layer
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;lib.registry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;
&lt;span class="nf"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wt_post_db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# calls highest-priority handler
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Auto-loading hooks
&lt;/h2&gt;

&lt;p&gt;Skills don't help if the agent doesn't load them. I got tired of manually telling it which skill to read, so I added a hook that suggests the right skills automatically based on what you're doing.&lt;/p&gt;

&lt;p&gt;The mechanism is &lt;code&gt;ensure-skills-loaded.sh&lt;/code&gt;, a hook that runs before every message (in Claude Code, this is a &lt;code&gt;UserPromptSubmit&lt;/code&gt; hook; other agent platforms would use their own equivalent). It does three things:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wk4z05w7ht6n32dr4py.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wk4z05w7ht6n32dr4py.png" alt="diagram" width="784" height="1768"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Project context detection
&lt;/h3&gt;

&lt;p&gt;The hook scans all skill directories for &lt;code&gt;hook-config/context-match.yml&lt;/code&gt; files. If any pattern in the file matches the current working directory or the active-repo tracker, that skill is identified as the project overlay. This is how teatree knows you're working in a specific project without you having to say so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# hook-config/context-match.yml&lt;/span&gt;
&lt;span class="na"&gt;cwd_patterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acme-backend"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acme-frontend"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your &lt;code&gt;$PWD&lt;/code&gt; contains &lt;code&gt;acme-backend&lt;/code&gt;, the hook knows you're in the acme project and will suggest loading the &lt;code&gt;ac-acme&lt;/code&gt; overlay alongside whatever lifecycle skill you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Intent detection
&lt;/h3&gt;

&lt;p&gt;The hook parses the prompt to figure out which lifecycle phase you're in. It checks for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;URL patterns&lt;/strong&gt; — a GitLab issue URL triggers &lt;code&gt;t3-ticket&lt;/code&gt;, a Sentry URL triggers &lt;code&gt;t3-debug&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyword patterns&lt;/strong&gt; — "implement" triggers &lt;code&gt;t3-code&lt;/code&gt;, "push" triggers &lt;code&gt;t3-ship&lt;/code&gt;, "broken" triggers &lt;code&gt;t3-debug&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-of-session phrases&lt;/strong&gt; — "done", "all set", "that's it" triggers &lt;code&gt;t3-retro&lt;/code&gt; (only if at least one other skill was loaded this session)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bare imperative verbs&lt;/strong&gt; — "Fix the login page" triggers &lt;code&gt;t3-code&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If nothing matches and you're in project context, it defaults to &lt;code&gt;t3-code&lt;/code&gt; — because most prompts in a project directory are about coding.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Dependency resolution and suggestion
&lt;/h3&gt;

&lt;p&gt;Once the hook knows which skill you need, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parses the skill's &lt;code&gt;requires:&lt;/code&gt; frontmatter to find dependencies&lt;/li&gt;
&lt;li&gt;Checks which skills are already loaded (tracked in a session file)&lt;/li&gt;
&lt;li&gt;Builds a suggestion list of skills that need loading&lt;/li&gt;
&lt;li&gt;Adds companion skills (e.g., &lt;code&gt;ac-django&lt;/code&gt; for backend work in a Django project)&lt;/li&gt;
&lt;li&gt;Adds reference file injections from &lt;code&gt;reference-injections.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The output looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOAD THESE SKILLS NOW: /t3-workspace, /t3-code, /ac-acme.
ACME references to read: references/prerequisites-and-setup.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent sees this as a system message and loads the skills before doing anything else. The wording is intentionally forceful ("LOAD THESE SKILLS NOW") — softer phrasing ("Consider loading...") gets ignored by models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symlink health checks
&lt;/h3&gt;

&lt;p&gt;The hook also runs a once-per-session health check on skills that you maintain (determined by an ownership config):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifies skill symlinks are actual symlinks (not stale copies)&lt;/li&gt;
&lt;li&gt;Checks that the source is a real git repository (not a downloaded zip)&lt;/li&gt;
&lt;li&gt;Validates that symlinks point into git repos (so retrospective commits work)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If anything is broken, it either auto-fixes (re-running the installer) or warns with a specific remediation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The retrospective loop
&lt;/h2&gt;

&lt;p&gt;After every non-trivial session, &lt;code&gt;t3-retro&lt;/code&gt; runs a retrospective — a systematic audit of the conversation that produces concrete skill improvements and optionally contributes them upstream.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figu93yip3et13p3r9dsq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figu93yip3et13p3r9dsq.png" alt="diagram" width="784" height="1848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What the audit catches
&lt;/h3&gt;

&lt;p&gt;The retrospective categorizes issues into specific types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;What went wrong&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;False completion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claimed "done" without full verification&lt;/td&gt;
&lt;td&gt;Said feature was complete but didn't run the test suite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skill not loaded&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A relevant skill existed but wasn't loaded&lt;/td&gt;
&lt;td&gt;Worked in project context without the overlay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Playbook miss&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A playbook covered the task but wasn't consulted&lt;/td&gt;
&lt;td&gt;Didn't check the deployment playbook before pushing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Over-engineering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did unnecessary work&lt;/td&gt;
&lt;td&gt;Built a migration when admin config would have sufficed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Under-engineering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Missed required work&lt;/td&gt;
&lt;td&gt;Updated the backend but forgot the frontend changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hook gap&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-loading should have triggered but didn't&lt;/td&gt;
&lt;td&gt;Hook didn't detect intent from "fix the flaky test"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stale guidance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Followed outdated instructions&lt;/td&gt;
&lt;td&gt;Playbook referenced pre-refactoring patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For each issue, the retrospective determines the root cause and writes the fix directly into the skill system — a new guardrail, an updated playbook, a troubleshooting entry, a hook pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where improvements go
&lt;/h3&gt;

&lt;p&gt;The retrospective respects a clear hierarchy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project overlay&lt;/strong&gt; (&lt;code&gt;$T3_OVERLAY&lt;/code&gt;) — receives project-specific improvements (troubleshooting, playbooks, guardrails). This is the default target when &lt;code&gt;T3_CONTRIBUTE&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core skills&lt;/strong&gt; (&lt;code&gt;$T3_REPO&lt;/code&gt;) — only modified when &lt;code&gt;T3_CONTRIBUTE=true&lt;/code&gt;, and only for generic improvements (missing verification steps, hook gaps, stale core guidance)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal config&lt;/strong&gt; (memory files, agent config like &lt;code&gt;AGENTS.md&lt;/code&gt;) — for user preferences and environment-specific facts. Also serves as a fallback location when the overlay isn't maintained by the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The contribution model
&lt;/h3&gt;

&lt;p&gt;When you enable &lt;code&gt;T3_CONTRIBUTE=true&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The retrospective creates a local commit on the current branch in your fork. It never pushes automatically.&lt;/li&gt;
&lt;li&gt;A privacy scan checks for emails, home directory paths, API keys, internal hostnames, and any terms in &lt;code&gt;$T3_BANNED_TERMS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When you're ready, &lt;code&gt;/t3-contribute&lt;/code&gt; reviews what will be pushed, checks for fork divergence, and optionally opens an issue on the upstream repo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The idea is that every user's failures make the system better for all users — but only through an explicit, reviewed contribution path. Nothing happens without your consent. The default is &lt;code&gt;T3_CONTRIBUTE=false&lt;/code&gt;, which means the retrospective only improves your project overlay and personal config.&lt;/p&gt;

&lt;h3&gt;
  
  
  A concrete example
&lt;/h3&gt;

&lt;p&gt;Suppose during a session, the agent set up a multi-repo worktree and claimed it was ready, but the backend server failed to start due to port conflicts with a previous worktree. The agent didn't verify that the infrastructure was actually running before declaring complete.&lt;/p&gt;

&lt;p&gt;The retrospective would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audit&lt;/strong&gt;: Identify this as "false completion" — claimed infrastructure ready without verification evidence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root cause&lt;/strong&gt;: The &lt;code&gt;t3-workspace&lt;/code&gt; script runs through all setup steps but has no way for projects to define and verify health checks before the agent declares the worktree usable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix (core)&lt;/strong&gt;: Add a new extension point &lt;code&gt;wt_health_check&lt;/code&gt; to &lt;code&gt;t3-workspace&lt;/code&gt; that projects can implement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix (overlay)&lt;/strong&gt;: Implement &lt;code&gt;wt_health_check&lt;/code&gt; in the project's &lt;code&gt;project_hooks.py&lt;/code&gt; to curl the backend, check the frontend dev server, verify the database is accessible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify&lt;/strong&gt;: Check that the skill file parses, the extension point is registered correctly, and the overlay hook runs without errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit&lt;/strong&gt;: If &lt;code&gt;T3_CONTRIBUTE=true&lt;/code&gt;, commit the core extension point to the fork's teatree core skills; overlay changes go to the project overlay repo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next time the agent sets up a worktree, &lt;code&gt;t3-workspace&lt;/code&gt; runs the project's health checks before finishing — the core provides the mechanism, the project overlay provides the specifics. Both are enforced going forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  It adds up
&lt;/h3&gt;

&lt;p&gt;A single retrospective might fix one guardrail. After enough sessions, you've accumulated a lot of them — each one from a specific failure that actually happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Companion skills
&lt;/h2&gt;

&lt;p&gt;Teatree handles the lifecycle — ticket intake, worktree management, TDD, review, delivery. It doesn't know about your programming language's conventions or your framework's best practices. That's what companion skills are for.&lt;/p&gt;

&lt;p&gt;Companion skills are standalone skills that live in separate repos and are loaded alongside teatree when relevant. I maintain a few (&lt;a href="https://github.com/souliane/skills" rel="noopener noreferrer"&gt;souliane/skills&lt;/a&gt;) covering Django and Python conventions, but the best companion skill for your stack is one you find (or build) yourself. I wrote a separate post about &lt;a href="https://dev.to/souliane/skill-driven-development-transferring-your-craft-to-ai-agents"&gt;skill-driven development and the skills I'm open-sourcing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The project overlay's &lt;code&gt;hook-config/context-match.yml&lt;/code&gt; wires companion skills to repo patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;companion_skills&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ac-django&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acme-backend"&lt;/span&gt;
  &lt;span class="na"&gt;ac-python&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acme-backend"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the hook detects you're working in &lt;code&gt;acme-backend&lt;/code&gt;, it suggests loading &lt;code&gt;ac-django&lt;/code&gt; and &lt;code&gt;ac-python&lt;/code&gt; alongside the lifecycle skill. You get framework conventions without cluttering the core lifecycle skills with language-specific details.&lt;/p&gt;

&lt;p&gt;This separation matters. Django conventions change on a different cadence than worktree management. Keeping them in separate skills means you can update one without touching the other, and teams using Flask or Express aren't burdened with Django-specific guidance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Companion skills vs framework layer
&lt;/h3&gt;

&lt;p&gt;These are different things. The &lt;strong&gt;framework layer&lt;/strong&gt; is teatree's built-in middle priority in the 3-layer extension point registry — it ships stock implementations for common frameworks (e.g., a Django integration that auto-registers &lt;code&gt;manage.py migrate&lt;/code&gt; as the post-DB hook). &lt;strong&gt;Companion skills&lt;/strong&gt; are external standalone skills that teach the agent coding conventions — they don't register extension points, they provide guidelines. The framework layer handles &lt;em&gt;infrastructure&lt;/em&gt; (how to run migrations); companion skills handle &lt;em&gt;conventions&lt;/em&gt; (how to write good Django code).&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;An AI coding agent (the auto-loading hooks currently target &lt;a href="https://docs.anthropic.com/en/docs/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, but the skills and scripts work with any agent that can read files and run commands)&lt;/li&gt;
&lt;li&gt;Python 3.12+&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt; (Python package manager)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Teatree requires a local git clone — it has shared infrastructure (&lt;code&gt;scripts/&lt;/code&gt;, &lt;code&gt;references/&lt;/code&gt;, &lt;code&gt;integrations/&lt;/code&gt;) that lives outside the individual skill directories, so &lt;code&gt;npx skills add&lt;/code&gt; alone isn't enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/souliane/teatree/fork" rel="noopener noreferrer"&gt;Fork the repo on GitHub&lt;/a&gt; (or just clone it directly if you don't plan to contribute back), then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:YOUR_USERNAME/teatree.git ~/workspace/teatree
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/workspace/teatree
./scripts/install_skills.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The install script creates symlinks from your agent's skills directory to the clone. Then open your agent and run &lt;code&gt;/t3-setup&lt;/code&gt; — it handles config, shell integration, hooks, and optionally scaffolds a project overlay for your repos.&lt;/p&gt;

&lt;p&gt;If you want the retrospective loop to write improvements back into skill files, set &lt;code&gt;T3_CONTRIBUTE=true&lt;/code&gt; in &lt;code&gt;~/.teatree&lt;/code&gt; (created by &lt;code&gt;/t3-setup&lt;/code&gt;). This requires a fork — the agent pushes to your fork, not to the upstream repo.&lt;/p&gt;

&lt;p&gt;The setup wizard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Checks prerequisites&lt;/strong&gt; — verifies all required tools are installed, reports a summary table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creates &lt;code&gt;~/.teatree&lt;/code&gt;&lt;/strong&gt; — asks for workspace path, branch prefix, issue tracker, chat platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaffolds a project overlay&lt;/strong&gt; (optional) — ask it about your repos, framework, and database, and it generates the skeleton&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configures shell integration&lt;/strong&gt; — adds sourcing lines to &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Installs skill symlinks&lt;/strong&gt; — creates the symlink chain from the agent's skills directory to your clone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configures hooks&lt;/strong&gt; — sets up &lt;code&gt;ensure-skills-loaded.sh&lt;/code&gt; and the statusline (Claude Code-specific; other agents would configure their own hooks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs a smoke test&lt;/strong&gt; — verifies hooks parse, statusline runs, Python imports work&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After setup, restart your agent (or start a new conversation). Try: "start working on ticket PROJ-1234" — the hook should suggest &lt;code&gt;/t3-ticket&lt;/code&gt; + &lt;code&gt;/t3-workspace&lt;/code&gt;, and the agent will take it from there.&lt;/p&gt;

&lt;p&gt;You can re-run &lt;code&gt;/t3-setup&lt;/code&gt; at any time as a health check. It validates the existing installation, checks for broken symlinks, verifies hook wording, and reports what needs fixing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The directory structure after setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/
├── .teatree                    # Config file (sourced by shell)
├── .local/share/teatree/       # Runtime data (ticket cache, dashboard, MR reminders, cache)
├── .claude/                    # Claude Code example (adapt paths for your agent)
│   ├── CLAUDE.md               # Agent instructions (skill-loading block)
│   ├── settings.json           # Hooks, statusline
│   └── skills/
│       ├── t3-ticket -&amp;gt; ~/workspace/teatree/t3-ticket
│       ├── t3-code -&amp;gt; ~/workspace/teatree/t3-code
│       ├── ...
│       └── ac-acme -&amp;gt; ~/workspace/acme-overlay
└── workspace/
    ├── teatree/                # Teatree clone (or fork)
    ├── acme-overlay/           # Project overlay
    ├── acme-backend/           # Main repo clone
    ├── acme-frontend/          # Main repo clone
    └── ac/                     # Ticket worktrees
        ├── 1234/
        │   ├── acme-backend/   # Worktree
        │   ├── acme-frontend/  # Worktree
        │   └── .env.worktree   # Shared env
        └── 5678/
            └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The symlinks ensure that skill files always resolve to the live git clone. This is important for the retrospective — when the agent writes improvements to skill files, the changes land in a real git repository where they can be committed and pushed.&lt;/p&gt;




&lt;h2&gt;
  
  
  When it helps (and when it doesn't)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It helps most with:&lt;/strong&gt; structured, repeatable processes that span multiple repos or require project-specific knowledge. Ticket intake, worktree setup, TDD cycles, code review, MR creation, CI debugging. The kind of work that eats hours but follows a pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It helps less with:&lt;/strong&gt; one-off creative decisions, highly ambiguous tasks, or projects simple enough that a single repo with &lt;code&gt;npm start&lt;/code&gt; covers everything. If your development workflow is "edit a file and push," teatree is overkill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sweet spot&lt;/strong&gt; is when you have enough friction that encoding it pays off through repetition. The project works for my workflow but hasn't been tested beyond that. If something doesn't click for your setup, open an issue or a PR. Or point your AI agent at the problem and let it fix things until it works for you — that's kind of the point.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on security
&lt;/h3&gt;

&lt;p&gt;Teatree skills are prompt instructions — they control what your AI agent does. That makes the supply chain a security surface. The defaults are conservative: self-improvement is off (&lt;code&gt;T3_CONTRIBUTE=false&lt;/code&gt;), pushing is disabled (&lt;code&gt;T3_PUSH=false&lt;/code&gt;), and there is no auto-update mechanism. You opt in to each level of automation explicitly. If you use a fork from someone else, you're trusting that person's skill files as agent instructions — review changes before pulling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why "teatree"?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TEA&lt;/strong&gt;'s &lt;strong&gt;E&lt;/strong&gt;xtensible &lt;strong&gt;A&lt;/strong&gt;rchitecture for work*&lt;em&gt;tree&lt;/em&gt;* management. Also, teatree oil cuts through grime, which felt fitting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/souliane/teatree" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://github.com/souliane/teatree/blob/main/LICENSE" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
