Files
nanobot-runtime/results/2026-06-28_ponytail-plugin-analysis.md
2026-06-30 05:32:46 +02:00

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:

  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)

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

  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)

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)

{
  "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í

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