Compare commits

..
Author SHA1 Message Date
EmerikoandClaude Opus 4.8 fb2a45db51 Use an announcement banner instead of the badge
Replace the flat orange badge with a clickable terminal-style banner
image: the "he's building" sticker, a bold headline, and an orange
JOIN THE WAITLIST call to action. Localized banners for EN/ES/KO.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 19:02:41 +02:00
DietrichGebertandClaude Opus 4.8 7086abecc2 Make the waitlist teaser pop and translate it (#314)
The blue NOTE callout was too quiet. Replace it with a centered bold
line plus a bright orange "join the waitlist" badge (matches the site),
and add the same banner to the Spanish and Korean READMEs.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:13:53 +02:00
DietrichGebertandClaude Opus 4.8 4e5f9ccc6c Add a waitlist teaser to the README (#312)
Promotes the pre-launch waitlist at ponytail.dev/soon with a NOTE
callout just under the header.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 18:05:42 +02:00
DietrichGebertandClaude Opus 4.8 a945778b4a docs: add Star History chart to end of READMEs (#294)
Appends a star-history.com chart (light/dark via <picture>) to the end of
the English, Spanish, and Korean READMEs. Heading follows each file's
convention: translated in Spanish ("Historial de estrellas"), English
elsewhere.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 15:37:06 +02:00
DietrichGebertandClaude Opus 4.8 6cd0c42e86 docs: add Trendshift daily and weekly badges to READMEs (#293)
Adds the daily and weekly Trendshift badges to the header badge area of
the English, Spanish, and Korean READMEs. Alt text decoded from %2F to a
plain slash for screen readers.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 15:30:26 +02:00
DietrichGebert 025da371cd release: 4.8.3 (#286) 2026-06-24 04:32:12 +02:00
b9fa564429 feat: inject ponytail ruleset into subagents via SubagentStart hook (#254)
* feat: inject ponytail ruleset into subagents via SubagentStart hook

SessionStart additionalContext is parent-thread only, so every Task-spawned
agent ran ponytail-unaware. Add a SubagentStart hook that injects the active
ruleset into each subagent, reusing getPonytailInstructions. Native Claude
needs the hookSpecificOutput JSON form (not raw stdout), so writeHookOutput
grows a SubagentStart branch; readMode exposes the live flag.

Workflow- and team-spawned coverage is undocumented upstream; verify in a
fresh session once installed.

Closes #252

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1dMag33yz1Kf1jS24Aira

* fix: address QA round 1 — make hook tests hermetic + cover Codex SubagentStart

QA round 1 (panel + deepseek-v4-pro) found two real test issues:
- The new subagent block (and the pre-existing claudeEnv block) used a no-op
  `delete env.PLUGIN_DATA`; run() spreads process.env, so a PLUGIN_DATA /
  COPILOT_PLUGIN_DATA leaked from the shell would steer writeHookOutput into the
  codex/copilot branch and silently mis-fire the native-Claude assertions.
  Fixed at the source: neutralize both vars once at the top, like CLAUDE_CONFIG_DIR.
- The Codex SubagentStart branch (claude-codex-hooks.json is shared by both plugin
  manifests) had zero coverage. Added a codex-path assertion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1dMag33yz1Kf1jS24Aira

* fix: use PowerShell-safe command form for SubagentStart hook

Match the post-#265 'node ...; exit 0' form used by the sibling hooks. The old 'command -v node ... || exit 0' form fails tests/hooks-windows.test.js (POSIX-guard and non-blocking asserts) once this branch merges onto current main.

---------

Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
2026-06-24 04:19:14 +02:00
9d0118df34 docs: add Korean README translation (#283)
* docs: add Korean README translation

Add README.ko.md following the README.es.md convention (community
translation note, English as canonical) and link it from README.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(ko): sync translation with current README

Bring README.ko.md up to current English: npm install for OpenCode
(@dietrichgebert/ponytail) + npm badge, drop the obsolete ln -sf command-symlink
note (the plugin self-registers commands since #197), add the Swival section and
the Codex two-prompt install note.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
2026-06-24 03:47:31 +02:00
DietrichGebert a0766a396d docs: npm install + badge for OpenCode, drop obsolete symlink note (#285)
The plugin registers the /ponytail commands itself now (#197), so the manual
ln -sf workaround is obsolete. Add the npm install snippet + version badge,
and bring README.es.md's OpenCode section up to parity with the English one.
2026-06-24 03:30:46 +02:00
DietrichGebert 17e277387d release: 4.8.2 (#284) 2026-06-24 03:19:28 +02:00
DietrichGebert 7d303b7175 ci: publish via npm trusted publishing (OIDC), drop NPM_TOKEN (#282)
Authenticate the release workflow through GitHub OIDC instead of a long-lived
npm token: no secret to leak or rotate, provenance attached automatically.
Upgrade npm on the runner since OIDC publishing needs npm >= 11.5.1 (Node 22
ships npm 10).
2026-06-24 02:58:03 +02:00
DietrichGebert e368c48c52 feat: scope npm package as @dietrichgebert/ponytail (#280)
Publish under a scope you own outright instead of opencode-ponytail (which
sits on a third-party npm account). Matches the GitHub handle and repo.
publishConfig.access is already public, which scoped public packages need.
2026-06-24 02:50:42 +02:00
17 changed files with 450 additions and 16 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ponytail", "name": "ponytail",
"version": "4.8.1", "version": "4.8.3",
"description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.", "description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.",
"author": { "author": {
"name": "Dietrich Gebert", "name": "Dietrich Gebert",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ponytail", "name": "ponytail",
"version": "4.8.1", "version": "4.8.3",
"description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.", "description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.",
"author": { "author": {
"name": "Dietrich Gebert", "name": "Dietrich Gebert",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "ponytail", "name": "ponytail",
"description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.", "description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.",
"version": "4.8.1", "version": "4.8.3",
"author": { "author": {
"name": "Dietrich Gebert", "name": "Dietrich Gebert",
"url": "https://github.com/DietrichGebert" "url": "https://github.com/DietrichGebert"
+5 -4
View File
@@ -17,7 +17,8 @@ jobs:
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '22' node-version: '22'
registry-url: 'https://registry.npmjs.org' # Trusted publishing (OIDC) needs npm >= 11.5.1; Node 22 ships npm 10.
- run: npm publish --provenance --access public - run: npm install -g npm@latest
env: # No token: id-token: write above lets npm authenticate via OIDC, and
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # provenance is attached automatically. access set in publishConfig.
- run: npm publish
+27 -1
View File
@@ -14,10 +14,16 @@
<p align="center"> <p align="center">
<img src="https://img.shields.io/github/stars/DietrichGebert/ponytail?style=flat-square&color=111111&label=stars" alt="Stars"> <img src="https://img.shields.io/github/stars/DietrichGebert/ponytail?style=flat-square&color=111111&label=stars" alt="Stars">
<img src="https://img.shields.io/github/v/release/DietrichGebert/ponytail?style=flat-square&color=111111&label=release" alt="Release"> <img src="https://img.shields.io/github/v/release/DietrichGebert/ponytail?style=flat-square&color=111111&label=release" alt="Release">
<img src="https://img.shields.io/npm/v/@dietrichgebert/ponytail?style=flat-square&color=111111&label=npm" alt="npm">
<img src="https://img.shields.io/badge/funciona%20con-14%20agentes-111111?style=flat-square" alt="Works with 14 agents"> <img src="https://img.shields.io/badge/funciona%20con-14%20agentes-111111?style=flat-square" alt="Works with 14 agents">
<img src="https://img.shields.io/badge/licencia-MIT-111111?style=flat-square" alt="MIT license"> <img src="https://img.shields.io/badge/licencia-MIT-111111?style=flat-square" alt="MIT license">
</p> </p>
<p align="center">
<a href="https://trendshift.io/repositories/50668" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/50668/daily" alt="DietrichGebert/ponytail | Trendshift" width="250" height="55"/></a>
<a href="https://trendshift.io/repositories/50668" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/50668/weekly" alt="DietrichGebert/ponytail | Trendshift" width="250" height="55"/></a>
</p>
<p align="center"> <p align="center">
<strong>~54% menos código (hasta 94%) &middot; ~20% más barato &middot; ~27% más rápido &middot; 100% seguro</strong><br> <strong>~54% menos código (hasta 94%) &middot; ~20% más barato &middot; ~27% más rápido &middot; 100% seguro</strong><br>
<sub>Medido en sesiones reales de Claude Code editando un repo open-source real (FastAPI + React), contra el mismo agente sin skill. ~54% es el promedio de 12 tareas de feature (Haiku 4.5, n=4); llega al 94% cuando un agente sobre-construye (un selector de fechas) y es casi cero cuando el código ya es mínimo. ponytail mantiene cada guarda de seguridad, mientras que un prompt pelado de "escribe one-liners" se salta una. (El benchmark anterior de un solo disparo reportaba 80-94% como cifra plana; contra un baseline agéntico justo, ese es el techo por tarea, no el promedio.) <a href="benchmarks/results/2026-06-18-agentic.md">Reporte completo</a> &middot; <a href="benchmarks/">reprodúcelo</a>.</sub> <sub>Medido en sesiones reales de Claude Code editando un repo open-source real (FastAPI + React), contra el mismo agente sin skill. ~54% es el promedio de 12 tareas de feature (Haiku 4.5, n=4); llega al 94% cuando un agente sobre-construye (un selector de fechas) y es casi cero cuando el código ya es mínimo. ponytail mantiene cada guarda de seguridad, mientras que un prompt pelado de "escribe one-liners" se salta una. (El benchmark anterior de un solo disparo reportaba 80-94% como cifra plana; contra un baseline agéntico justo, ese es el techo por tarea, no el promedio.) <a href="benchmarks/results/2026-06-18-agentic.md">Reporte completo</a> &middot; <a href="benchmarks/">reprodúcelo</a>.</sub>
@@ -29,6 +35,10 @@
--- ---
<p align="center">
<a href="https://ponytail.dev/soon"><img src="assets/waitlist-banner-es.png" alt="Algo nuevo está por llegar, únete a la lista" width="760"></a>
</p>
Lo conoces. Cola de caballo larga. Lentes ovalados. Lleva más tiempo en la empresa que el control de versiones. Le muestras cincuenta líneas; las mira, no dice nada, y las reemplaza por una. Lo conoces. Cola de caballo larga. Lentes ovalados. Lleva más tiempo en la empresa que el control de versiones. Le muestras cincuenta líneas; las mira, no dice nada, y las reemplaza por una.
Ponytail lo pone dentro de tu agente de IA. Ponytail lo pone dentro de tu agente de IA.
@@ -150,7 +160,13 @@ pi install git:github.com/DietrichGebert/ponytail
### OpenCode ### OpenCode
Ejecuta OpenCode desde un checkout de este repo (el plugin reutiliza sus `hooks/` y `skills/`), y agrega esto a `opencode.json`: Agrega esto a `opencode.json`:
```json
{ "plugin": ["@dietrichgebert/ponytail"] }
```
O ejecútalo desde un checkout (el plugin reutiliza sus `hooks/` y `skills/`):
```json ```json
{ "plugin": ["./.opencode/plugins/ponytail.mjs"] } { "plugin": ["./.opencode/plugins/ponytail.mjs"] }
@@ -248,3 +264,13 @@ Ya sabes exactamente por qué.
## Licencia ## Licencia
[MIT](LICENSE). La licencia más corta que funciona. [MIT](LICENSE). La licencia más corta que funciona.
## Historial de estrellas
<a href="https://www.star-history.com/dietrichgebert/ponytail#history">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=DietrichGebert/ponytail&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=DietrichGebert/ponytail&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=DietrichGebert/ponytail&type=Date" />
</picture>
</a>
+296
View File
@@ -0,0 +1,296 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.png">
<img src="assets/logo.png" width="220" alt="Ponytail, the lazy senior dev">
</picture>
</p>
<h1 align="center">Ponytail</h1>
<p align="center">
<em>말이 없다. 한 줄을 쓴다. 돌아간다.</em>
</p>
<p align="center">
<img src="https://img.shields.io/github/stars/DietrichGebert/ponytail?style=flat-square&color=111111&label=stars" alt="Stars">
<img src="https://img.shields.io/github/v/release/DietrichGebert/ponytail?style=flat-square&color=111111&label=release" alt="Release">
<img src="https://img.shields.io/npm/v/@dietrichgebert/ponytail?style=flat-square&color=111111&label=npm" alt="npm">
<img src="https://img.shields.io/badge/works%20with-14%20agents-111111?style=flat-square" alt="Works with 14 agents">
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
</p>
<p align="center">
<a href="https://trendshift.io/repositories/50668" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/50668/daily" alt="DietrichGebert/ponytail | Trendshift" width="250" height="55"/></a>
<a href="https://trendshift.io/repositories/50668" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/50668/weekly" alt="DietrichGebert/ponytail | Trendshift" width="250" height="55"/></a>
</p>
<p align="center">
<strong>코드 약 54% 감소(최대 94%) &middot; 약 20% 저렴 &middot; 약 27% 빠름 &middot; 100% 안전</strong><br>
<sub>실제 오픈소스 저장소(FastAPI + React)를 고치는 실제 Claude Code 세션에서, 스킬을 끈 같은 에이전트와 견줘 측정했다. 약 54%는 기능 작업 12건의 평균이다(Haiku 4.5, n=4). 에이전트가 과하게 짤 여지가 있는 곳(날짜 선택기)에선 94%까지 오르고, 코드가 이미 최소한인 곳에선 0에 가깝다. ponytail은 안전 가드를 하나도 빼놓지 않지만, 그냥 "한 줄로 써"라고만 시킨 프롬프트는 그중 하나를 놓친다. (예전 단발성 벤치마크는 80-94%를 단일 수치로 내세웠는데, 공정한 에이전트 기준선에 견주면 그건 평균이 아니라 작업별 상한이다.) <a href="benchmarks/results/2026-06-18-agentic.md">전체 보고서</a> &middot; <a href="benchmarks/">직접 재현하기</a>.</sub>
</p>
<p align="center">
<sub>커뮤니티 번역이다. 기준이 되는 최신 버전은 <a href="README.md">영어 README</a>다.</sub>
</p>
---
<p align="center">
<a href="https://ponytail.dev/soon"><img src="assets/waitlist-banner-ko.png" alt="새로운 것이 다가오고 있습니다, 대기자 명단 신청" width="760"></a>
</p>
이런 사람, 다들 알 거다. 긴 포니테일에 타원형 안경. 버전 관리 시스템보다 회사에 오래 있었다. 코드 쉰 줄을 들이밀면 잠깐 보더니, 말없이 한 줄로 바꿔 놓는다.
Ponytail은 그를 당신의 AI 에이전트 안에 앉혀 둔다.
## Before / after
날짜 선택기 하나 만들어 달라고 한다. 에이전트는 flatpickr를 깔고, 래퍼 컴포넌트를 짜고, 스타일시트를 붙이더니, 타임존 얘기를 꺼내기 시작한다.
ponytail이라면:
```html
<!-- ponytail: browser has one -->
<input type="date">
```
살아남은 것들이 더 궁금하다면 [examples/](examples/)로.
## Numbers
공정하게 재려면 실제 에이전트에게 실질적인 작업을 시켜 봐야 한다. 헤드리스 Claude Code 세션에게 [tiangolo의 full-stack-fastapi-template](https://github.com/fastapi/full-stack-fastapi-template)(진짜 FastAPI + React 저장소)을 맡기고, 남긴 `git diff`로 점수를 매겼다. 기능 티켓 12건, 같은 에이전트를 스킬만 켜고 끄며 비교, n=4, Haiku 4.5.
<p align="center">
<img src="assets/benchmark-agentic.svg" width="860" alt="Each arm as a percent of the no-skill baseline across LOC, tokens, cost and time (Haiku 4.5). ponytail is lowest on every metric (LOC 46%, tokens 78%, cost 80%, time 73%); caveman rises above 100% on tokens, cost and time; yagni-oneliner LOC 67%. Safety, separate adversarial tier: baseline, caveman and ponytail 100%, yagni-oneliner 95%.">
</p>
| 스킬 없는 기준선 대비 | LOC | tokens | cost | time | safe |
|---|--:|--:|--:|--:|--:|
| **ponytail** | **-54%** | **-22%** | **-20%** | **-27%** | **100%** |
| caveman (간결한 산문 대조군) | -20% | +7% | +3% | +2% | 100% |
| "YAGNI + one-liners" 프롬프트 | -33% | -14% | -21% | -30% | 95% |
모든 지표를 깎은 건 ponytail뿐이고, 그러면서 안전까지 온전히 지킨 것도 ponytail뿐이다. 깎이는 폭은 과잉 구현의 함정이 실제로 있는 곳에서 가장 크다. 컴포넌트 대신 네이티브 `<input>`으로 손이 가니 날짜 선택기는 404줄에서 23줄로, 색상 선택기는 287줄에서 23줄로 줄어든다. 반대로 이미 군더더기 없는 코드에선 거의 0이다. 전체 방법론, 작업별 표, 한계는 [benchmarks/results/2026-06-18-agentic.md](benchmarks/results/2026-06-18-agentic.md)에 있다.
<details>
<summary><strong>예전 단발성 수치 (격리된 생성)</strong></summary>
일상적인 작업 다섯 가지, 모델 셋, 비교군 셋(스킬 없음, [caveman](https://github.com/JuliusBrussee/caveman), ponytail), 10회 실행, 중앙값 기준. 프롬프트 하나에 응답 하나, 답변의 줄 수를 셌다:
<p align="center">
<img src="assets/benchmark-3model.svg" width="860" alt="Median lines of code per arm across Haiku, Sonnet and Opus">
</p>
여기선 **코드 80-94% 감소**가 나왔다. 다만 [#126](https://github.com/DietrichGebert/ponytail/issues/126)이 맞게 짚었듯, 스킬을 전혀 안 붙인 기준선 모델은 답변을 설명과 선택지로 부풀린다. 그래서 그 격차의 일부는 대화형 기준선이 만들어 낸 착시다. 위의 에이전트 수치가 그걸 바로잡은, 근거 있는 버전이다. 단발성 실행은 `npx promptfoo eval -c benchmarks/promptfooconfig.yaml`로 재현할 수 있다.
</details>
**규칙은 애초에 "토큰 최소화"가 아니었다.** 작업에 필요한 만큼만 쓰되, 검증·에러 처리·보안·접근성은 절대 덜어내지 않는다는 것이다. 코드가 작아지는 건 억지로 줄여서가 아니라 딱 그만큼만 필요해서다. 비용과 지연이 낮아지는 것도 단계를 충실히 밟는 모델에서나 부수적으로 딸려 오는 효과일 뿐이다. 그 단계를 고민하느라 사고 토큰을 쏟는 간결한 추론 모델은 오히려 거꾸로 갈 수도 있다(GPT-5.5가 그렇다).
## How it works
코드를 쓰기 전에, 에이전트는 가장 먼저 들어맞는 단계에서 멈춘다:
```
1. 이게 있을 필요가 있나? → 없다: 건너뛴다 (YAGNI)
2. 이미 이 코드베이스에 있나? → 다시 짜지 말고 가져다 쓴다
3. 표준 라이브러리로 되나? → 쓴다
4. 네이티브 플랫폼 기능인가? → 쓴다
5. 깔려 있는 의존성이 푸나? → 쓴다
6. 한 줄로 되나? → 한 줄
7. 그제서야: 돌아가는 최소한
```
단계를 밟는 건 문제를 이해한 *다음*이지, 이해를 대신하는 게 아니다. 변경이 닿는 코드를 읽고 실제 흐름을 따라가 본 뒤에야 단계를 고른다. 해법에는 게을러도, 읽는 데는 절대 게으르지 않다.
게으른 거지 부주의한 게 아니다. 신뢰 경계의 검증, 데이터 손실 방지, 보안, 접근성은 결코 잘려 나가지 않는다.
## Install
ponytail이 당신에게 요구할 수고의 최대치:
Claude Code와 Codex 플러그인은 자그마한 Node.js 라이프사이클 훅 두 개를 돌리니, `node`가 PATH에 잡혀 있어야 한다(Nix/nvm 사용자라면 비대화형 셸의 PATH에 있어야 한다). 없어도 스킬은 멀쩡히 돌아간다. 다만 늘 켜져 있던 자동 활성화가 매 프롬프트마다 에러를 뱉는 대신 조용히 비활성으로 남을 뿐이다.
### Claude Code
```
/plugin marketplace add DietrichGebert/ponytail
```
```
/plugin install ponytail@ponytail
```
(설치가 되려면 두 프롬프트를 따로 보내야 한다)
데스크톱 앱에는 `/plugin` 명령이 없다. 대신 UI에서 설치한다: Customize, 개인 플러그인 옆의 +, Create plugin and add marketplace, Add from repository, 그다음 저장소 URL 입력(감사합니다 @NiklasDHahn, #98).
### Codex
```bash
codex plugin marketplace add DietrichGebert/ponytail
codex
```
`/plugins`를 열어 Ponytail 마켓플레이스를 고르고 Ponytail을 설치한다. 그런 다음
`/hooks`를 열어 라이프사이클 훅 두 개를 검토하고 신뢰한 뒤, 새 스레드를 시작한다.
이 설치 한 번이면 Codex 데스크톱 앱도 같이 잡힌다. 설치 후 앱을 다시 켜면 플러그인을 알아챈다.
### GitHub Copilot CLI
```bash
copilot plugin marketplace add DietrichGebert/ponytail
copilot plugin install ponytail@ponytail
```
대화형 Copilot CLI 세션에서는 슬래시 명령으로 똑같이 하면 된다:
```
/plugin marketplace add DietrichGebert/ponytail
/plugin install ponytail@ponytail
```
Copilot CLI는 플러그인 명령에 그 이름을 네임스페이스로 붙인다. 예를 들면:
```text
/ponytail:ponytail ultra
/ponytail:ponytail-review
```
### Pi agent harness
```
pi install git:github.com/DietrichGebert/ponytail
```
### OpenCode
`opencode.json`에 다음을 더한다:
```json
{ "plugin": ["@dietrichgebert/ponytail"] }
```
체크아웃에서 직접 돌려도 된다(플러그인이 `hooks/``skills/`를 그대로 쓴다):
```json
{ "plugin": ["./.opencode/plugins/ponytail.mjs"] }
```
매 턴마다 지금 레벨의 룰셋을 주입하고, `/ponytail` 명령들을 붙여 준다([Commands](#commands) 참고). OpenCode는 이 저장소의 `AGENTS.md`도 알아서 불러오니, 플러그인이 없어도 규칙은 살아 있다. 플러그인은 `lite/full/ultra/off` 레벨을 얹어 준다.
`./` 경로는 프로젝트의 `opencode.json`을 기준으로 풀린다. 체크아웃 하나를 여러 프로젝트에서 같이 쓰려면, 대신 `.mjs`의 절대 경로를 가리키면 된다(그 파일은 제 위치를 기준으로 `hooks/``skills/`를 찾는다).
### Gemini CLI
```bash
gemini extensions install https://github.com/DietrichGebert/ponytail
```
매 세션 룰셋을 늘 켜진 컨텍스트로 불러오고 `/ponytail` 명령들을 등록한다. `skills/`도 함께 실리며, 작업에 필요할 때 켜진다.
Gemini 어댑터는 일부러 루트 `hooks/hooks.json`을 두지 않는다. Gemini는 그 경로를 자동으로 불러오는데, ponytail의 라이프사이클 훅은 Claude/Codex 이벤트 이름을 쓰기 때문이다.
### Antigravity CLI
Google이 Gemini CLI를 Antigravity CLI(`agy` 바이너리)로 이름을 바꾸는 중인데, 같은 확장이 거기에도 설치된다:
```bash
agy plugin install https://github.com/DietrichGebert/ponytail
```
이 저장소의 `gemini-extension.json`을 그대로 재사용한다. 차이는 하나다. Antigravity는 `/ponytail` 명령들을 스킬로 바꿔 버려서, 슬래시 메뉴에서 고르는 대신 채팅에 직접 친다(예: `/ponytail-review`를 메시지로). 전환이 마무리될 때까지(2026년 6월 18일경)는 `gemini extensions install`도 여전히 먹힌다. 늘 켜진 규칙으로 돌리고 싶으면, 룰셋을 `.agents/rules/`에 넣으면 된다.
### CodeWhale
프로젝트 루트의 `AGENTS.md`를 읽고, 설정은 전혀 필요 없다. [`AGENTS.md`](AGENTS.md)를 프로젝트에 복사하거나, 이 저장소를 체크아웃한 곳에서 `codewhale`을 돌리면 된다. 그게 끝이다.
### Swival
먼저 컬렉션을 라이브러리에 스테이징한 다음, 원하는 스킬을 더한다:
```bash
swival skills add --global https://github.com/DietrichGebert/ponytail # ~/.config/swival/library에 스테이징
swival skills add ponytail # 이 프로젝트에 컬렉션 설치
swival skills add --global ponytail # 또는 모든 프로젝트에서 켜기
```
Swival도 프로젝트 루트의 `AGENTS.md`와 전역의 `~/.config/swival/AGENTS.md`를 읽는다. 지시문 전용 폴백이다.
명령줄에서는 `$` 접두사로 스킬을 명시적으로 켠다. 예: `$ponytail-review`.
### OpenClaw
```bash
clawhub install ponytail
```
ClawHub에서 ponytail을 OpenClaw 스킬로 설치한다. review, audit, debt, gain, help 스킬도 같은 식으로 깐다(`clawhub install ponytail-review` 등). OpenClaw는 코딩 작업에 이를 적용하고 `/ponytail` 명령으로도 열어 준다. ClawHub가 없으면 [`.openclaw/skills/ponytail`](.openclaw/skills/)을 `~/.openclaw/skills/`에 복사하면 된다.
이게 끝이었다. 그 사람이라면 흐뭇해할 거다. 입 밖으로 내진 않겠지만.
매 세션 켜져 있고, 명령 몇 개가 딸려 온다([Commands](#commands) 참고). `/ponytail ultra`는 코드베이스가 당신에게 단단히 밉보인 날을 위해 있다. 시작할 때와 모드를 바꿀 때 지금 모드를 보여 준다.
새 세션마다 적용할 레벨은 `PONYTAIL_DEFAULT_MODE` 환경 변수(`lite`/`full`/`ultra`/`off`)로, 또는 `~/.config/ponytail/config.json``defaultMode` 필드(Windows에선 `%APPDATA%\ponytail\config.json`)로 정한다. 기본값은 `full`이다.
Cursor, Windsurf, Cline, GitHub Copilot(에디터), Aider, Kiro, Zed, CodeWhale: 이 저장소에서 맞는 규칙 파일을 복사하면 된다([`.cursor/rules/`](.cursor/rules/), [`.windsurf/rules/`](.windsurf/rules/), [`.clinerules/`](.clinerules/), [`.github/copilot-instructions.md`](.github/copilot-instructions.md), [`AGENTS.md`](AGENTS.md), [`.kiro/steering/`](.kiro/steering/)).
Kiro: `.kiro/steering/ponytail.md``~/.kiro/steering/`(전역)이나 프로젝트의 `.kiro/steering/`에 복사한다.
GitHub Copilot CLI 폴백(지시문 전용 모드): 프로젝트의 `AGENTS.md``.github/copilot-instructions.md`를 읽거나, 모든 프로젝트에서 ponytail을 돌리려면 규칙을 `~/.copilot/copilot-instructions.md`에 복사한다. 이 경로는 늘 켜진 가이드는 살리지만, 플러그인 모드 전환이나 훅은 더해 주지 않는다.
Codex 확장을 쓰는 VS Code는 이 저장소가 함께 싣는 `AGENTS.md`를 읽으니, 저장소 루트에서 설정 없이 돌아간다(`~/.codex/AGENTS.md`를 두면 Codex 전역으로 잡힌다).
어떤 파일이 어느 에이전트에 매핑되는지: [Agent portability](docs/agent-portability.md).
## Commands
| 명령 | 하는 일 |
|---------|--------------|
| `/ponytail [lite \| full \| ultra \| off]` | 강도를 정하거나, 끈다. 인수가 없으면 지금 레벨을 알려 준다. |
| `/ponytail-review` | 지금 diff를 과잉 구현 관점에서 훑고, 삭제 목록을 돌려준다. |
| `/ponytail-audit` | diff만이 아니라 저장소 전체를 과잉 구현 관점에서 감사한다. |
| `/ponytail-debt` | 미뤄 둔 `ponytail:` 간소화들을 장부로 모아, "나중에"가 "영영"이 되지 않게 한다. |
| `/ponytail-gain` | 벤치마크로 잰 효과 스코어보드(코드 절감, 비용 절감, 속도 향상)를 보여 준다. |
| `/ponytail-help` | 위 명령들의 빠른 참조. |
명령들은 스킬을 지원하는 호스트가 있어야 돈다(Claude Code, Codex, OpenCode, Gemini, pi). Codex에선 스킬이라 `@`로 부른다(`@ponytail-review`). 지시문 전용 어댑터(Cursor, Windsurf, Cline, Copilot, Kiro, Antigravity)는 명령 없이 늘 켜진 룰셋만 불러온다.
## Development
압축 규칙 텍스트를 바꿀 때는, 에이전트 사본들을 같은 상태로 맞춰 둔다:
```bash
node scripts/check-rule-copies.js
npm test
```
OpenClaw 스킬 패키지(`.openclaw/skills/`)는 `skills/`에서 생성된다. 스킬을 바꾼 뒤에는 `node scripts/build-openclaw-skills.js`를 다시 돌린다. 묵은 상태면 테스트 스위트가 실패한다.
정확성 벤치마크는 이메일·CSV 검사를 위해 Python을 띄운다. `python`보다 `python3`를 먼저 시도한다. CSV 검사는 로컬에 `pandas`가 깔려 있어야 한다.
## FAQ
**설정 파일이 필요한가?**
아니다. 선택 사항인 `~/.config/ponytail/config.json`이나 `PONYTAIL_DEFAULT_MODE` 환경 변수로 기본 레벨을 정할 순 있지만, 꼭 있어야 하는 건 없다.
**그래도 120줄짜리 캐시 클래스가 정말 필요하다면?**
필요 없다. 그래도 우기면 그가 만들어 준다. 천천히. 정확하게. 당신을 쳐다보면서.
**확장은 되나?**
당신이 안 쓴 코드는 무한히 확장된다. 버그 0, CVE 0, 가동률 100%. 예나 지금이나.
**왜 하필 "ponytail"인가?**
당신은 이유를 정확히 안다.
## License
[MIT](LICENSE). 돌아가는 가장 짧은 라이선스.
## Star History
<a href="https://www.star-history.com/dietrichgebert/ponytail#history">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=DietrichGebert/ponytail&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=DietrichGebert/ponytail&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=DietrichGebert/ponytail&type=Date" />
</picture>
</a>
+21 -3
View File
@@ -14,21 +14,31 @@
<p align="center"> <p align="center">
<img src="https://img.shields.io/github/stars/DietrichGebert/ponytail?style=flat-square&color=111111&label=stars" alt="Stars"> <img src="https://img.shields.io/github/stars/DietrichGebert/ponytail?style=flat-square&color=111111&label=stars" alt="Stars">
<img src="https://img.shields.io/github/v/release/DietrichGebert/ponytail?style=flat-square&color=111111&label=release" alt="Release"> <img src="https://img.shields.io/github/v/release/DietrichGebert/ponytail?style=flat-square&color=111111&label=release" alt="Release">
<img src="https://img.shields.io/npm/v/@dietrichgebert/ponytail?style=flat-square&color=111111&label=npm" alt="npm">
<img src="https://img.shields.io/badge/works%20with-14%20agents-111111?style=flat-square" alt="Works with 14 agents"> <img src="https://img.shields.io/badge/works%20with-14%20agents-111111?style=flat-square" alt="Works with 14 agents">
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license"> <img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
</p> </p>
<p align="center">
<a href="https://trendshift.io/repositories/50668" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/50668/daily" alt="DietrichGebert/ponytail | Trendshift" width="250" height="55"/></a>
<a href="https://trendshift.io/repositories/50668" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/50668/weekly" alt="DietrichGebert/ponytail | Trendshift" width="250" height="55"/></a>
</p>
<p align="center"> <p align="center">
<strong>~54% less code (up to 94%) &middot; ~20% cheaper &middot; ~27% faster &middot; 100% safe</strong><br> <strong>~54% less code (up to 94%) &middot; ~20% cheaper &middot; ~27% faster &middot; 100% safe</strong><br>
<sub>Measured on real Claude Code sessions editing a real open-source repo (FastAPI + React), against the same agent with no skill. ~54% is the mean across 12 feature tasks (Haiku 4.5, n=4); it reaches 94% where an agent over-builds (a date picker) and is near zero where the code is already minimal. ponytail keeps every safety guard while a bare "write one-liners" prompt drops one. (The earlier single-shot benchmark reported 80-94% as a flat figure; against a fair agentic baseline that is the per-task ceiling, not the average.) <a href="benchmarks/results/2026-06-18-agentic.md">Full writeup</a> &middot; <a href="benchmarks/">reproduce it</a>.</sub> <sub>Measured on real Claude Code sessions editing a real open-source repo (FastAPI + React), against the same agent with no skill. ~54% is the mean across 12 feature tasks (Haiku 4.5, n=4); it reaches 94% where an agent over-builds (a date picker) and is near zero where the code is already minimal. ponytail keeps every safety guard while a bare "write one-liners" prompt drops one. (The earlier single-shot benchmark reported 80-94% as a flat figure; against a fair agentic baseline that is the per-task ceiling, not the average.) <a href="benchmarks/results/2026-06-18-agentic.md">Full writeup</a> &middot; <a href="benchmarks/">reproduce it</a>.</sub>
</p> </p>
<p align="center"> <p align="center">
<sub><a href="README.es.md">Español</a></sub> <sub><a href="README.es.md">Español</a> &middot; <a href="README.ko.md">한국어</a></sub>
</p> </p>
--- ---
<p align="center">
<a href="https://ponytail.dev/soon"><img src="assets/waitlist-banner.png" alt="Something's coming, join the waitlist" width="760"></a>
</p>
You know him. Long ponytail. Oval glasses. Has been at the company longer than the version control. You show him fifty lines; he looks at them, says nothing, and replaces them with one. You know him. Long ponytail. Oval glasses. Has been at the company longer than the version control. You show him fifty lines; he looks at them, says nothing, and replaces them with one.
Ponytail puts him inside your AI agent. Ponytail puts him inside your AI agent.
@@ -170,8 +180,6 @@ Injects the ruleset every turn at the active level; adds the `/ponytail` command
The `./` path resolves against your project's `opencode.json`; to share one checkout across projects, point it at the absolute path of the `.mjs` instead (it finds its `hooks/` and `skills/` relative to its own file). The `./` path resolves against your project's `opencode.json`; to share one checkout across projects, point it at the absolute path of the `.mjs` instead (it finds its `hooks/` and `skills/` relative to its own file).
The plugin path loads the ruleset everywhere, but the `/ponytail` commands are separate files in `.opencode/command/` that OpenCode only discovers from your project or the global commands dir. To use them outside this checkout, link them once: `ln -sf /absolute/path/to/ponytail/.opencode/command/* ~/.config/opencode/command/`.
### Gemini CLI ### Gemini CLI
```bash ```bash
@@ -287,3 +295,13 @@ You know exactly why.
## License ## License
[MIT](LICENSE). The shortest license that works. [MIT](LICENSE). The shortest license that works.
## Star History
<a href="https://www.star-history.com/dietrichgebert/ponytail#history">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=DietrichGebert/ponytail&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=DietrichGebert/ponytail&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=DietrichGebert/ponytail&type=Date" />
</picture>
</a>
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ponytail", "name": "ponytail",
"version": "4.8.1", "version": "4.8.3",
"description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.", "description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.",
"contextFileName": "AGENTS.md" "contextFileName": "AGENTS.md"
} }
+13
View File
@@ -14,6 +14,19 @@
] ]
} }
], ],
"SubagentStart": [
{
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-subagent.js\"; exit 0",
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\ponytail-subagent.js\" }",
"timeout": 5,
"statusMessage": "Loading ponytail mode..."
}
]
}
],
"UserPromptSubmit": [ "UserPromptSubmit": [
{ {
"hooks": [ "hooks": [
+17
View File
@@ -21,6 +21,15 @@ function clearMode() {
try { fs.unlinkSync(statePath); } catch (e) {} try { fs.unlinkSync(statePath); } catch (e) {}
} }
// Live mode written by activate/mode-tracker. Absent flag = ponytail off.
function readMode() {
try {
return fs.readFileSync(statePath, 'utf8').trim() || null;
} catch (e) {
return null;
}
}
function writeHookOutput(event, mode, context = '') { function writeHookOutput(event, mode, context = '') {
if (isCopilot) { if (isCopilot) {
// Copilot reads additionalContext on SessionStart; ignores output elsewhere. // Copilot reads additionalContext on SessionStart; ignores output elsewhere.
@@ -39,6 +48,13 @@ function writeHookOutput(event, mode, context = '') {
process.stdout.write(JSON.stringify(output)); process.stdout.write(JSON.stringify(output));
return; return;
} }
// Native Claude: SessionStart accepts raw stdout, but SubagentStart needs the
// hookSpecificOutput JSON form or the context is dropped.
if (event === 'SubagentStart') {
process.stdout.write(JSON.stringify(
{ hookSpecificOutput: { hookEventName: event, additionalContext: context } }));
return;
}
process.stdout.write(context); process.stdout.write(context);
} }
@@ -46,6 +62,7 @@ module.exports = {
clearMode, clearMode,
isCodex, isCodex,
isCopilot, isCopilot,
readMode,
setMode, setMode,
writeHookOutput, writeHookOutput,
}; };
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env node
// ponytail — Claude Code SubagentStart hook
//
// SessionStart context is parent-thread only and never reaches subagents, so
// without this every Task-spawned agent runs ponytail-unaware (issue #252).
// When ponytail mode is active, inject the same ruleset into each subagent.
const { getPonytailInstructions } = require('./ponytail-instructions');
const { readMode, writeHookOutput } = require('./ponytail-runtime');
const mode = readMode();
// Absent flag or off → ponytail isn't active; inject nothing.
if (!mode || mode === 'off') {
process.exit(0);
}
try {
writeHookOutput('SubagentStart', mode, getPonytailInstructions(mode));
} catch (e) {
// Silent fail — a stdout error at hook exit must not surface as a hook failure.
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@dietrichgebert/ponytail", "name": "@dietrichgebert/ponytail",
"version": "4.8.1", "version": "4.8.3",
"description": "Lazy senior dev mode for AI agents. The best code is the code you never wrote.", "description": "Lazy senior dev mode for AI agents. The best code is the code you never wrote.",
"keywords": ["opencode-plugin", "opencode", "ponytail", "pi-package", "pi", "skills"], "keywords": ["opencode-plugin", "opencode", "ponytail", "pi-package", "pi", "skills"],
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ponytail-mcp", "name": "ponytail-mcp",
"version": "4.8.1", "version": "4.8.3",
"description": "MCP server that serves Ponytail's lazy-senior-dev instructions as a prompt and a tool.", "description": "MCP server that serves Ponytail's lazy-senior-dev instructions as a prompt and a tool.",
"private": true, "private": true,
"type": "module", "type": "module",
+43 -2
View File
@@ -26,9 +26,14 @@ function run(script, env, input = '') {
}); });
} }
// Keep the base env clean so the default-dir checks are deterministic; the // Keep the base env clean so the default-dir / native-Claude checks are
// CLAUDE_CONFIG_DIR case sets it explicitly. // deterministic; the CLAUDE_CONFIG_DIR and codex/copilot cases set these
// explicitly where needed. run() spreads process.env, so a PLUGIN_DATA /
// COPILOT_PLUGIN_DATA leaked from the dev or CI shell would otherwise steer
// writeHookOutput into the wrong branch and mis-fire the native assertions.
delete process.env.CLAUDE_CONFIG_DIR; delete process.env.CLAUDE_CONFIG_DIR;
delete process.env.PLUGIN_DATA;
delete process.env.COPILOT_PLUGIN_DATA;
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ponytail-hooks-')); const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ponytail-hooks-'));
// Runs on normal exit and on assertion-throw exit; force makes it idempotent. // Runs on normal exit and on assertion-throw exit; force makes it idempotent.
@@ -168,4 +173,40 @@ assert.equal(
output = JSON.parse(result.stdout); output = JSON.parse(result.stdout);
assert.deepEqual(output, {}); assert.deepEqual(output, {});
// SubagentStart hook: when ponytail mode is active it injects the ruleset into
// each subagent (issue #252). Native Claude must get the hookSpecificOutput JSON
// form, not raw stdout, or the context is dropped.
const subHome = path.join(temp, 'sub-home');
const subFlag = path.join(subHome, '.claude', '.ponytail-active');
fs.mkdirSync(path.dirname(subFlag), { recursive: true });
const subEnv = { HOME: subHome, USERPROFILE: subHome };
fs.writeFileSync(subFlag, 'full');
result = run('ponytail-subagent.js', subEnv);
assert.equal(result.status, 0, result.stderr);
output = JSON.parse(result.stdout);
assert.equal(output.hookSpecificOutput.hookEventName, 'SubagentStart');
assert.match(
output.hookSpecificOutput.additionalContext,
/PONYTAIL MODE ACTIVE — level: full/,
);
// No flag → ponytail off → inject nothing (empty stdout, no failure).
fs.unlinkSync(subFlag);
result = run('ponytail-subagent.js', subEnv);
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout, '', 'SubagentStart must stay silent when ponytail is off');
// Codex shares claude-codex-hooks.json, so SubagentStart is reachable under Codex
// too — assert the codex branch emits the badge plus hookSpecificOutput.
const subCodex = path.join(temp, 'sub-codex');
fs.mkdirSync(subCodex, { recursive: true });
fs.writeFileSync(path.join(subCodex, '.ponytail-active'), 'full');
result = run('ponytail-subagent.js', { HOME: subHome, USERPROFILE: subHome, PLUGIN_DATA: subCodex });
assert.equal(result.status, 0, result.stderr);
output = JSON.parse(result.stdout);
assert.equal(output.systemMessage, 'PONYTAIL:FULL');
assert.equal(output.hookSpecificOutput.hookEventName, 'SubagentStart');
assert.match(output.hookSpecificOutput.additionalContext, /PONYTAIL MODE ACTIVE — level: full/);
console.log('hook compatibility checks passed'); console.log('hook compatibility checks passed');