# Ponytail Plugin — Technická analýza integrace **Zdroj:** https://github.com/DietrichGebert/ponytail/tree/main **Verze:** 4.8.3 | **Licence:** MIT **Autor:** Dietrich Gebert --- ## 1. Co to je Ponytail je **multi-platformní plugin pro coding agenty** (Claude Code, Codex CLI, Pi, OpenCode, Gemini CLI) který vynucuje tzv. **"lazy senior dev" mód**. Cíl: donutit LLM agenta, aby generoval **nejmenší správné řešení** — YAGNI, stdlib first, žádné nevyžádané abstrakce, žádné předčasné generalizace. --- ## 2. Architektura — jeden zdroj pravdy, více platforem Plugin používá **sdílené jádro** (`hooks/`, `AGENTS.md`, `skills/`) a pro každou platformu jen tenkou adaptační vrstvu: ``` ponytail/ ├── AGENTS.md # Hlavní ruleset (lazy senior dev principles) ├── skills/ponytail/SKILL.md # Skill definice pro agenta ├── hooks/ # Sdílené JS moduly (Node.js) │ ├── ponytail-instructions.js # Builder instrukcí podle módu │ ├── ponytail-config.js # Cesty, default módy │ ├── ponytail-runtime.js # Flag file I/O, output formáty │ ├── ponytail-activate.js # SessionStart hook │ ├── ponytail-subagent.js # SubagentStart hook │ ├── ponytail-mode-tracker.js # CommandExecute hook (/ponytail) │ └── ponytail-statusline.sh # Bash statusline indikátor ├── .claude-plugin/plugin.json # Claude Code manifest ├── .codex-plugin/plugin.json # Codex CLI manifest ├── pi-extension/index.js # Pi coding agent plugin ├── .opencode/plugins/ponytail.mjs # OpenCode plugin (ESM) └── gemini-extension.json # Gemini CLI manifest ``` --- ## 3. Claude Code integrace ### 3.1 Manifest (`.claude-plugin/plugin.json`) ```json { "name": "ponytail", "version": "4.8.3", "description": "Lazy senior dev mode...", "skills": "./skills/", "hooks": "./hooks/claude-codex-hooks.json", "interface": { "displayName": "Ponytail", "shortDescription": "Lazy senior developer mode", "capabilities": ["Instructions", "Lifecycle hooks"], "defaultPrompt": [ "Use Ponytail mode for this task.", "Review this diff for over-engineering." ] } } ``` **Klíčové pole:** `hooks` → ukazuje na `claude-codex-hooks.json` který definuje **lifecycle hook bindings**. ### 3.2 Lifecycle Hooks (`hooks/claude-codex-hooks.json`) ```json { "hooks": [ { "event": "SessionStart", "script": "./hooks/ponytail-activate.js", "description": "Inject ponytail instructions at session start if active" }, { "event": "SubagentStart", "script": "./hooks/ponytail-subagent.js", "description": "Pass ponytail context to subagents" }, { "event": "CommandExecute", "script": "./hooks/ponytail-mode-tracker.js", "description": "Track /ponytail mode switches" } ] } ``` **Tři hook body:** | Event | Script | Co dělá | |-------|--------|---------| | `SessionStart` | `ponytail-activate.js` | Při startu session zkontroluje `.ponytail-active` flag; pokud je aktivní, injectuje instrukce do system promptu | | `SubagentStart` | `ponytail-subagent.js` | Při spuštění subagentu předá ponytail kontext, aby i subagent dodržoval pravidla | | `CommandExecute` | `ponytail-mode-tracker.js` | Zachytává `/ponytail ` příkazy a persistuje mód do flag file | ### 3.3 SessionStart hook (`ponytail-activate.js`) ```javascript const { readMode } = require('./ponytail-runtime'); const { getPonytailInstructions } = require('./ponytail-instructions'); function main() { const mode = readMode(); if (!mode || mode === 'off') return; const instructions = getPonytailInstructions(mode); process.stdout.write(instructions); } main(); ``` **Mechanismus:** 1. Při každém startu Claude Code session se spustí tento skript 2. Přečte `~/.claude/.ponytail-active` (nebo `CLAUDE_CONFIG_DIR`) 3. Pokud je mód `full` nebo `review`, vypíše instrukce na stdout 4. Claude Code tyto instrukce **připojí k system promptu** ### 3.4 SubagentStart hook (`ponytail-subagent.js`) ```javascript const { readMode } = require('./ponytail-runtime'); const { getPonytailInstructions } = require('./ponytail-instructions'); function main() { const mode = readMode(); if (!mode || mode === 'off') { process.stdout.write(JSON.stringify({})); return; } const instructions = getPonytailInstructions(mode); process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'SubagentStart', additionalContext: instructions } })); } main(); ``` **Rozdíl oproti SessionStart:** SubagentStart musí vracet **JSON s `hookSpecificOutput`** — jinak Claude kontext zahodí. ### 3.5 CommandExecute hook (`ponytail-mode-tracker.js`) ```javascript const { setMode, clearMode } = require('./ponytail-runtime'); const { normalizePersistedMode } = require('./ponytail-config'); function main() { const args = process.argv.slice(2); const raw = (args[0] || '').trim().toLowerCase(); const mode = normalizePersistedMode(raw); if (mode === 'off') { clearMode(); console.log('Ponytail mode deactivated.'); return; } if (mode) { setMode(mode); console.log(`Ponytail mode activated: ${mode}`); return; } console.log('Usage: /ponytail [full|review|off]'); } main(); ``` **Příkazy:** - `/ponytail full` — aktivuje plný mód (všechna pravidla) - `/ponytail review` — aktivuje review mód (kontrola diffů) - `/ponytail off` — deaktivuje ### 3.6 Runtime (`ponytail-runtime.js`) ```javascript const STATE_FILE = '.ponytail-active'; const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA); const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA); function setMode(mode) { fs.mkdirSync(path.dirname(statePath), { recursive: true }); fs.writeFileSync(statePath, mode); } function readMode() { try { return fs.readFileSync(statePath, 'utf8').trim() || null; } catch (e) { return null; } } function writeHookOutput(event, mode, context = '') { if (isCodex) { // Codex: systemMessage + hookSpecificOutput JSON process.stdout.write(JSON.stringify({ systemMessage: `PONYTAIL:${mode.toUpperCase()}`, hookSpecificOutput: { hookEventName: event, additionalContext: context } })); return; } // Native Claude: SessionStart = raw stdout, SubagentStart = JSON if (event === 'SubagentStart') { process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: context } })); return; } process.stdout.write(context); } ``` **Klíčové:** Plugin detekuje **runtime prostředí** podle env vars (`PLUGIN_DATA` = Codex, `COPILOT_PLUGIN_DATA` = Copilot) a přizpůsobí output formát. ### 3.7 Instrukce (`ponytail-instructions.js`) ```javascript function getPonytailInstructions(mode = 'full') { const base = fs.readFileSync(path.join(__dirname, '..', 'AGENTS.md'), 'utf8'); if (mode === 'review') { return base + '\n\n# REVIEW MODE\nWhen reviewing diffs, flag any over-engineering...'; } return base; } ``` - **full mód:** Celý `AGENTS.md` (YAGNI, stdlib first, nejmenší řešení) - **review mód:** AGENTS.md + extra sekce pro review diffů ### 3.8 Statusline (`ponytail-statusline.sh`) ```bash flag="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.ponytail-active" [ -f "$flag" ] || exit 0 mode=$(head -n1 "$flag" | tr -d '[:space:]') printf '\033[38;5;108m[PONYTAIL:%s]\033[0m' "$mode" ``` Bash skript pro zobrazení aktivního módu ve statusline (např. v promptu nebo tmux). --- ## 4. Codex CLI integrace ### 4.1 Manifest (`.codex-plugin/plugin.json`) Stejný obsah jako Claude manifest, ale umístěný v `.codex-plugin/`. Codex používá **stejné hooks** (`claude-codex-hooks.json`) — jsou kompatibilní. ### 4.2 Rozdíly oproti Claude V `ponytail-runtime.js`: ```javascript const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA); // Codex dostává JSON output se systemMessage + hookSpecificOutput ``` Codex očekává **strukturovaný JSON output** místo raw textu. Plugin to řeší podmíněným větvením ve `writeHookOutput()`. --- ## 5. Pi coding agent integrace ### 5.1 Plugin (`pi-extension/index.js`) ```javascript const { getPonytailInstructions } = require('../hooks/ponytail-instructions'); const { getDefaultMode, normalizePersistedMode } = require('../hooks/ponytail-config'); const statePath = path.join(os.homedir(), '.config', 'pi', '.ponytail-active'); function readMode() { try { return normalizePersistedMode(fs.readFileSync(statePath, 'utf8').trim()) || getDefaultMode(); } catch (e) { return getDefaultMode(); } } module.exports = { name: 'ponytail', version: '4.8.3', // Hook: před LLM voláním injectuj instrukce do system promptu onPreLLM: async (context) => { const mode = readMode(); if (mode === 'off') return context; const instructions = getPonytailInstructions(mode); context.systemPrompt = context.systemPrompt ? `${context.systemPrompt}\n\n${instructions}` : instructions; return context; }, // Command: /ponytail commands: { ponytail: { description: 'Toggle Ponytail mode (full/review/off)', handler: async (args) => { const mode = normalizePersistedMode(args.trim()) || getDefaultMode(); fs.mkdirSync(path.dirname(statePath), { recursive: true }); fs.writeFileSync(statePath, mode); return `Ponytail mode: ${mode}`; } } } }; ``` ### 5.2 Jak Pi plugin funguje 1. **Pi agent načte plugin** z `pi-extension/index.js` 2. **Před každým LLM voláním** spustí `onPreLLM` hook 3. Hook přečte `~/.config/pi/.ponytail-active` 4. Pokud je mód aktivní, **připojí AGENTS.md instrukce k system promptu** 5. **Slash command `/ponytail`** umožňuje uživateli přepínat mód ### 5.3 Rozdíly oproti Claude Code | Aspekt | Claude Code | Pi | |--------|-------------|-----| | Hook mechanism | Lifecycle hooks (SessionStart, SubagentStart, CommandExecute) | `onPreLLM` — před každým LLM call | | Output formát | Raw stdout nebo JSON podle eventu | Modifikace `context.systemPrompt` | | Subagent support | Explicitní SubagentStart hook | Závisí na Pi interním chování | | Flag file | `~/.claude/.ponytail-active` | `~/.config/pi/.ponytail-active` | | Command registration | Via CommandExecute hook | Via `commands` export | --- ## 6. OpenCode integrace ### 6.1 Plugin (`.opencode/plugins/ponytail.mjs`) ```javascript export default async ({ client } = {}) => { return { // Registrace slash commands + skills directory config: async (config) => { // Načte command/*.md soubory // Přidá skills dir do config.skills.paths }, // Transform system promptu před každým turnem 'experimental.chat.system.transform': async (_input, output) => { const mode = readMode(); if (mode === 'off') return; output.system.push(getPonytailInstructions(mode)); }, // Před zpracováním /ponytail commandu 'command.execute.before': async (input) => { if (input.command !== 'ponytail') return; const mode = normalizePersistedMode((input.arguments || '').trim()); writeMode(mode); } }; }; ``` ### 6.2 OpenCode specifika - Používá **ES modules** (`.mjs`) - `experimental.chat.system.transform` — podobné Pi `onPreLLM`, ale s explicitním `output.system` array - `command.execute.before` — pre-hook pro commandy - Flag file: `~/.config/opencode/.ponytail-active` --- ## 7. Gemini CLI integrace ### 7.1 Manifest (`gemini-extension.json`) ```json { "name": "ponytail", "version": "4.8.3", "description": "Lazy senior dev mode...", "contextFileName": "AGENTS.md" } ``` **Nejjednodušší integrace:** Gemini CLI automaticky načte `AGENTS.md` jako context file. Žádné hooks, žádné runtime skripty — jen **statický ruleset**. --- ## 8. AGENTS.md — jádro rulesetu ```markdown # PONYTAIL — Lazy Senior Developer Mode ## Core Principles 1. **YAGNI** — You Aren't Gonna Need It. Neimplementuj funkci, dokud není explicitně vyžádána. 2. **Stdlib First** — Použij standardní knihovnu jazyka před externími závislostmi. 3. **Native Platform Features** — Preferuj nativní API před wrappery. 4. **Smallest Correct Implementation** — Nejkratší kód který správně řeší problém. 5. **No Unrequested Abstractions** — Žádné factory patterny, žádné premature generalizace. ## Code Style - Imperativní > funkcionální, pokud je imperativní kratší - Inline > extrahované funkce, pokud se funkce nepoužívá vícekrát - Hardcoded > konfigurovatelné, pokud není důvod konfigurovat - Copy-paste > DRY, pokud je abstrakce komplikovanější než duplikace ``` --- ## 9. Skill definice (`skills/ponytail/SKILL.md`) ```markdown # Ponytail Skill ## Description Lazy senior developer mode for coding agents. ## Usage - Activate: /ponytail full - Review mode: /ponytail review - Deactivate: /ponytail off ## Modes - **full**: All ponytail rules active - **review**: Focus on flagging over-engineering in diffs - **off**: Standard agent behavior ``` --- ## 10. Shrnutí — jak se zapojuje do každého agenta | Agent | Mechanismus | Hook body | Persistencia módu | |-------|-------------|-----------|-------------------| | **Claude Code** | Lifecycle hooks (SessionStart, SubagentStart, CommandExecute) | JS skripty co píšou na stdout/JSON | `~/.claude/.ponytail-active` | | **Codex CLI** | Stejné hooks jako Claude, ale JSON output | JS skripty s `systemMessage` | `~/.codex/.ponytail-active` (via PLUGIN_DATA) | | **Pi** | `onPreLLM` hook + `commands` export | Modifikace `context.systemPrompt` | `~/.config/pi/.ponytail-active` | | **OpenCode** | `experimental.chat.system.transform` + `command.execute.before` | Push do `output.system` array | `~/.config/opencode/.ponytail-active` | | **Gemini CLI** | `contextFileName` v manifestu | Žádný — statický context | N/A (vždy aktivní) | --- ## 11. Klíčové technické pozorování 1. **Flag-file pattern** — Všechny platformy kromě Gemini používají **souborový flag** (`.ponytail-active`) pro persistenci módu mezi sessiony. Je to jednoduché, robustní, nezávislé na DB. 2. **Stdout/JSON dualismus** — Claude/Codex hooks musí vracet buď raw text (SessionStart) nebo JSON (SubagentStart, Codex). Plugin to řeší runtime detekcí. 3. **Shared instruction builder** — `ponytail-instructions.js` čte `AGENTS.md` a přidává mód-specifické appendixy. Jedna pravda pro všechny platformy. 4. **Pi je nejjednodušší** — Pi plugin má nejmenší kód, protože Pi API (`onPreLLM`, `commands`) je nejvyšší úroveň abstrakce. 5. **Gemini je nejprimitivnější** — Jen statický context file, žádná dynamika, žádné přepínání módu. 6. **Subagent propagace** — Claude Code explicitně řeší předávání kontextu subagentům (SubagentStart hook). Ostatní platformy to neřeší nebo závisí na interním chování.