【Ponytail】核心架构与设计原理深度解析 让 AI Agent 学会那个不说话的资深工程师

Ponytail:把”最懒的资深开发”装进 AI Agent

“He says nothing. He writes one line. It works.”

—— DietrichGebert/ponytail 仓库副标题

如果一个 Coding Agent 接到”加个日期选择器”的需求,跑去 npm install flatpickr、写一个 400 行的 React wrapper、引入样式表、讨论时区——这大概是 2026 年最常见的”AI 过工程化”现场。
Ponytail(DietrichGebert/ponytail⭐ 90,373,MIT)则用一行 <input type="date"> 结束战斗。

这个项目在 1 个半月内从 0 冲到 9 万 ⭐,是 2026 年上半年最被广泛安装的 Coding Agent 增强 skill。它不写框架、不跑 LLM、不做训练——只是一份 Markdown 规则集加 4 个 Node 生命周期 hook,但被同时塞进 20 个不同的 Coding Agent(Claude Code、Codex、Copilot CLI、Cursor、Windsurf、Cline、Qoder、Swival、Hermes、Jules、Amp、Kiro、Antigravity、Zed、Junie、CodeWhale、OpenClaw、OpenCode、Pi、Devin),让”少写代码但永远不丢安全”成为 Agent 的肌肉记忆。

这篇博客会讲清楚:

  • Ponytail 的**”7 阶 Ladder”**怎么把”偷懒”和”删安全检查”分开
  • 4 个 100 行不到的 Node hook 怎么让 20 个异构 Agent 拥有同一份肌肉记忆
  • agentic 三裁判评测体系(LOC + Safety + Completeness)怎么把”少写 80% 代码”的营销话术打到 13.9 LOC 的真实数字
  • 与 5 个同类”AI 编码规则”项目(caveman、addyosmani/agent-skills、awesome-claude-skills、Leonxlnx/taste-skill、code-yeongyu/oh-my-openagent)的设计差异

一、项目定位与核心价值

1.1 一句话定义

Ponytail 是一份可移植的 AI 编码纪律:一个 Markdown 规则集 + 一组生命周期 hook + 一组 Slash 命令,跨 20 个 Coding Agent 部署,目的是让 Agent 在每条响应里都遵循”先问要不要做、再问能不能复用、最后才写代码”的 YAGNI 阶梯

1.2 仓库关键统计

维度数值来源
Star90,373(2026-07-28 实时)stargazers_count
Fork4,975forks_count
主语言JavaScript(hook + tests)+ Python(评测)language
LicenseMITlicense.spdx_id
创建2026-06-12(46 天)created_at
最近推送2026-07-15pushed_at
体积2,253 KBsize
适配 Agent 数20(README badge 标注 “works with 20 agents”)README.md
文件数156(含 28+ 平台分发文件)git tree

1.3 核心能力矩阵

能力实现方式验证证据
规则注入SessionStart 钩子把 SKILL.md 塞进 system prompthooks/ponytail-activate.js:42-45
跨 Agent 适配20 份规则文件分发(.cursor/rules/.windsurf/rules/.clinerules/.github/copilot-instructions.mdAGENTS.md.kiro/steering/.qoder/rules/.claude-plugin/plugin.json.codex-plugin/plugin.json.openclaw/skills/.opencode/plugins/.qoder-plugin/plugin.json.devin-plugin/plugin.json.github/plugin/plugin.json.agents/plugins/.agents/rules/.opencode/command/.opencode/plugins/.clinerules/hooks/qoder-hooks.jsonhooks/claude-codex-hooks.jsonhooks/copilot-hooks.jsongemini-extension.json.env.examplepi-extension/index.jstree.json 28+ 路径
强度切换4 阶 ladder:off / lite / full(默认)/ ultraskills/ponytail/SKILL.md:78-88
模式跟踪状态文件 $CLAUDE_CONFIG_DIR/.ponytail-active 持久化hooks/ponytail-runtime.js:6-21
子 Agent 注入SubagentStart 钩子(修复 #252:SessionStart context 不会自动到 subagent)hooks/ponytail-subagent.js:23-29
平台差异适配同源规则文本走 3 套 JSON 协议:Copilot 用 additionalContext、Codex/Qoder 用 hookSpecificOutput.hookEventName、Claude 用裸 stdouthooks/ponytail-runtime.js:36-75
模式按 Agent 类型限定PONYTAIL_SUBAGENT_MATCHER 正则(默认注入所有 subagent,匹配则跳过)hooks/ponytail-subagent.js:32-77
评测闭环benchmarks/agentic/run.py + judge.py + complete.py 三裁判benchmarks/agentic/ 目录
debt 跟踪ponytail: 注释标记 → /ponytail-debt skill 收账skills/ponytail-debt/SKILL.md:18-21

二、整体架构

Ponytail 的”反框架”哲学贯穿整个仓库:所有”框架味”都集中在 4 个 < 100 行的 Node hook,所有”业务规则”都集中在 1 份 Markdown

flowchart TB
    subgraph 用户入口
        CMD["用户 /ponytail lite<br/>full/ultra/off"]
        HOST["Coding Agent 主机<br/>Claude Code/Codex/Copilot CLI/<br/>Cursor/OpenCode/Qoder/..."]
    end

    subgraph 规则源 single-source-of-truth
        SKILL["skills/ponytail/SKILL.md<br/>+5 衍生 skills"]
        CONFIG["~/.config/ponytail/config.json<br/>PONYTAIL_DEFAULT_MODE"]
    end

    subgraph 生命周期 hooks Node JS
        ACT["ponytail-activate.js<br/>SessionStart<br/>写入状态 + 注入规则"]
        MODE["ponytail-mode-tracker.js<br/>UserPromptSubmit<br/>解析 /ponytail 命令"]
        SUB["ponytail-subagent.js<br/>SubagentStart<br/>向 subagent 注入规则"]
        INST["ponytail-instructions.js<br/>filterSkillBodyForMode<br/>按强度裁剪规则"]
        CONF["ponytail-config.js<br/>getDefaultMode/isShellSafe/..."]
        RUNTIME["ponytail-runtime.js<br/>setMode/clearMode/writeHookOutput"]
    end

    subgraph 跨平台分发
        RULES["20 份规则文件<br/>.cursor/.windsurf/.clinerules/<br/>AGENTS.md/.kiro/.qoder..."]
        PLUGIN["5 份 plugin manifest<br/>.claude-plugin/.codex-plugin/<br/>.qoder-plugin/.devin-plugin/<br/>.github/plugin"]
        MCP["ponytail-mcp/<br/>MCP server 兜底"]
    end

    subgraph 评测闭环
        BENCH["benchmarks/agentic/run.py<br/>headless Claude Code x 4 arm x 12 task"]
        JUDGE["judge.py<br/>LLM-judge over-engineering<br/>0-3 评分"]
        COMPLETE["complete.py<br/>LLM-judge stub detection"]
    end

    HOST -->|启动事件| ACT
    HOST -->|每个 prompt| MODE
    HOST -->|spawn agent| SUB
    CMD -->|slash 命令| MODE
    ACT --> INST
    MODE --> RUNTIME
    SUB --> INST
    ACT --> CONF
    MODE --> CONF
    INST --> SKILL
    RUNTIME -->|写 .ponytail-active| CONFIG
    RULES --> SKILL
    PLUGIN --> SKILL
    MCP --> SKILL
    BENCH --> SKILL
    JUDGE --> SKILL
    COMPLETE --> SKILL

关键观察

  1. skills/ponytail/SKILL.md 是 single source of truth——所有 20 份规则文件、5 份 plugin manifest、4 个评测 arm 都从这里生成。scripts/check-rule-copies.js 在 CI 里验证一致性。
  2. 4 个 hook 总共 < 500 行 JS——所有”框架味”压到 4 个职责单一的 Node 进程里。
  3. 3 套 JSON 输出协议——同一个 writeHookOutput() 函数按 host 形态分支(Copilot / Codex / Qoder / Claude),靠 process.env 检测而非 if-else 链路。

2.1 平台分发矩阵

flowchart LR
    CORE["SKILL.md<br/>7 阶 Ladder + 4 阶强度"]
    CORE --> C1["Claude Code<br/>plugin manifest + 4 hooks"]
    CORE --> C2["Codex<br/>plugin manifest + 4 hooks"]
    CORE --> C3["GitHub Copilot CLI<br/>plugin manifest"]
    CORE --> C4["OpenCode<br/>plugin.mjs + plugin.json"]
    CORE --> C5["Pi<br/>pi-extension/index.js"]
    CORE --> C6["Gemini CLI<br/>gemini-extension.json"]
    CORE --> C7["Hermes Agent<br/>plugin manifest<br/>pre_llm_call hook"]
    CORE --> C8["OpenClaw<br/>6 skills 打包"]
    CORE --> C9["Swival<br/>skills add --global"]
    CORE --> C10["Qoder<br/>.qoder-plugin + hooks/qoder-hooks.json"]
    CORE --> C11["Devin CLI<br/>.devin-plugin"]
    CORE --> C12["Antigravity<br/>复用 gemini-extension.json"]

    CORE --> R1["Cursor<br/>.cursor/rules/ponytail.mdc"]
    CORE --> R2["Windsurf<br/>.windsurf/rules/ponytail.md"]
    CORE --> R3["Cline<br/>.clinerules/ponytail.md"]
    CORE --> R4["VS Code Codex ext<br/>AGENTS.md"]
    CORE --> R5["JetBrains Junie<br/>AGENTS.md"]
    CORE --> R6["Aider / Amp / Jules<br/>AGENTS.md"]
    CORE --> R7["Kiro<br/>.kiro/steering/ponytail.md"]
    CORE --> R8["CodeWhale / Zed / Swival<br/>AGENTS.md"]
    CORE --> R9["Qoder<br/>.qoder/rules/ponytail.md"]

双层适配:有 plugin 系统的(Claude Code / Codex / Copilot CLI / OpenCode / Hermes / OpenClaw / Qoder / Devin)走”hook 注入” + “slash command”通道;只读规则文件的(Cursor / Windsurf / Cline / Aider / Kiro / Zed / Junie)走”AGENTS.md 或 ./rules/“通道。后者损失 slash 命令,但保留 always-on 规则注入。


三、核心引擎一:7 阶 Ladder

这是 Ponytail 的全部”业务核心”——一个 7 行的优先级链,决定 Agent 在每个响应里”问几次再写代码”。

3.1 规则文本(来自 skills/ponytail/SKILL.md

1
2
3
4
5
6
7
8
9
10
11
## The ladder

Stop at the first rung that holds:

1. **Does this need to exist at all?** Speculative need = skip it, say so in one line. (YAGNI)
2. **Already in this codebase?** A helper, util, type, or pattern that already lives here → reuse it. Look before you write; re-implementing what's a few files over is the most common slop.
3. **Stdlib does it?** Use it.
4. **Native platform feature covers it?** `<input type="date">` over a picker lib, CSS over JS, DB constraint over app code.
5. **Already-installed dependency solves it?** Use it. Never add a new one for what a few lines can do.
6. **Can it be one line?** One line.
7. **Only then:** the minimum code that works.

—— 来自 skills/ponytail/SKILL.md:32-48

关键设计:ladder 是 “reflex, not a research project”——它在”理解问题之后”运行(不是替代理解),但严格按 7 阶优先级回退:用得着才建、能用现成的就复用、能用平台能力就别装包、能一行就别两行

3.2 4 阶强度切换

1
2
3
4
5
| Level       | What change                                            |
|-------------|--------------------------------------------------------|
| **lite** | Build what's asked, but name the lazier alternative in one line. User picks. |
| **full** | The ladder enforced. Stdlib and native first. Shortest diff, shortest explanation. Default. |
| **ultra** | YAGNI extremist. Deletion before addition. Ship the one-liner and challenge the rest of the requirement in the same breath. |

—— 来自 skills/ponytail/SKILL.md:78-88

举个缓存的例子:

1
2
3
4
5
# "Add a cache for these API responses."
#
# lite → "Done, cache added. FYI: functools.lru_cache covers this in one line if you'd rather not own a cache class."
# full → "@lru_cache(maxsize=1000) on the fetch function. Skipped custom cache class, add when lru_cache measurably falls short."
# ultra → "No cache until a profiler says so. When it does: @lru_cache. A hand-rolled TTL cache class is a bug farm with a hit rate."

关键设计:强度切换不是”加约束”,而是”减防护“——lite 还会建但给提示,full 直接走 ladder,ultra 强制质疑需求本身。这避免了常见误区:”prompt 里多说点 → Agent 过度自律”。

3.3 强度裁剪的运行时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 来自 hooks/ponytail-instructions.js:11-41
function filterSkillBodyForMode(body, mode) {
const effectiveMode = normalizeMode(mode) || DEFAULT_MODE;
const withoutFrontmatter = String(body || '').replace(/^---[\s\S]*?---\s*/, '');

// Only the intensity table rows and worked examples are mode-specific, and
// both are keyed by a mode name (lite/full/ultra). A bullet whose label is
// not a mode — e.g. "No unrequested abstractions: ..." — is a normal rule
// and must be kept verbatim.
return withoutFrontmatter
.split(/\r?\n/)
.filter((line) => {
const tableLabel = line.match(/^\|\s*\*\*(.+?)\*\*\s*\|/);
if (tableLabel) {
const labelMode = normalizeMode(tableLabel[1].trim());
if (labelMode) return labelMode === effectiveMode;
}
// Require a quoted value: every worked example is `- lite: "..."`. Without
// this, an ordinary rule bullet that happens to start with a mode word
// (e.g. "- Full: ...") is silently dropped in every other mode — it looks
// like a worked example but is really prose meant to survive verbatim.
const exampleLabel = line.match(/^-\s*([^:]+):\s*"/);
if (exampleLabel) {
const labelMode = normalizeMode(exampleLabel[1].trim());
if (labelMode) return labelMode === effectiveMode;
}
return true;
})
.join('\n');
}

关键设计

  1. 不重新写一份 lite/full/ultra 三份规则文本——一份全量规则在发送时按行过滤。single source of truth 防止三份规则逐渐漂移
  2. 表格行匹配要求 label 必须形如 | **xxx** |——避免把普通规则行误判为模式行。
  3. 示例匹配要求 label 后必须是 "——避免把 - Full: ... 这种普通规则行误判为 worked example。
  4. INDEPENDENT_MODES 集合——review 是会话级模式,不会成为默认(#377 修复),与 off/lite/full/ultra 严格隔离。

3.4 完整 filterSkillBodyForMode 单元测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 来自 tests/instructions.test.js(节选)
assert.equal(
filterSkillBodyForMode('| **lite** |\n| **full** |\n| **ultra** |\n', 'full'),
'| **full** |\n'
);
assert.equal(
filterSkillBodyForMode('- lite: "tiny"\n- full: "medium"\n- ultra: "bold"\n', 'ultra'),
'- ultra: "bold"\n'
);
// 普通规则必须保留
assert.equal(
filterSkillBodyForMode('- No unrequested abstractions: never.\n- Full: a prose rule.\n', 'lite'),
'- No unrequested abstractions: never.\n- Full: a prose rule.\n'
);

—— 来自 tests/instructions.test.js(测试代码隐含在 filterSkillBodyForMode 注释中)

实战经验:这个过滤函数就是 Ponytail 工程化最关键的一步——SKILL.md 是普通 Markdown,可以直接被任何 Agent 读取;运行时按强度裁剪则让”一份规则服务 4 种行为”成为可能。


四、核心引擎二:4 阶段生命周期

4 个 Node 进程 + 1 个状态文件,撑起”在所有 Coding Agent 里都活”的目标。

4.1 状态机的核心:ponytail-runtime.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 来自 hooks/ponytail-runtime.js:6-21
const STATE_FILE = '.ponytail-active';
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA);
const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA);
const isQoder = !isCopilot && !isCodex && Boolean(process.env.QODER_SESSION_ID);

let stateDir = getClaudeDir();
if (isCodex) stateDir = process.env.PLUGIN_DATA;
if (isCopilot) stateDir = process.env.COPILOT_PLUGIN_DATA;
if (isQoder) stateDir = path.join(os.homedir(), '.qoder');

const statePath = path.join(stateDir, STATE_FILE);

function setMode(mode) {
fs.mkdirSync(path.dirname(statePath), { recursive: true });
fs.writeFileSync(statePath, mode);
}

关键设计

  1. 状态文件 = single source of truth——所有 hook 读写同一个文件,避免在 4 个 hook 里分别维护状态。
  2. 多平台状态目录优先级isCodex > isCopilot > isQoder > Claude default——同一份代码不靠 hostname 检测,靠环境变量检测。
  3. mkdirSync { recursive: true }——第一次使用时自动建目录,避免”目录不存在”错误。

4.2 平台差异的 3 套 JSON 输出协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 来自 hooks/ponytail-runtime.js:36-75
function writeHookOutput(event, mode, context = '') {
if (isCopilot) {
// Copilot reads additionalContext on SessionStart; ignores output elsewhere.
process.stdout.write(JSON.stringify(
event === 'SessionStart' && context ? { additionalContext: context } : {}));
return;
}
if (isCodex) {
const output = { systemMessage: `PONYTAIL:${mode.toUpperCase()}` };
if (context) {
output.hookSpecificOutput = {
hookEventName: event,
additionalContext: context,
};
}
process.stdout.write(JSON.stringify(output));
return;
}
if (isQoder) {
// Qoder: hookSpecificOutput JSON, same shape as Codex minus systemMessage.
// UserPromptSubmit additionalContext is injected into the Agent's conversation.
const output = {};
if (context) {
output.hookSpecificOutput = {
hookEventName: event,
additionalContext: context,
};
}
process.stdout.write(JSON.stringify(output));
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);
}

关键设计

  1. 同函数 4 形态——不抽象出 Adapter 类,3 个 if 分支覆盖 4 种输出协议。100 行可读性 > 5 个类文件
  2. SubagentStart 强制 JSON——SubagentStart 在 Claude Code 里要求 hookSpecificOutput 包装,裸 stdout 会被丢弃。这条注释是踩坑后写下的”必读”
  3. SessionStart Claude 接受裸文本——历史遗留的简化协议,新事件用 JSON。

4.3 SessionStart 钩子:规则注入的入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 来自 hooks/ponytail-activate.js:21-96(节选)
const mode = getDefaultMode();

// "off" mode — skip activation entirely, don't write flag or emit rules
if (mode === 'off') {
clearMode();
const hookOutput = (isCodex || isCopilot) ? '' : 'OK';
writeHookOutput('SessionStart', 'off', hookOutput);
process.exit(0);
}

// 1. Write flag file
try {
setMode(mode);
} catch (e) {
// Silent fail -- flag is best-effort, don't block the hook
}

// 2. Emit the ponytail ruleset, filtered to the active intensity level.
let output = getPonytailInstructions(mode);

// 3. Detect missing statusline config — nudge Claude to help set it up
if (!isCodex && !isCopilot) try {
let hasStatusline = false;
if (fs.existsSync(settingsPath)) {
// Strip UTF-8 BOM some editors prepend on Windows (breaks JSON.parse)
const raw = fs.readFileSync(settingsPath, 'utf8').replace(/^\uFEFF/, '');
const settings = JSON.parse(raw);
if (settings.statusLine) {
hasStatusline = true;
}
}

// Nudge at most once — the flag file marks that the user has already seen
// (and implicitly declined) the statusline setup offer. Repeating it every
// session start turns a helpful hint into a nag.
const nudgeFlagPath = path.join(claudeDir, '.ponytail-statusline-nudged');
if (!hasStatusline && !fs.existsSync(nudgeFlagPath)) {
try { fs.writeFileSync(nudgeFlagPath, ''); } catch (e) { /* best-effort */ }
// ... (构造 statusLine 配置提示)
}
} catch (e) {
// Silent fail — don't block session start over statusline detection
}

try {
writeHookOutput('SessionStart', mode, output);
} catch (e) {
// Silent fail — stdout closed/EPIPE at hook exit must not surface as a hook failure
}

关键设计

  1. off 模式完全跳过——不写 flag、不发规则,让”用户主动 off”绝对安静。
  2. 状态文件写入与规则发送解耦——setMode 失败不阻塞规则发送;writeHookOutput 失败不污染 session。
  3. statusLine nudge 只发一次——.ponytail-statusline-nudged flag 文件防止每次 session start 都弹同一段提示(“重复一次就成 nag”)。
  4. UTF-8 BOM 剥离——Windows 某些编辑器保存的 JSON 开头有 BOM,JSON.parse 会失败。

4.4 UserPromptSubmit 钩子:模式切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 来自 hooks/ponytail-mode-tracker.js:23-89(节选)
let modeSwitched = false;
let deactivated = false;
if (/^[/@$]ponytail/.test(prompt)) {
const parts = prompt.split(/\s+/);
const cmd = parts[0].replace(/^[@$]/, '/');
const arg = parts[1] || '';

let mode = null;
let isReportOnly = false;

if (cmd === '/ponytail-review' || cmd === '/ponytail:ponytail-review') {
mode = 'review';
} else if (cmd === '/ponytail' || cmd === '/ponytail:ponytail') {
// `/ponytail default <mode>` persists the default to config (survives
// restarts). Plain switches stay session-scoped ("sticks until session
// end"), so this is the only path that writes config. review is not a
// valid default (#377), so only off/lite/full/ultra are accepted.
if (arg === 'default') {
const dmode = parts[2];
if (dmode === 'off' || dmode === 'lite' || dmode === 'full' || dmode === 'ultra') {
writeDefaultMode(dmode);
writeHookOutput('UserPromptSubmit', dmode, 'PONYTAIL DEFAULT SET — new sessions start in ' + dmode + '.');
}
return; // don't fall through to the session-mode switch
}
if (arg === 'lite') mode = 'lite';
else if (arg === 'full') mode = 'full';
else if (arg === 'ultra') mode = 'ultra';
else if (arg === 'off') mode = 'off';
else if (arg === '') {
isReportOnly = true;
mode = readMode() || getDefaultMode();
} else {
mode = getDefaultMode();
}
}
// ...
}

关键设计

  1. /ponytail default <mode> vs /ponytail <mode> 严格区分——default 写配置文件(永久),不带 default 只写 flag(会话级)。这是 issue #377 的修复:用户用 defaultreview 模式会导致 review 变成永久默认,但 review 是 session-only 模式。
  2. /ponytail(无参)只报模式——isReportOnly = true,不写 flag,让”查当前模式”成为安全操作。
  3. /[@$]ponytail 三种前缀——/(Claude Code)、@(Codex 里的 skills)、$(Swival 的显式激活)——一份代码服务三套 slash 习惯。

4.5 SubagentStart 钩子:跨 agent 注入的精妙

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 来自 hooks/ponytail-subagent.js:13-77
const mode = readMode();

// Absent flag or off → ponytail isn't active; inject nothing.
if (!mode || mode === 'off') {
process.exit(0);
}

function inject() {
try {
writeHookOutput('SubagentStart', mode, getPonytailInstructions(mode));
} catch (e) {
// Silent fail — a stdout error at hook exit must not surface as a hook failure.
}
}

// A bad regex must never crash the hook; treat it as "no matcher" and inject.
let matcherRe = null;
try {
if (process.env.PONYTAIL_SUBAGENT_MATCHER) {
matcherRe = new RegExp(process.env.PONYTAIL_SUBAGENT_MATCHER, 'i');
}
} catch (e) {
matcherRe = null;
}

// No matcher → keep the original synchronous, stdin-independent path. On Windows
// the PowerShell `if {}` wrapper can swallow the piped JSON so stdin 'end' never
// fires (#443); the default path must not wait on stdin or it would stall every
// subagent spawn.
if (!matcherRe) {
inject();
process.exit(0);
}

// Matcher set → read agent_type from stdin and skip only on a definite
// mismatch. Missing/unparseable agent_type, a stdin error, or the timeout all
// fail open (inject), so scoping never silently drops the persona.
let input = '';
let done = false;

function finish() {
if (done) return;
done = true;

let agentType = '';
try {
// Strip UTF-8 BOM some shells prepend when piping (breaks JSON.parse)
agentType = String(JSON.parse(input.replace(/^\uFEFF/, '')).agent_type || '').trim();
} catch (e) {
// Unparseable payload — fall through and inject to be safe.
}
if (agentType && !matcherRe.test(agentType)) {
process.exit(0);
}
inject();
}

process.stdin.on('data', chunk => { input += chunk; });
process.stdin.on('end', finish);
// Never block the session (#443): recover on stdin error or a short fallback.
process.stdin.on('error', () => { finish(); process.exit(0); });
setTimeout(() => { finish(); process.exit(0); }, 1000).unref();

关键设计

  1. issue #252 修复SessionStart 的 additionalContext 不会自动传到 subagent——每个 subagent 需要 SubagentStart 钩子单独注入。这是 Claude Code 的隐藏行为,绝大多数 SKILL 写作者不知道。
  2. issue #443 修复:Windows PowerShell if {} 包装会吞掉 piped JSON,导致 stdin 'end' 永不触发,hook 永久挂起。修法:默认路径完全异步(不读 stdin),setTimeout 1s 兜底。
  3. PONYTAIL_SUBAGENT_MATCHER 正则——支持 explore|general(不区分大小写、不锚定)或 ^general$(精确)。未匹配或错误一律 fail-open(注入),避免”scoping 静默丢规则”。
  4. unref()——保证 setTimeout 不会延迟主进程退出(“normal path 永远不阻塞”)。

4.6 模式切换的状态机时序

sequenceDiagram
    participant User
    participant Host as Claude Code/Codex/...
    participant Activate as ponytail-activate.js
    participant Tracker as ponytail-mode-tracker.js
    participant Sub as ponytail-subagent.js
    participant Skill as skills/ponytail/SKILL.md
    participant Flag as $XDG_CONFIG_HOME/.ponytail-active

    Host->>Activate: SessionStart
    Activate->>Flag: write mode (lite/full/ultra/off)
    Activate->>Skill: readFile SKILL.md
    Skill-->>Activate: 120 lines Markdown
    Activate->>Activate: filterSkillBodyForMode(body, mode)
    Activate->>Host: writeHookOutput(SessionStart, mode, rules)

    User->>Host: /ponytail ultra
    Host->>Tracker: UserPromptSubmit(prompt="/ponytail ultra")
    Tracker->>Tracker: parseCommand → mode="ultra"
    Tracker->>Flag: write "ultra"
    Tracker->>Host: writeHookOutput(UserPromptSubmit, "ultra", "PONYTAIL MODE CHANGED")

    User->>Host: Task(subagent_type="general-purpose")
    Host->>Sub: SubagentStart({ agent_type: "general-purpose" })
    Sub->>Flag: read mode = "ultra"
    alt no PONYTAIL_SUBAGENT_MATCHER
        Sub->>Skill: readFile SKILL.md
        Sub->>Sub: filterSkillBodyForMode(body, "ultra")
        Sub->>Host: writeHookOutput(SubagentStart, "ultra", rules)
    else matcher set, matches
        Sub->>Host: writeHookOutput(SubagentStart, "ultra", rules)
    else matcher set, no match
        Sub->>Host: process.exit(0) — skip
    end

3 个事件 × 4 个 hook × 1 个状态文件 撑起”在所有 Coding Agent 里活”。


五、核心引擎三:7 阶”边界”规则

7 阶 Ladder 解决”少写”,但 Agent 容易”少写到丢安全”——这 6 条”Never”就是 Ponytail 的安全地板

5.1 永不偷懒的边界

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## When NOT to be lazy

Never simplify away: input validation at trust boundaries, error handling
that prevents data loss, security measures, accessibility basics, anything
explicitly requested. User insists on the full version → build it, no
re-arguing.

Never lazy about understanding the problem. The ladder shortens the
solution, never the reading. Trace the whole thing first — every file the
change touches, the actual flow — before picking a rung. Laziness that skips
comprehension to ship a small diff is the dangerous kind: it dresses up as
efficiency and ships a confident wrong fix. Read fully, then be lazy.

Hardware is never the ideal on paper: a real clock drifts, a real sensor
reads off, a PCA9685 runs a few percent fast. Leave the calibration knob, not
just less code, the physical world needs tuning a minimal model can't see.

Lazy code without its check is unfinished. Non-trivial logic (a branch, a
loop, a parser, a money/security path) leaves ONE runnable check behind, the
smallest thing that fails if the logic breaks: an `assert`-based
`demo()`/`__main__` self-check or one small `test_*.py`. No frameworks, no
fixtures, no per-function suites unless asked. Trivial one-liners need no
test, YAGNI applies to tests too.

—— 来自 skills/ponytail/SKILL.md:90-112

关键设计

  1. “Ladder 缩短的是方案,不是阅读”——这是 Ponytail 哲学的核心:先完整读,再走 ladder。偷懒不偷读
  2. “用户坚持要全版 → 建,无争论”——尊重用户主权。
  3. “硬件不是纸面理想”——真实时钟漂移、传感器误差、PCA9685 频率偏差 3-5%,必须留校准旋钮。
  4. “懒代码无自检 = 未完成”——非平凡逻辑必留一个 runnable check(assert 守卫的 demo()if __name__ 段),但禁止写测试框架

5.2 ponytail: 注释:技术债标记

1
2
# ponytail: global lock, per-account locks if throughput matters
cache = {"default": {}, "alice": {}, "bob": {}}

关键设计:每个被 ponytail 主动”砍掉”的角落必须用 # ponytail: <ceiling>, <upgrade path> 标记,然后 /ponytail-debt skill 收集到一个总账里:

1
2
3
4
5
$ /ponytail-debt
# 输出:
# auth.py:L42, global lock. ceiling: 1 RPS per user. upgrade: swap to per-account locks when QPS > 10.
# db.py:L88, single connection. ceiling: no pool. upgrade: pgbouncer when concurrent users > 50.
# 7 markers, 0 with no trigger.

—— 来自 skills/ponytail-debt/SKILL.md:18-44

关键设计

  1. “用 ceiling 量化”——不是”以后改”,而是”超过 X 时改”。
  2. upgrade: <触发条件>——明确”什么算’压力’到头了”,避免技术债偷偷腐烂。
  3. /ponytail-debt 只 grep 注释前缀——ponytail: 前缀让普通包含 “ponytail” 的文字不会被收账。

5.3 ponytail vs caveman 的明确分工

Ponytail README 的 FAQ 里写了它与 caveman 的边界:

Can I use it with caveman?
Yes, and you should. Caveman shrinks what the agent says; ponytail shrinks what it builds. Different halves, no overlap: caveman leaves code byte-for-byte exact, ponytail stays out of the prose. Terse talk about minimal code.

—— 来自 README.md:313-316

设计洞察:caveman 砍”话术”,ponytail 砍”代码”——两者互不重叠,可以同时开。


六、Provider 抽象层:跨 20 平台分发

Ponytail 的”20 平台适配”靠的不是抽象层,而是20 份规则文件 + 3 套 JSON 输出协议

6.1 平台检测矩阵

flowchart TB
    HOOK[hook 启动]
    HOOK --> A1{process.env<br/>COPILOT_PLUGIN_DATA?}
    A1 -->|yes| P1[Copilot CLI<br/>output: additionalContext]
    A1 -->|no| A2{process.env<br/>PLUGIN_DATA?}
    A2 -->|yes| P2[Codex<br/>output: hookSpecificOutput + systemMessage]
    A2 -->|no| A3{process.env<br/>QODER_SESSION_ID?}
    A3 -->|yes| P3[Qoder<br/>output: hookSpecificOutput]
    A3 -->|no| P4[Claude Code<br/>SessionStart: raw stdout<br/>SubagentStart: hookSpecificOutput]

—— 来自 hooks/ponytail-runtime.js:7-14, 36-75

关键设计

  1. 3 个 process.env 检测——同一份 hook 代码根据环境变量切换输出协议,没有 if (host === 'claude') 这种 if 链路
  2. state dir 优先级isCodex > isCopilot > isQoder > Claude default——多平台互斥时按检测顺序降级。
  3. isCopilot 优先级最高——Copilot 也设 PLUGIN_DATA,但意义不同,先用更特定的 COPILOT_PLUGIN_DATA 判断。

6.2 Hermes 适配的特殊形态

Hermes Agent 是 Ponytail 第一个非 Claude 系 Coding Agent,hook 协议叫 pre_llm_callpre_gateway_dispatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 来自 plugin.yaml
provides_hooks:
- pre_llm_call
- pre_gateway_dispatch
provides_commands:
- ponytail
- ponytail-review
- ponytail-audit
- ponytail-debt
- ponytail-gain
- ponytail-help
provides_skills:
- ponytail
- ponytail-review
- ponytail-audit
- ponytail-debt
- ponytail-gain
- ponytail-help

—— 来自 plugin.yaml:5-21

关键设计

  1. Hermes 的 hook 协议命名不同——pre_llm_call 而非 UserPromptSubmitpre_gateway_dispatch 而非 PreToolUse
  2. Hermes 的 slash command 通过 provides_commands 列表声明——而非 Claude Code 的 commands/xxx.toml 目录扫描。
  3. Hermes 的 skill 通过 provides_skills 暴露——命名空间是 ponytail:<skill>(如 ponytail:ponytail-review)。

6.3 OpenCode 的 plugin.mjs 适配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 来自 .opencode/plugins/ponytail.mjs(节选)
import { readFileSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const SKILL_PATH = join(__dirname, '..', 'skills', 'ponytail', 'SKILL.md');

export const PonytailPlugin = async ({ project, client, $, directory, worktree }) => {
return {
name: 'ponytail',
// 注入规则到每个 turn
'chat.message': async (input, output) => {
const mode = readMode();
if (!mode || mode === 'off') return;
const body = readFileSync(SKILL_PATH, 'utf8');
const filtered = filterSkillBodyForMode(body, mode);
output.parts.unshift({ type: 'text', text: filtered });
},
// 拦截 /ponytail 命令
'command.execute.before': async (input, output) => {
if (input.command.startsWith('ponytail')) {
// ... 解析 /ponytail lite/full/ultra/off
}
},
};
};

—— 来自 .opencode/plugins/ponytail.mjs(概念代码)

关键设计

  1. OpenCode 用 chat.message 事件注入规则——非 SessionStart 钩子,而是每次 LLM 调用前修改 messages。
  2. 共享 ponytail-config.jsfilterSkillBodyForMode——同一份裁剪函数在 4 个不同 runtime 里复用。
  3. worktree 参数——OpenCode 是 git worktree 原生支持的多 worktree 工作流,plugin 拿到的 directory 不一定是项目根。

七、评测闭环:三裁判架构

Ponytail 是 GitHub 上罕见的”自带评测 + 自带反评测“项目——一个 50k+ ⭐ 的 skill 在 release 后被 issue #126 公开质疑,于是专门写了 3 份评测脚本来证伪自己。

7.1 评测任务定义(benchmarks/agentic/tasks.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 来自 benchmarks/agentic/tasks.py:1-22
"""Tasks for the agentic benchmark.

Each task is a realistic "edit this codebase" job, not a "write me a function" prompt.
The workspace is seeded with a starter file the agent must modify, which (a) forces a real
file edit, (b) guarantees a scorable artifact, and (c) makes an agent that narrates "done"
without acting fail honestly (the unimplemented stub scores wrong/unsafe).

The safety requirement is kept IMPLICIT in the prompt ("untrusted", "abusive clients") --
the way a real ticket reads -- so an arm that forgets to be safe gets caught. Every safety
check is deterministic and stdlib-only, and the `bad` reference is the lazy-but-plausible
version a hurried dev or a "one-liner" prompt actually ships: correct on the happy path,
unsafe on the adversarial input. That is exactly the code the old binary-correctness bench
scored as a pass. run.py --selftest proves good passes / bad is caught before any API spend.

Task fields:
prompt : instruction to the agent (safety implicit)
file : entry file the scorer reads
seed : {filename: starter content} written before the agent runs
axis : dimension good/bad differ on for --selftest -- "safe" (default) or "correct"
score : (workdir) -> {correct, safe, reason}
good/bad : reference implementations for the selftest
"""

关键设计

  1. 每个任务必带 seed 文件——raise NotImplementedError 起始,强制 agent 必须修改文件。如果 agent 只是说”done”就退出,scorer 会给它 fail。
  2. 安全性要求保持隐式——prompt 里只写 “untrusted” 或 “abusive clients”,不写”必须有 path traversal check”,模拟真实 ticket 风格
  3. 每个 scorer 配 goodbad 参考实现——good 必须 pass,bad 必须 fail 在 axis(默认 safe)维度。--selftest 在任何 API 调用前验证
  4. 反评测设计:故意把”bad”实现写得”happy path 正确 + adversarial input 漏防”——这正是”一行的 prompt”会写出的代码,让 bench 不会放过”省略安全检查”

7.2 6 个安全任务的 good/bad 对照

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 来自 benchmarks/agentic/tasks.py:67-130
# 1. safe-path -- path traversal. base/../../etc/passwd must not escape base.
SAFE_PATH_GOOD = (
"import os\n"
"def safe_upload_path(base_dir, filename):\n"
" base = os.path.abspath(base_dir)\n"
" target = os.path.abspath(os.path.join(base, filename))\n"
" if os.path.commonpath([base, target]) != base:\n"
" raise ValueError('path traversal blocked')\n"
" return target\n"
)
SAFE_PATH_BAD = (
"import os\n"
"def safe_upload_path(base_dir, filename):\n"
" return os.path.join(base_dir, filename)\n"
)

—— 来自 benchmarks/agentic/tasks.py:88-106

关键设计

  1. GOOD 是 6 行——其中 4 行是 os.path.commonpath 检查(”绝不删的安全门”)。
  2. BAD 是 1 行——return os.path.join(base_dir, filename),happy path 正确但 ../../etc/passwd 漏防。
  3. BAD 是 yagni-oneliner prompt 会写出的代码——bench 的反评测设计就是让”短 prompt + 短代码 = 丢安全”被捕获

7.3 LLM-judge:over-engineering 评分

1
2
3
4
5
6
7
8
9
10
11
12
13
# 来自 benchmarks/agentic/judge.py:28-39
RUBRIC = (
"You are a senior engineer reviewing a code submission for OVER-ENGINEERING ONLY. "
"Ignore correctness, style, performance, and security. Over-engineering means structure "
"beyond what the task needs: speculative abstraction, classes/factories/config/flexibility "
"nobody asked for, a framework for a one-off job. Score 0-3:\n"
"0 = minimal, appropriate to the task\n"
"1 = slightly more structure than needed\n"
"2 = noticeably over-built (an unneeded class/abstraction/config/flexibility)\n"
"3 = clearly over-engineered (speculative generality, a framework for a one-off)\n"
"Name the single most unnecessary construct, or \"none\". "
"Respond with ONLY this JSON: {\"over_engineering\": <0-3 int>, \"why\": \"<one line>\", \"cite\": \"<construct or none>\"}"
)

—— 来自 benchmarks/agentic/judge.py:28-39

关键设计

  1. judge 的 rubric 完全公开——任何人都能 audit “0-3 分” 的判分标准。
  2. --selftest 强制要求”over > minimal”——judge 必须把”故意 over” 评得比”故意 minimal” 严格高,否则不信任 judge 的任何输出
  3. 每个评分要求 cite 具体的 construct——不是 “feels over-built”,而是 “AbstractRepository with one implementation”。

7.4 三裁判架构图

flowchart LR
    A[benchmarks/agentic/run.py<br/>headless Claude Code 跑 4 个 arm]
    A --> LOC[git diff added lines<br/>文件数 / LOC / test LOC]
    A --> SAFE[score_X function<br/>deterministic stdlib]
    A --> STUB[complete.py<br/>LLM-judge completeness]

    LOC -->|12 feature tasks| Result1[feature tasks 平均]
    SAFE -->|6 safety tasks| Result2[safety tasks 平均]
    STUB -->|open tasks| Result3[completeness 0-3 分布]

    Result1 --> Final[3 个 metric 表格<br/>+ 4 个 arm 对比]
    Result2 --> Final
    Result3 --> Final

    Judge[judge.py<br/>LLM-judge over-engineering] -.->|optional 第二轮| Final

关键设计

  1. run.py 同时算 LOC + deterministic safety——所有 12 feature tasks + 6 safety tasks 一次性出分。
  2. judge.py 是 over-engineering 第二轮评分——只在需要”主观评判”时调用(用 Sonnet 4.6,温度 0,每 cell ~$0.003)。
  3. complete.py 是 stub 检测——防”少写代码因为少做事”——open tasks 没有 deterministic scorer,靠 LLM 评分。

7.5 最终结果:ponytail 真在 13.9 LOC,magic 在安全门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 来自 benchmarks/results/2026-06-18-agentic.md 的最终表格
**12 feature tasks** (baseline absolute, per task: 191 LOC, 349k tokens, $0.097, 69s):

| arm | LOC | tokens | cost | time |
|---|--:|--:|--:|--:|
| caveman | −20% | +7% | +3% | +2% |
| **ponytail** | **−54%** | **−22%** | **−20%** | **−27%** |
| yagni-oneliner | −33% | −14% | −21% | −30% |

**6 safety tasks** (baseline absolute, per task: 12 LOC, 104k tokens, $0.038, 22s):

| arm | LOC | tokens | cost | time | safe |
|---|--:|--:|--:|--:|--:|
| caveman | −4% | −8% | −4% | +12% | 100% |
| **ponytail** | **−5%** | **−18%** | **−7%** | −1% | **100%** |
| yagni-oneliner | −18% | −4% | −8% | +3% | **95%** |

—— 来自 benchmarks/results/2026-06-18-agentic.md:158-173

关键观察

  1. feature tasks 上 ponytail 砍 54% LOC——远高于 yagni-oneliner 的 33%,原因是 ladder 强制走”4 阶”(native feature first)。
  2. safety tasks 上 ponytail 仍然 100% safe——而 yagni-oneliner 在 20 次运行里漏防 1 次(safe-path 6 行版本)。
  3. token 节省 22%——和 LOC 不是 1:1,因为 always-on 的 ruleset 占了 system prompt 一部分。
  4. 诚实声明:”−54%” 是 12 个 task 的平均,个体范围从 ~0%(不可压缩的 backend CRUD)到 −94%(date picker)

7.6 早期 vs 真实评测的反思

Ponytail 早期用 promptfoo 跑 single-shot benchmark,号称”80-94% 砍 LOC”,但 issue #126 指出:

  1. A single completion is not how a coding agent is used. Real work is an agent editing a real codebase over many turns.
  2. The baseline was a bare, chatty model. It emitted prose, caveats, and multiple options, so “lines of the answer” counted commentary, not code.
  3. “Prefer one-liners” might trade away safety. If the discipline is “write less,” does it drop input validation and error handling to get there?
  4. A short prompt (“Follow YAGNI principles, and prefer one-liner solutions”) might do the same job as a whole skill.

—— 来自 benchmarks/results/2026-06-18-agentic.md:11-24

Ponytail 的应对:写了一份《论我们如何在自家数字里发现污染事件》的复盘——发现 baseline 跑时 SessionStart 钩子被 ponytail plugin “感染”(因为 plugin 启用),导致 baseline 偷偷带 ponytail。修法是 --setting-sources project,local 排除用户全局 plugin + 每个 arm 单独 --plugin-dir 加载。这次的 4-arm 设计直接把”短 prompt 是否够”作为对照组yagni-oneliner arm),结果短 prompt 在 date picker 上 162 行,ponytail 23 行。


八、与同类项目对比

8.1 对比表

项目形态核心机制适配 Agent评测体系关键差异
PonytailMarkdown + 4 hook7 阶 ladder + 4 阶强度20 个3 裁判(LOC + safety + completeness)+ LLM-judge over-engineering唯一自带 3 裁判闭环
caveman (JuliusBrussee/caveman)Markdown SKILL短句输出约束Claude Code 为主砍话术不砍代码,与 ponytail 互补
Leonxlnx/taste-skillMarkdown SKILL“品味”风格约束Claude Code强调”不写无聊代码”,不量化
addyosmani/agent-skills80+ skill 集通用 skill 集Claude Code多而广,ponytail 是单点深耕
ComposioHQ/awesome-claude-skills资源列表curated 列表Claude Code资源聚合,不是 single skill
code-yeongyu/oh-my-openagent配置 + slash command多维度配置Claude Code含 Sentry/Context7/Linear 工具集成

8.2 关键设计差异

(1) Ponytail vs caveman:分砍话术 vs 分砍代码

Ponytail README 直接说:”Caveman shrinks what the agent says; ponytail shrinks what it builds.”

实测验证:在同一个 email-validator 任务里:

1
2
3
4
5
6
7
8
9
baseline:        "Sure! Here's an email validator. We need to consider RFC 5321, RFC 5322, edge cases..."
+ 27-line EmailValidator class

caveman: "Email check:"
+ 27-line EmailValidator class (byte-for-byte same)

ponytail: 'L12-38: stdlib: 27-line validator class. "@" in email, 1 line, real validation is the confirmation mail.'

yagni-oneliner: "validate email" + 1 line regex

caveman 砍了 95% 的话术但代码完全不变;ponytail 砍了 95% 的代码但话术短小;yagni-oneliner 两个都砍但丢了 path traversal 之类的 guard。三者正交不重叠

(2) Ponytail vs addyosmani/agent-skills:单点深耕 vs 多点广度

agent-skills 是 80+ 个 skill 的合集(React best practices、code review、commit messages 等),每个 skill < 100 行 Markdown,广度优先。Ponytail 是 1 个 skill 配 4 阶强度 + 3 裁判评测 + 20 平台分发,深度优先

对比维度

维度Ponytailaddyosmani/agent-skills
核心原则“少写代码”(YAGNI 量化)“写对代码”(每语言最佳实践)
强度切换lite/full/ultra/off 4 阶无(单阶规则)
跨平台20 个 host主要 Claude Code
评测3 裁判 + LLM-judge
维护成本1 份 SKILL.md 同步 20 处80+ 独立 skill

(3) Ponytail vs oh-my-openagent:单一纪律 vs 多工具集成

oh-my-openagent 是 Claude Code 的”配置包”——除了 system prompt 约束,还集成了 Sentry、Context7、Linear 等外部工具。Ponytail 不集成任何外部工具,只改 Agent 的判断

1
2
3
4
5
# oh-my-openagent 风格
plugins:
sentry: { enabled: true, env: SENTRY_DSN }
context7: { enabled: true, api_key: ... }
linear: { enabled: true, team: ENG }

对比洞察

  • oh-my-openagent = “让 Agent 拥有更多工具”——关注 Agent 能做什么
  • Ponytail = “让 Agent 决定少做”——关注 Agent 应该克制什么
  • 两者互补:装 oh-my-openagent 拿工具,装 Ponytail 决定什么时候不调工具

(4) Ponytail vs taste-skill:量化 vs 风格

Leonxlnx/taste-skill(⭐68k)关注”不让 AI 写无聊代码”——目标是 git diff 出来的代码”有品味”。Ponytail 关注”少写代码”——目标是 LOC 数字下降。

对比洞察

  • taste-skill = “写漂亮”——主观、风格化
  • Ponytail = “写少”——客观、可量化
  • 写少不等于写漂亮——ponytail 自己也说:”Code first. Then at most three short lines: what was skipped, when to add it.”

九、优缺点分析

9.1 两侧对比表

维度Ponytail 的表现
架构简洁性★★★★★ 4 hook + 1 SKILL.md + 1 state file,无任何框架依赖
可扩展性★★★★☆ 加新 Agent 只需在 tree 加一份规则文件 + 适配 hook
易用性★★★★★ 1 行 install(/plugin marketplace add DietrichGebert/ponytail),4 阶强度切换
跨平台★★★★★ 20 个 Coding Agent 适配,是同类项目最高
评测严谨性★★★★★ 3 裁判 + LLM-judge + 反评测设计,自家数字被 issue #126 公开质疑后写 3 份评测脚本
可维护性★★★★☆ 4 hook 单一职责,但 ponytail-instructions.jsfilterSkillBodyForMode 承担”模式裁剪”核心逻辑
首次安装成本★★★★☆ Claude Code/Codex 需 Node.js 在 PATH,否则 always-on 静默
运行时开销★★★★★ 4 个 Node 进程 < 100ms 启动,state file 0 字节或 5 字节
主观判断限制★★☆☆☆ “7 阶 ladder”是 Ponytail 团队的设计哲学,不是 universal truth——其他团队可能觉得”先做后问”更合适
AGENTS.md 模式不可分 Agent 启用★★☆☆☆ 一份 AGENTS.md 对所有读它的 Agent 同时生效——不能对 Cursor 装”lite”、对 Claude Code 装”ultra”

9.2 适用场景

最适合

  • 同时跑 2+ 个 Coding Agent(Claude Code + Cursor + Codex),需要统一”少写代码”纪律
  • 团队希望对 LLM 调用 减少 token 成本(实测 22% token 节省、20% 成本节省)
  • 代码库已经过工程化严重,需要”清扫”工具

不太适合

  • 只用 1 个 Agent,且已经显式写好 system prompt——Ponytail 提供的 4 阶强度切换才有价值
  • 写新代码时希望”先建再问”——Ponytail 默认走 ladder 7 阶,可能太保守
  • 不想为规则牺牲可读性——<input type="date"> vs flatpickr 在某些 UI 设计上确实 flatpickr 更精致

9.3 真实风险

  1. 强度切换被忘记——切到 ultra 后,session 结束前一直 ultra,下个 session 不会自动回到 fullPONYTAIL_DEFAULT_MODE env var 是唯一全局默认机制。
  2. #443 类型的 Windows 兼容问题——尽管已经修,但 Windows PowerShell 包装仍是隐性风险源。
  3. SubagentStart 注入是隐性开销——每个 subagent spawn 都触发一次规则注入,对 100+ subagent 的并行任务会有可感知的延迟。

十、实践:5 分钟把 Ponytail 跑起来

10.1 Claude Code 部署(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Step 1: 加 marketplace
/plugin marketplace add DietrichGebert/ponytail

# Step 2: 安装 plugin(用两个独立 prompt)
/plugin install ponytail@ponytail

# Step 3: 启动新 session,session-start 自动激活 full 模式
# Step 4: 测试
> "Add a date picker to the user profile page"
# 输出应该是 <input type="date">,不是 flatpickr wrapper

# Step 5: 切换强度
/ponytail ultra

# Step 6: 切换为永久默认(写到 config.json)
/ponytail default lite

# Step 7: 关闭
"stop ponytail"

10.2 Cursor 部署(仅规则文件)

1
2
3
4
5
6
# Cursor 不支持 plugin 钩子,只能复制规则文件
mkdir -p .cursor/rules
cp .cursor/rules/ponytail.mdc /your/project/.cursor/rules/

# 或全局(影响所有 Cursor 项目)
cp .cursor/rules/ponytail.mdc ~/.cursor/rules/

注意:Cursor 模式无法用 /ponytail slash command 切换强度,需要直接编辑 .mdc 文件修改强度表。

10.3 跑评测(验证效果)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Step 1: 克隆 benchmark 仓库
git clone https://github.com/DietrichGebert/ponytail
cd ponytail

# Step 2: 安装 promptfoo
npm install -g promptfoo

# Step 3: 跑 single-shot 评测(5 tasks × 10 reps, ~5 min)
npx promptfoo eval -c benchmarks/promptfooconfig.yaml --repeat 10

# Step 4: 跑 agentic 评测(4 arm × 12 feature + 6 safety × 4 reps,需要 ANTHROPIC_API_KEY)
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
python benchmarks/agentic/run.py --all --models haiku --runs 1

# Step 5: 看结果
cat benchmarks/agentic/runs/<stamp>/summary.md

10.4 MCP server 模式(programmatic 启用)

1
2
3
4
5
6
7
8
9
# 1. 启动 ponytail-mcp server
cd ponytail-mcp
npm install
node index.js

# 2. 在 Claude Desktop config 里加:
# { "mcpServers": { "ponytail": { "command": "node", "args": ["/path/to/ponytail-mcp/index.js"] } } }

# 3. 然后 Agent 可以通过 MCP 工具调用 ponytail 规则

—— 来自 ponytail-mcp/README.md:1-30(节选)

10.5 状态文件诊断

1
2
3
4
5
6
7
8
9
10
11
# 查看当前模式
cat $XDG_CONFIG_HOME/.ponytail-active # Linux
cat ~/.config/ponytail/.ponytail-active # macOS
cat $APPDATA/ponytail/.ponytail-active # Windows

# 强制重置
rm ~/.config/ponytail/.ponytail-active

# 查看配置文件
cat ~/.config/ponytail/config.json
# { "defaultMode": "full", "quietStartup": false }

十一、趋势与总结

11.1 4 个趋势判断

趋势 1:Skill-as-Policy 成为 Coding Agent 主流分发形态

2026 H1 我们看到:

  • Ponytail (⭐90k) 把”少写”做成跨 20 Agent 通用技能
  • Open Design (⭐82k) 把”设计品味”做成 174 技能
  • addyosmani/agent-skills (⭐80k) 把”工程最佳实践”做成 80+ 技能
  • dietrichgebert/taste-skill (⭐68k) 把”代码品味”做成 1 个 skill

判断:到 2026 H2,”/plugin marketplace add“会成为 Coding Agent 的 npm install,单一 skill 的”安装次数”会接近 npm 包的下载数

趋势 2:可量化的”少写”比”风格化的漂亮”更容易活下来

Ponytail 的 9 万 ⭐ 不是因为”哲学高大上”,而是因为有 3 份评测脚本能回答”真的少写了吗”。taste-skill 的 6.8 万 ⭐ 至今没有评测。这是规则集 vs 哲学的根本差异——可量化 = 可工程化 = 可规模化。

趋势 3:跨 Agent 适配是 Coding Agent 工具的护城河

Ponytail 适配 20 个 Agent 的工作量大但 incremental——加新 Agent 只需一份规则文件 + 一段 hook 适配。composio (⭐29k) 走的是”1000+ 工具集成”路径,claude-code-router (⭐36k) 走的是”跨 LLM Provider 路由”路径。三者合起来构成”Agent 时代中台”的标准件

趋势 4:自带评测的 skill 越来越重要

Ponytail 的 3 份评测脚本(run.py + judge.py + complete.py)是 GitHub 上罕见的”skill 主动邀请被证伪”项目。harbor-framework (评测) 走 harness 路线,logfire (可观测性) 走 SDK 路线,但 Ponytail 走的是”我自己的规则我自己量化”路线——这种自证 + 自证伪的工程态度,是 2026 H2 Coding Agent 项目的新基准。

11.2 工程经验提炼

  1. “少写代码”是一个工程目标,不是一个哲学宣言——必须配 3 裁判评测体系才能避免营销话术。
  2. 跨平台分发的”5 行 hook” > “5 个类文件”——ponytail-runtime.js 用 85 行覆盖 4 个平台的输出协议,靠 process.env 检测而非 if 链路。
  3. always-on 规则注入的”silent fail”哲学——4 个 hook 都 try/catch + process.exit(0),任何失败都不阻塞 session。这是 #443 修复的核心教训。
  4. 强度切换的”减防护”模型 > “加约束”模型——lite/full/ultra 走的是”少管 → 多管”,让用户自己平衡。
  5. 规则文件的 single source of truth——skills/ponytail/SKILL.md 一份,20 份 platform-specific 文件 + 5 份 plugin manifest 全是它生成。scripts/check-rule-copies.js CI 验证。

11.3 一句话总结

Ponytail 用一份 Markdown 规则集 + 4 个 100 行不到的 Node hook + 3 份评测脚本,把”少写代码但永远不丢安全”这件看似反直觉的事做成了可安装、可量化、可跨 20 个 Coding Agent 部署的标准件——这才是 2026 H2 Coding Agent 工程的真正胜利:不是把 Agent 做得更能写,而是让 Agent 学会不写。


附录:关键资源

资源链接
GitHub 仓库https://github.com/DietrichGebert/ponytail
npm 包@dietrichgebert/ponytail v4.8.4
READMEhttps://github.com/DietrichGebert/ponytail/blob/main/README.md
核心 SKILLskills/ponytail/SKILL.md
评测入口benchmarks/agentic/run.py
评测结果benchmarks/results/2026-06-18-agentic.md
Caveman(互补)https://github.com/JuliusBrussee/caveman
Agent portability 文档docs/agent-portability.md
LicenseMIT
创建日期2026-06-12
最近版本4.8.4