runtime zaloha
This commit is contained in:
455
results/2026-06-28_ponytail-plugin-analysis.md
Normal file
455
results/2026-06-28_ponytail-plugin-analysis.md
Normal file
@@ -0,0 +1,455 @@
|
||||
# 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 <mode>` 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 <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`)
|
||||
|
||||
```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í.
|
||||
Reference in New Issue
Block a user