runtime zaloha
This commit is contained in:
19
AGENTS.md
19
AGENTS.md
@@ -37,6 +37,25 @@ Never use `/tmp/`, hardcoded absolute paths, or in-memory databases for persiste
|
||||
|
||||
The exec safety guard blocks commands without an explicit workspace path (e.g. `lua -e '...'`, `which`). Write scripts to files inside the workspace (e.g. `tmp/script.lua`) and run them with `working_dir` set to the workspace root.
|
||||
|
||||
## File / Code Conventions
|
||||
|
||||
### Report / result files
|
||||
- Save important reports to `results/` directory with descriptive, date-prefixed filenames (e.g., `2026-06-02_remind-skill-analysis-and-improvements.md`)
|
||||
|
||||
### Script-writing convention
|
||||
- Location: always save scripts in the `scripts/` directory.
|
||||
- Language choice:
|
||||
- Extremely short script (a few lines) -> bash.
|
||||
- Longer / non-trivial script -> Python.
|
||||
- Override: if the user explicitly specifies a language or location, their instruction always takes precedence.
|
||||
|
||||
### Temporary files
|
||||
- All temporary files go to `tmp/` directory.
|
||||
- Clean up after tests and one-off operations.
|
||||
|
||||
### Code changes
|
||||
- User prefers changes to be made in a temporary clone under `workspace/tmp/<repo-name>` for review before applying
|
||||
|
||||
## No proactive actions
|
||||
|
||||
When I ask you to **find out**, **investigate**, **look into**, or **check**
|
||||
|
||||
24
USER.md
24
USER.md
@@ -13,8 +13,7 @@
|
||||
- Silver Gym
|
||||
- Pokoj „chlívek" — okno (holubí hřeby), projekt teplotního senzoru
|
||||
- Dítě: Ema
|
||||
- Bětka — řeší pro ni papírování
|
||||
- Kontakt: Přibyl
|
||||
- Žena: Bětka
|
||||
|
||||
## Preferovaný model
|
||||
- Preset: `glm`
|
||||
@@ -26,7 +25,7 @@
|
||||
- Monitoring přes Zabbix
|
||||
- Píše kód i spravuje infrastrukturu, ad-hoc skripty pro Linux
|
||||
- Preferované programovací jazyky: C#, Python, Rust
|
||||
- Skriptovací jazyky: Bash, PowerShell
|
||||
- Skriptovací jazyky: Bash, PowerShell, Python
|
||||
|
||||
## Preferred interaction
|
||||
- Tykání, terse, technicky, bez kytek / bez emojis
|
||||
@@ -59,37 +58,18 @@
|
||||
- Daily reminders at random times within a window (e.g., 8:00–21:00), not at fixed clock times
|
||||
- Prefers single reminder with multiple fire times rather than separate reminders for each time
|
||||
|
||||
## Automation testing
|
||||
- Při nastavování periodických jobů nejprve otestuje s krátkým intervalem, po potvrzení smaže testovací job a nastaví cílový interval
|
||||
- U časových testů očekává výstup s milisekundovou přesností
|
||||
- U nových automatizací preferuje nejprve human-in-the-loop kontrolu; méně důležité úkoly mohou běžet autonomně
|
||||
|
||||
## Zájmy
|
||||
- CLI coding agents — srovnávání, analýza výkonu (zná: Claude Code, Gemini CLI, Qwen Code, Pi-agent, oh-my-pi)
|
||||
- Pro Pi agenta používá AGENTS.md jako primární instrukční soubor, nikoli CLAUDE.md
|
||||
- Linux tools (strace a další)
|
||||
- Filmy
|
||||
- Windows utility
|
||||
- Poslouchá Radio 1 stream
|
||||
- DT GLASS sklenice (z láhve vína) — Universal a Amber varianty z darkove-sklo.com #dt-glass
|
||||
- Chce Coca-Cola sklenice z práce, ideálně dvě
|
||||
|
||||
## Poznámky k organizaci
|
||||
- Organizuje poznámky pomocí hashtagů (#chata, #dt-glass, #devops)
|
||||
- Todo: koupit nové merino triko
|
||||
|
||||
## Co nemá smysl nabízet
|
||||
- Hardware/IoT funkce (MaixCam, GPIO, I2C, sériový port) — běží v LXC bez fyzického HW
|
||||
|
||||
## Životní filozofie
|
||||
- Je mi blízký stoicismus a jeho přístup k životu
|
||||
- Drž se toho, co můžu ovlivnit; zbytek nech být
|
||||
- Když je něco špatně, řekni mi to přímo a bez příkras
|
||||
|
||||
## Prostor / rozměry
|
||||
- Vzdálenost židle–stůl (sedák → deska): 25.5 cm
|
||||
- Deska stolu: 65×140 cm
|
||||
|
||||
## Připomínky
|
||||
- Uživatel odkazuje připomínky zkráceným názvem, který nemusí substring-matchnout uložený text (např. "wifi do tiskarny" vs "WiFi modul do 3D tiskarny")
|
||||
- Cíluje připomínky přímo přes `#<id>` syntax
|
||||
|
||||
@@ -1 +1 @@
|
||||
372
|
||||
379
|
||||
@@ -4,8 +4,6 @@ This file stores important information that should persist across sessions.
|
||||
|
||||
## Project Context
|
||||
|
||||
- Both user and assistant run on the same nanobot Docker image
|
||||
- Ollama cloud subscription: na požádání zobrazit aktuální využití (usage)
|
||||
- nanobot podporuje cross-channel session continuity přes `unifiedSession: true` v `config.json` pod `agents.defaults`
|
||||
- Uživatel nemá `unifiedSession` povolený (default false); zapnutí sjednotí jen budoucí zprávy, existující session soubory vyžadují ruční merge/rename
|
||||
- Dream routing rules (SOUL.md → personality, USER.md → user profile, MEMORY.md → project knowledge) are hardcoded in Dream prompt, not user-configurable beyond interval/model/batch size
|
||||
@@ -14,30 +12,6 @@ This file stores important information that should persist across sessions.
|
||||
- User wants to try writing a nanobot extension in TypeScript
|
||||
- Pi agent načítá všechny AGENTS.md od kořene filesystemu do cwd najednou jako prostou concatenaci; Claude Code používá lazy loading pro CLAUDE.md v podadresářích a podmíněná pravidla v `.claude/rules/*.md` s atributem `paths`
|
||||
- Zálohy: wood.hell → pivo.hell (rekurzivní)
|
||||
- „giga" zařízení k integraci do Zabbix monitoringu
|
||||
|
||||
## Agent Model Selection
|
||||
- Prioritizes agentic performance, correct tool calling, and overall result quality
|
||||
- Conservative fallback nanobot agent model: DeepSeek V3.2:cloud
|
||||
- OpenRouter is pay-per-token alternative to Ollama subscription for model access
|
||||
- Gemini Flash via Google AI Studio Free Tier: 15 RPM limit — unusable for agent work; only viable for simple prompts without tool calls
|
||||
- Gemini Flash via Google AI Studio Tier 1 (paid): 360+ RPM
|
||||
- Haiku (Claude) via OpenRouter: high rate limits, viable agent model alternative
|
||||
- Gemini Flash Lite via OpenRouter: high rate limits, viable agent model alternative
|
||||
- Kimi K2.6 has known bug with random switching to Chinese output (reported by Cursor and Reddit users) — critical risk for Czech use
|
||||
- minimax-m3:cloud is blocked for nanobot agent deployment due to empty tool result responses (ollama/ollama #16389)
|
||||
- deepseek-v4-pro:cloud is blocked for interactive nanobot agent use due to 15.4 tok/s and 57s TTFT
|
||||
- deepseek-v4-flash:cloud is a viable nanobot agent alternative with 1M ctx, MIT license, and ~30-50 tok/s speed
|
||||
- qwen3.5:397b-cloud is a viable nanobot agent alternative with multimodal support, 1M ctx, 201 languages including Czech, but has speed and accuracy tradeoffs
|
||||
- devstral-2:123b-cloud is a viable nanobot agent alternative with Terminal-Bench 77.3%, coding-only focus, and 128K ctx limit
|
||||
- User is interested in switching to GLM-5.2:cloud as primary nanobot agent model if/when it becomes available on Ollama Cloud
|
||||
- User prefers Qwen model for deep research tasks (not currently in presets)
|
||||
- Available model presets: gemini-flash, gemini-flash-lite, glm, haiku, kimi, minimax, nemotron-3-super, sonnet
|
||||
- Model specs: `nemotron-3-super` ctx 262144 / max output 16384; `gemini-flash` ctx 256000 / max output 16384
|
||||
|
||||
### NuGet package caching
|
||||
- Evaluating NuGet package hosting/caching on Linux
|
||||
- Preferred solution: BaGetter (bagetter/BaGetter) — active BaGet fork with multiple upstream mirror support (PR #269), solves original BaGet's single-upstream limitation
|
||||
|
||||
## Runtime / Deployment
|
||||
- Installed as a `uv` tool: package `nanobot-ai`, update via `uv tool upgrade nanobot-ai`
|
||||
|
||||
File diff suppressed because one or more lines are too long
493
results/2026-06-28_ponytail-claude-pi-deepdive.md
Normal file
493
results/2026-06-28_ponytail-claude-pi-deepdive.md
Normal file
@@ -0,0 +1,493 @@
|
||||
# Ponytail Plugin — Deep-dive: Claude Code a Pi Coding Agent
|
||||
|
||||
**Zdroj:** https://github.com/DietrichGebert/ponytail
|
||||
**Verze:** 4.8.3 | **Licence:** MIT
|
||||
**Analýza vyčtená přímo ze zdrojových kódů**
|
||||
|
||||
---
|
||||
|
||||
## 1. Claude Code — jak se plugin zapojuje
|
||||
|
||||
### 1.1 Manifest (`.claude-plugin/plugin.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ponytail",
|
||||
"version": "4.8.3",
|
||||
"description": "Lazy senior dev mode...",
|
||||
"author": { "name": "Dietrich Gebert", "url": "..." },
|
||||
"hooks": "./hooks/claude-codex-hooks.json"
|
||||
}
|
||||
```
|
||||
|
||||
**Jediné klíčové pole:** `hooks` → ukazuje na JSON soubor s definicemi lifecycle hook bindings. Claude Code při načtení pluginu přečte tento manifest a zaregistruje hooky.
|
||||
|
||||
### 1.2 Hook bindings (`hooks/claude-codex-hooks.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup|resume|clear|compact",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-activate.js\"; exit 0",
|
||||
"timeout": 5,
|
||||
"statusMessage": "Loading ponytail mode..."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SubagentStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-subagent.js\"; exit 0",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-mode-tracker.js\"; exit 0",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tři hook body, každý spouští Node.js skript:**
|
||||
|
||||
| Hook | Kdy se spouští | Co dělá |
|
||||
|------|---------------|---------|
|
||||
| `SessionStart` | Při `startup`, `resume`, `clear`, `compact` | Aktivuje mód, zapíše flag file, emituje instrukce do system promptu |
|
||||
| `SubagentStart` | Při spuštění subagentu (Task tool) | Předá ponytail kontext subagentu |
|
||||
| `UserPromptSubmit` | Při každém user promptu | Detekuje `/ponytail` commandy, přepíná mód |
|
||||
|
||||
**Důležité:** `exit 0` na konci každého commandu — hook musí skončit úspěšně, jinak Claude považuje celý hook za failed.
|
||||
|
||||
---
|
||||
|
||||
### 1.3 SessionStart hook (`ponytail-activate.js`) — 91 řádků
|
||||
|
||||
**Flow:**
|
||||
|
||||
1. **Načte default mód** z `ponytail-config.js` (env var > config file > `'full'`)
|
||||
2. **Pokud `off`:** vymaže flag file, vypíše `'OK'` (nebo prázdný string pro Codex), exit
|
||||
3. **Zapíše flag file** `~/.claude/.ponytail-active` s aktuálním módem
|
||||
4. **Načte instrukce** z `SKILL.md` přes `ponytail-instructions.js`, filtruje podle módu
|
||||
5. **Detekuje statusline** — kontroluje `~/.claude/settings.json` zda existuje `statusLine` config; pokud ne, připojí do outputu nudge pro uživatele
|
||||
6. **Vypíše instrukce na stdout** — Claude Code stdout zachytí a **připojí k system promptu**
|
||||
|
||||
**Klíčový kód (zkráceně):**
|
||||
|
||||
```javascript
|
||||
const mode = getDefaultMode(); // 'full' default
|
||||
|
||||
if (mode === 'off') {
|
||||
clearMode();
|
||||
writeHookOutput('SessionStart', 'off', isCodex ? '' : 'OK');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
setMode(mode); // zapíše ~/.claude/.ponytail-active
|
||||
let output = getPonytailInstructions(mode); // načte SKILL.md, filtruje
|
||||
|
||||
// Detekce chybějícího statusline
|
||||
if (!hasStatusline) {
|
||||
output += "\n\nSTATUSLINE SETUP NEEDED: ...";
|
||||
}
|
||||
|
||||
writeHookOutput('SessionStart', mode, output); // → stdout → Claude system prompt
|
||||
```
|
||||
|
||||
**Stdout capture:** Claude spustí skript jako shell command, přečte stdout a **připojí obsah k system promptu** jako hidden context. Uživatel to nevidí, ale LLM to vidí jako instrukce.
|
||||
|
||||
---
|
||||
|
||||
### 1.4 SubagentStart hook (`ponytail-subagent.js`) — 22 řádků
|
||||
|
||||
**Proč existuje:** SessionStart context je **parent-thread only** a nikdy nedosáhne subagentů (issue #252 v ponytail repo). Bez tohoto hooku by každý Task-spawned agent běžel bez ponytail pravidel.
|
||||
|
||||
**Flow:**
|
||||
|
||||
1. Přečte mód z flag file (`readMode()`)
|
||||
2. Pokud `off` nebo chybí flag — exit, nic neinjectuje
|
||||
3. Načte instrukce pro daný mód
|
||||
4. Vypíše **JSON s `hookSpecificOutput`** — Claude SubagentStart vyžaduje tento formát
|
||||
|
||||
```javascript
|
||||
const mode = readMode();
|
||||
if (!mode || mode === 'off') process.exit(0);
|
||||
|
||||
writeHookOutput('SubagentStart', mode, getPonytailInstructions(mode));
|
||||
```
|
||||
|
||||
**Rozdíl oproti SessionStart:** SubagentStart **musí** vracet JSON:
|
||||
```json
|
||||
{"hookSpecificOutput": {"hookEventName": "SubagentStart", "additionalContext": "...instrukce..."}}
|
||||
```
|
||||
|
||||
Pokud by vrátil raw text (jako SessionStart), Claude by kontext zahodil.
|
||||
|
||||
---
|
||||
|
||||
### 1.5 UserPromptSubmit hook (`ponytail-mode-tracker.js`) — 55 řádků
|
||||
|
||||
**Flow:**
|
||||
|
||||
1. Čte **stdin jako JSON** (`{"prompt": "..."}`) — Claude předává user prompt hookům přes stdin
|
||||
2. Parsuje prompt, hledá `/ponytail [lite|full|ultra|off]` nebo `/ponytail-review`
|
||||
3. Při matchi zapíše/maže flag file a emituje potvrzení
|
||||
4. Detekuje deaktivační commandy: `"stop ponytail"` nebo `"normal mode"` (celá zpráva, ne substring)
|
||||
|
||||
```javascript
|
||||
process.stdin.on('end', () => {
|
||||
const data = JSON.parse(input.replace(/^\uFEFF/, '')); // strip BOM
|
||||
const prompt = (data.prompt || '').trim().toLowerCase();
|
||||
|
||||
if (/^[/@$]ponytail/.test(prompt)) {
|
||||
const parts = prompt.split(/\s+/);
|
||||
const cmd = parts[0].replace(/^[@$]/, '/');
|
||||
const arg = parts[1] || '';
|
||||
|
||||
if (cmd === '/ponytail-review') mode = 'review';
|
||||
else if (cmd === '/ponytail') {
|
||||
if (arg === 'lite') mode = 'lite';
|
||||
else if (arg === 'full') mode = 'full';
|
||||
else if (arg === 'ultra') mode = 'ultra';
|
||||
else if (arg === 'off') mode = 'off';
|
||||
else mode = getDefaultMode();
|
||||
}
|
||||
|
||||
if (mode && mode !== 'off') {
|
||||
setMode(mode);
|
||||
writeHookOutput('UserPromptSubmit', mode, 'PONYTAIL MODE CHANGED — level: ' + mode);
|
||||
} else if (mode === 'off') {
|
||||
clearMode();
|
||||
writeHookOutput('UserPromptSubmit', 'off', 'PONYTAIL MODE OFF');
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Důležité:** `UserPromptSubmit` hook běží **před** zpracováním promptu LLM. Pokud detekuje `/ponytail` command, zapíše flag file a emituje potvrzení — ale samotný `/ponytail` text pak jde do LLM jako normální prompt. LLM vidí potvrzení změny módu a reaguje na něj.
|
||||
|
||||
---
|
||||
|
||||
### 1.6 Runtime (`ponytail-runtime.js`) — 68 řádků
|
||||
|
||||
**Flag file pattern:**
|
||||
- **Cesta:** `~/.claude/.ponytail-active` (nebo `$CLAUDE_CONFIG_DIR/.ponytail-active`)
|
||||
- **Obsah:** prostý text — `'lite'`, `'full'`, `'ultra'`, `'review'`, nebo chybí = off
|
||||
|
||||
**Output formáty podle runtime:**
|
||||
|
||||
```javascript
|
||||
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA);
|
||||
const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA);
|
||||
|
||||
function writeHookOutput(event, mode, context) {
|
||||
if (isCopilot) {
|
||||
// Copilot: JSON s additionalContext na SessionStart
|
||||
process.stdout.write(JSON.stringify(
|
||||
event === 'SessionStart' && context ? { additionalContext: context } : {}));
|
||||
return;
|
||||
}
|
||||
if (isCodex) {
|
||||
// Codex: JSON se systemMessage + hookSpecificOutput
|
||||
const output = { systemMessage: `PONYTAIL:${mode.toUpperCase()}` };
|
||||
if (context) output.hookSpecificOutput = { hookEventName: event, additionalContext: context };
|
||||
process.stdout.write(JSON.stringify(output));
|
||||
return;
|
||||
}
|
||||
// Native Claude:
|
||||
if (event === 'SubagentStart') {
|
||||
// SubagentStart vyžaduje JSON formát
|
||||
process.stdout.write(JSON.stringify(
|
||||
{ hookSpecificOutput: { hookEventName: event, additionalContext: context } }));
|
||||
return;
|
||||
}
|
||||
// SessionStart / UserPromptSubmit: raw stdout
|
||||
process.stdout.write(context);
|
||||
}
|
||||
```
|
||||
|
||||
**Detekce runtime:**
|
||||
- `PLUGIN_DATA` env var = Codex CLI
|
||||
- `COPILOT_PLUGIN_DATA` env var = GitHub Copilot
|
||||
- Nic z toho = native Claude Code
|
||||
|
||||
---
|
||||
|
||||
### 1.7 Instrukce builder (`ponytail-instructions.js`) — 94 řádků
|
||||
|
||||
**Jedna pravda pro všechny platformy:**
|
||||
|
||||
1. Čte `skills/ponytail/SKILL.md` (relativní cesta `../skills/ponytail/SKILL.md`)
|
||||
2. Odstraní YAML frontmatter (`---...---`)
|
||||
3. **Filtruje podle módu:**
|
||||
- Tabulky: řádky začínající `| **mode** |` se ponechají jen pro aktivní mód
|
||||
- Worked examples: řádky `- mode: ...` se ponechají jen pro aktivní mód
|
||||
- Ostatní řádky (obecná pravidla) se ponechají vždy
|
||||
4. Pro `review` mód vrací jiný text: `'PONYTAIL MODE ACTIVE — level: review. Behavior defined by /ponytail-review skill.'`
|
||||
5. Fallback: pokud `SKILL.md` neexistuje, generuje inline instrukce (`getFallbackInstructions()`)
|
||||
|
||||
```javascript
|
||||
function getPonytailInstructions(mode) {
|
||||
const configuredMode = normalizePersistedMode(mode) || DEFAULT_MODE;
|
||||
|
||||
if (INDEPENDENT_MODES.has(configuredMode)) { // 'review'
|
||||
return 'PONYTAIL MODE ACTIVE — level: ' + configuredMode + '. Behavior defined by /ponytail-' + configuredMode + ' skill.';
|
||||
}
|
||||
|
||||
try {
|
||||
return 'PONYTAIL MODE ACTIVE — level: ' + effectiveMode + '\n\n' +
|
||||
filterSkillBodyForMode(fs.readFileSync(SKILL_PATH, 'utf8'), effectiveMode);
|
||||
} catch (e) {
|
||||
return getFallbackInstructions(effectiveMode); // fallback když SKILL.md chybí
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.8 Konfigurace (`ponytail-config.js`) — 122 řádků
|
||||
|
||||
**Resolution order default módu:**
|
||||
1. `PONYTAIL_DEFAULT_MODE` env var
|
||||
2. `$XDG_CONFIG_HOME/ponytail/config.json` nebo `~/.config/ponytail/config.json`
|
||||
3. `'full'`
|
||||
|
||||
**Validní módy:** `off`, `lite`, `full`, `ultra`, `review`
|
||||
|
||||
**Deaktivace:** `isDeactivationCommand(text)` matchuje celou zprávu `"stop ponytail"` nebo `"normal mode"` (ignoruje case a trailing punctuation). Důvod: dřívější verze matchovaly substring, což deaktivovalo mód u běžných requestů jako "add a normal mode toggle".
|
||||
|
||||
---
|
||||
|
||||
## 2. Pi Coding Agent — jak se plugin zapojuje
|
||||
|
||||
### 2.1 Plugin entry point (`pi-extension/index.js`) — 189 řádků, ESM
|
||||
|
||||
```javascript
|
||||
import { createRequire } from "node:module";
|
||||
const require = createRequire(import.meta.url);
|
||||
// Import sdílených modulů z hooks/
|
||||
const { getDefaultMode, normalizePersistedMode, ... } = require("../hooks/ponytail-config.js");
|
||||
const { getPonytailInstructions, filterSkillBodyForMode } = require("../hooks/ponytail-instructions.js");
|
||||
|
||||
export default function ponytailExtension(pi) {
|
||||
let currentMode = DEFAULT_MODE;
|
||||
let configuredDefaultMode = getDefaultMode();
|
||||
let isActive = false;
|
||||
let lastCtx = null;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Pi používá stejné sdílené moduly jako Claude** (`ponytail-config.js`, `ponytail-instructions.js`) — jedna pravda pro obě platformy.
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Command registrace
|
||||
|
||||
Pi má **explicitní API** pro commandy — nikoli hooky jako Claude:
|
||||
|
||||
```javascript
|
||||
pi.registerCommand("ponytail", {
|
||||
description: "Set or report Ponytail mode",
|
||||
handler: async (args, ctx) => {
|
||||
const parsed = parsePonytailCommand(args, configuredDefaultMode);
|
||||
|
||||
if (parsed.type === "status") {
|
||||
ctx?.ui?.notify?.(`Ponytail: current ${currentMode} • default ${configuredDefaultMode}`, "info");
|
||||
return;
|
||||
}
|
||||
if (parsed.type === "set-default") {
|
||||
writeDefaultMode(parsed.mode); // zapíše do ~/.config/ponytail/config.json
|
||||
configuredDefaultMode = getDefaultMode();
|
||||
return;
|
||||
}
|
||||
if (parsed.type === "set-mode") {
|
||||
setMode(parsed.mode, ctx); // změní currentMode, zapíše do session entries
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Podporované argumenty:**
|
||||
- `/ponytail` → toggle (off → full, jinak default)
|
||||
- `/ponytail lite|full|ultra|off` → set mód
|
||||
- `/ponytail status` → zobrazí current + default
|
||||
- `/ponytail default <mode>` → nastaví default mód do config file
|
||||
|
||||
**Skill aliasy:**
|
||||
```javascript
|
||||
pi.registerCommand("ponytail-review", {
|
||||
handler: (_args, ctx) => sendAlias("/skill:ponytail-review", "", ctx)
|
||||
});
|
||||
// + ponytail-audit, ponytail-gain, ponytail-debt, ponytail-help
|
||||
```
|
||||
|
||||
`sendAlias()` posílá zprávu jako user message — buď okamžitě (pokud agent idle) nebo jako follow-up (pokud agent běží).
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Event hooks (`pi.on()`)
|
||||
|
||||
Pi používá **event-driven API** místo Claude lifecycle hooks:
|
||||
|
||||
| Event | Kdy se spouští | Co dělá |
|
||||
|-------|---------------|---------|
|
||||
| `input` | Při každém user inputu | Detekuje `"stop ponytail"` / `"normal mode"`, deaktivuje |
|
||||
| `session_start` | Při startu session | Načte mód z session historie nebo defaultu, sync status bar |
|
||||
| `agent_start` | Když agent začne pracovat | `isActive = true`, sync status bar |
|
||||
| `agent_end` | Když agent skončí | `isActive = false`, sync status bar |
|
||||
| `before_agent_start` | **Před LLM voláním** | **Injectuje instrukce do systemPrompt** |
|
||||
|
||||
**Klíčový hook — `before_agent_start`:**
|
||||
|
||||
```javascript
|
||||
pi.on("before_agent_start", async (event) => {
|
||||
if (!currentMode || currentMode === "off") return;
|
||||
return { systemPrompt: `${event.systemPrompt}\n\n${getPonytailInstructions(currentMode)}` };
|
||||
});
|
||||
```
|
||||
|
||||
**Rozdíl oproti Claude:** Pi **explicitně modifikuje** `systemPrompt` a **vrací ho** jako return value. Claude zachytává stdout a připojuje ho interně.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Persistencia módu v Pi
|
||||
|
||||
Pi **nepoužívá flag file** jako Claude. Místo toho:
|
||||
|
||||
1. **Session entries:** `pi.appendEntry("ponytail-mode", { mode: normalized })` — zapisuje mód do session logu
|
||||
2. **Session restore:** `resolveSessionMode(entries, fallback)` — při `session_start` čte historii entries typu `"ponytail-mode"` od konce a najde poslední uložený mód
|
||||
3. **Default mód:** `getDefaultMode()` ze sdíleného configu (env var > `~/.config/ponytail/config.json` > `'full'`)
|
||||
|
||||
```javascript
|
||||
export function resolveSessionMode(entries, fallbackMode = DEFAULT_MODE) {
|
||||
if (!Array.isArray(entries)) return fallback;
|
||||
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
||||
const entry = entries[i];
|
||||
if (entry?.type !== "custom" || entry?.customType !== "ponytail-mode") continue;
|
||||
const mode = normalizePersistedMode(entry?.data?.mode);
|
||||
if (mode) return mode;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
```
|
||||
|
||||
**Proč ne flag file?** Pi agent má vlastní session management (`sessionManager.getBranch()`, `sessionManager.getEntries()`). Plugin využívá nativní Pi API místo externího souboru.
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Status bar
|
||||
|
||||
```javascript
|
||||
function syncStatus(ctx) {
|
||||
if (!c?.ui?.setStatus || !c.ui.theme?.fg) return;
|
||||
const levelIcons = { lite: "🌿", full: "⚡", ultra: "🔥" };
|
||||
const icon = levelIcons[currentMode] || "";
|
||||
const label = currentMode.toUpperCase();
|
||||
const indicator = isActive ? theme.fg("accent", "●") : theme.fg("dim", "○");
|
||||
c.ui.setStatus("ponytail", indicator + " 🐴 " + theme.fg("muted", "ponytail: ") + theme.fg("text", icon + " " + label));
|
||||
}
|
||||
```
|
||||
|
||||
Pi má **nativní status bar API** (`ui.setStatus`). Claude nemá — proto potřebuje externí bash skript (`ponytail-statusline.sh`) který se musí ručně nakonfigurovat v `settings.json`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Srovnání: Claude Code vs Pi
|
||||
|
||||
| Aspekt | Claude Code | Pi Coding Agent |
|
||||
|--------|-------------|-----------------|
|
||||
| **Plugin API** | Manifest JSON + lifecycle hooks | `pi.registerCommand()` + `pi.on()` events |
|
||||
| **Prompt injection** | Stdout capture — skript píše na stdout, Claude připojí k system promptu | `before_agent_start` event — explicitní modifikace `systemPrompt` |
|
||||
| **Subagent support** | `SubagentStart` hook — explicitní předání kontextu | Závisí na Pi interním chování (není explicitní SubagentStart) |
|
||||
| **Command parsing** | `UserPromptSubmit` hook parsuje stdin JSON, detekuje `/ponytail` | `pi.registerCommand("ponytail", ...)` — nativní command API |
|
||||
| **Persistencia módu** | Flag file `~/.claude/.ponytail-active` | Session entries (`pi.appendEntry`) + config file |
|
||||
| **Status indikace** | Externí bash skript (`ponytail-statusline.sh`) konfigurovaný v `settings.json` | Nativní `ui.setStatus()` API |
|
||||
| **Deaktivace** | `"stop ponytail"` / `"normal mode"` via `UserPromptSubmit` hook | `pi.on("input", ...)` detekuje stejné fráze |
|
||||
| **Output formát** | Raw stdout (SessionStart) nebo JSON (SubagentStart) | Return value `{ systemPrompt: "..." }` |
|
||||
| **Runtime detekce** | Env vars (`PLUGIN_DATA`, `COPILOT_PLUGIN_DATA`) | Není potřeba — Pi má vlastní API |
|
||||
| **Sdílené kódy** | `ponytail-config.js`, `ponytail-instructions.js` | Stejné + `ponytail-config.js`, `ponytail-instructions.js` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Klíčové technické detaily
|
||||
|
||||
### 4.1 Stdout vs explicitní API
|
||||
|
||||
**Claude:** Hook skripty jsou **externí procesy**. Claude spustí `node script.js`, přečte stdout a interně připojí k promptu. Skript nemá přímý přístup k Claude interním strukturám.
|
||||
|
||||
**Pi:** Plugin běží **v rámci Pi procesu** jako ESM modul. Má přímý přístup k `pi` objektu a může volat `pi.on()`, `pi.registerCommand()`, modifikovat `systemPrompt`.
|
||||
|
||||
### 4.2 Flag file vs session entries
|
||||
|
||||
**Claude:** Používá **souborový flag** (`~/.claude/.ponytail-active`) protože:
|
||||
- Hook skripty běží jako samostatné procesy — nemají přístup ke Claude session state
|
||||
- Flag file je jediný způsob, jak předat stav mezi SessionStart a SubagentStart hooky
|
||||
- Jednoduché, robustní, nezávislé na DB
|
||||
|
||||
**Pi:** Používá **session entries** (`pi.appendEntry`) protože:
|
||||
- Pi má nativní session management API
|
||||
- Entries se persistují v rámci session a jsou dostupné při restore
|
||||
- Lepší integrace s Pi architekturou
|
||||
|
||||
### 4.3 BOM handling
|
||||
|
||||
Oba systémy řeší UTF-8 BOM (Byte Order Mark) který některé editory přidávají na začátek souborů:
|
||||
|
||||
```javascript
|
||||
// Claude: v mode-tracker.js (stdin JSON)
|
||||
const data = JSON.parse(input.replace(/^\uFEFF/, ''));
|
||||
|
||||
// Claude: v activate.js (settings.json)
|
||||
const raw = fs.readFileSync(settingsPath, 'utf8').replace(/^\uFEFF/, '');
|
||||
```
|
||||
|
||||
BOM breakuje `JSON.parse()` — proto se explicitně stripuje.
|
||||
|
||||
### 4.4 Silent fail philosophy
|
||||
|
||||
Celý plugin je navržený s **silent fail** přístupem:
|
||||
|
||||
```javascript
|
||||
try {
|
||||
setMode(mode);
|
||||
} catch (e) {
|
||||
// Silent fail -- flag is best-effort, don't block the hook
|
||||
}
|
||||
|
||||
try {
|
||||
writeHookOutput('SessionStart', mode, output);
|
||||
} catch (e) {
|
||||
// Silent fail — stdout closed/EPIPE at hook exit must not surface as a hook failure
|
||||
}
|
||||
```
|
||||
|
||||
Důvod: hook failure by zastavila celou session. Plugin je best-effort — když selže, Claude běží normálně bez ponytail.
|
||||
|
||||
### 4.5 Shell safety
|
||||
|
||||
```javascript
|
||||
function isShellSafe(p) {
|
||||
return typeof p === 'string' && /^[A-Za-z0-9 _.\-:/\\~]+$/.test(p);
|
||||
}
|
||||
```
|
||||
|
||||
Před vložením cesty pluginu do shell commandu (statusline config) se kontroluje, že cesta neobsahuje shell metacharacters. Pokud ano, nudge se změní na manuální setup místo embeddovaného command snippetu.
|
||||
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