15 KiB
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)
{
"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)
{
"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 <mode> příkazy a persistuje mód do flag file |
3.3 SessionStart hook (ponytail-activate.js)
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:
- Při každém startu Claude Code session se spustí tento skript
- Přečte
~/.claude/.ponytail-active(neboCLAUDE_CONFIG_DIR) - Pokud je mód
fullneboreview, vypíše instrukce na stdout - Claude Code tyto instrukce připojí k system promptu
3.4 SubagentStart hook (ponytail-subagent.js)
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)
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)
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)
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)
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:
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)
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 <mode>
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
- Pi agent načte plugin z
pi-extension/index.js - Před každým LLM voláním spustí
onPreLLMhook - Hook přečte
~/.config/pi/.ponytail-active - Pokud je mód aktivní, připojí AGENTS.md instrukce k system promptu
- Slash command
/ponytailumožň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)
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é PionPreLLM, ale s explicitnímoutput.systemarraycommand.execute.before— pre-hook pro commandy- Flag file:
~/.config/opencode/.ponytail-active
7. Gemini CLI integrace
7.1 Manifest (gemini-extension.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
# 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)
# 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í
-
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. -
Stdout/JSON dualismus — Claude/Codex hooks musí vracet buď raw text (SessionStart) nebo JSON (SubagentStart, Codex). Plugin to řeší runtime detekcí.
-
Shared instruction builder —
ponytail-instructions.jsčteAGENTS.mda přidává mód-specifické appendixy. Jedna pravda pro všechny platformy. -
Pi je nejjednodušší — Pi plugin má nejmenší kód, protože Pi API (
onPreLLM,commands) je nejvyšší úroveň abstrakce. -
Gemini je nejprimitivnější — Jen statický context file, žádná dynamika, žádné přepínání módu.
-
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í.