runtime zaloha

This commit is contained in:
lachtan
2026-06-30 05:32:46 +02:00
parent 1856b6a866
commit 987bb11c8b
7 changed files with 977 additions and 49 deletions

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