Skip to content

Ponytail hooks 能力调研

调研对象: DietrichGebert/ponytail ,本地克隆路径 D:\code\store\ponytail__DietrichGebert,调研日期 2026-08-31。

一句话结论: ponytail 的 hooks 全部是"指令注入型"生命周期钩子,它们不做任何代码检查、不拦截任何工具调用,只负责在正确的时机、把一份"懒资深工程师"规则文本(按 lite/full/ultra 强度过滤后)塞进主对话、子代理和系统提示词里,从而让 agent 少写代码、避免过度工程。

1. 仓库定位

ponytail 是一个面向约 20 种 agent 开发工具(Claude Code、Codex、GitHub Copilot CLI、OpenCode、Gemini CLI、Qoder、Hermes Agent、pi、Grok Build、Devin CLI 等)的插件。它的核心理念是一个"懒资深工程师"人格:写代码前先过一遍七级阶梯——

text
1. 这东西需要存在吗?        → 不需要就跳过(YAGNI)
2. 代码库里已经有了吗?      → 复用,不重写
3. 标准库能做吗?            → 用标准库
4. 平台原生特性能覆盖吗?    → 用原生特性
5. 已安装的依赖能解决吗?    → 用现成依赖
6. 能一行写完吗?            → 一行写完
7. 以上都不行:写能工作的最小实现

官方基准测试(真实 Claude Code 会话编辑 FastAPI + React 仓库,Haiku 4.5,n=4)显示平均少写约 54% 代码(过度工程场景最高 94%)、省约 20% 成本、快约 27%,同时不牺牲任何安全护栏(信任边界校验、防数据丢失的错误处理、安全、可访问性永不简化)。

而 hooks 在整个体系里的角色,就是把这套规则"常驻"地注入到 agent 的上下文中——skills 是按需触发的,hooks 是每回合都在场的。

2. hooks 总览

Claude Code / Codex 适配层的 hook 注册表在 hooks/claude-codex-hooks.json,共 3 个事件、3 个脚本:

hook 事件执行脚本触发时机核心作用
SessionStartponytail-activate.js会话启动、恢复、/clear、上下文压缩(matcher: startup|resume|clear|compact解析默认模式、写状态标志文件、向主对话注入规则集、一次性 statusline 安装提示
SubagentStartponytail-subagent.js每次通过 Task/Agent 工具派生子代理时向子代理注入同一份规则集(SessionStart 上下文天然到不了子代理)
UserPromptSubmitponytail-mode-tracker.js用户每提交一条 prompt 时识别 /ponytail 系列命令切换强度、识别"stop ponytail"停用、Qoder 宿主下兼职激活

三个 hook 都是 type: command 的 Node.js 脚本,超时 5 秒,全部遵循"best-effort、绝不阻塞会话"的契约——任何一步失败都静默吞掉(源码里大量 catch (e) {} 注释写着 silent fail)。

3. 逐个 hook 详解

3.1. SessionStart:ponytail-activate.js(激活钩子)

这是最重要的一个 hook,每次会话冷启动、恢复、清空、压缩后都会重新跑一遍,流程如下:

  1. 解析默认模式:优先级为环境变量 PONYTAIL_DEFAULT_MODE → 配置文件 ~/.config/ponytail/config.json(Windows 为 %APPDATA%\ponytail\config.json)的 defaultMode 字段 → 兜底 full。合法值只有 off / lite / full / ultra 四个运行时级别(review 是会话级模式,不允许做默认值)。
  2. off 短路退出:默认模式是 off 时直接清掉标志文件、不注入任何内容。
  3. 写状态标志文件:把当前模式写入 .ponytail-active 标志文件。Claude Code 下位于 ~/.claude(可用 CLAUDE_CONFIG_DIR 覆盖);Codex 下写 PLUGIN_DATA;Copilot 下写 COPILOT_PLUGIN_DATA;Qoder 下写 ~/.qoder。这个文件是全家的"单一状态源"——statusline 徽章、mode-tracker、subagent 注入器都读它。
  4. 注入规则集:调用 ponytail-instructions.js 生成规则文本,作为 SessionStart 的 additionalContext 输出给主对话。
  5. statusline 安装提示(仅一次):检测 settings.json 里有没有 statusLine 配置;没有就写一个 .ponytail-statusline-nudged 标志(保证提示最多出现一次,避免每会话骚扰),然后在注入内容末尾追加一段"STATUSLINE SETUP NEEDED",引导 agent 主动帮用户配置状态栏徽章。这里还有个安全细节:安装路径必须通过 isShellSafe 白名单校验(只允许普通路径字符),含 shell 元字符的路径不会拼进命令片段,改走手工配置提示,防止注入。

3.2. SubagentStart:ponytail-subagent.js(子代理注入钩子)

存在的理由写在脚本头注释里:SessionStart 注入的上下文只属于父线程,永远到不了子代理,不补这个 hook 的话每个 Task 派生的 agent 都是"ponytail 失忆"状态(issue #252)。

逻辑非常克制:

  1. .ponytail-active 标志文件,没有或者 off → 立即退出,什么都不注入。
  2. 可选作用域过滤(issue #506):环境变量 PONYTAIL_SUBAGENT_MATCHER 是一个不锚定、大小写不敏感的正则,与子代理的 agent_type 匹配。比如 explore|general 匹配两类、^general$ 精确匹配。fail-open 设计:正则非法、平台没上报 agent_type、stdin 出错、超时——全部回退为"照常注入",宁可多注入也不静默丢规则。
  3. 输出格式:Claude Code 原生宿主下 SubagentStart 事件必须用 hookSpecificOutput.additionalContext 的 JSON 形态输出,裸 stdout 会被丢弃。

3.3. UserPromptSubmit:ponytail-mode-tracker.js(模式跟踪钩子)

每条用户 prompt 都会触发,从 stdin 读取 JSON({ prompt: "..." }),做三件事:

  1. 识别 /ponytail 命令族
    • /ponytail lite|full|ultra|off → 会话级切换模式,写标志文件,输出确认"PONYTAIL MODE CHANGED — level: X";
    • /ponytail default <mode> → 持久化默认模式到配置文件(唯一会写配置的路径,重启后仍生效);
    • /ponytail(无参数)→ 只报告当前级别;
    • /ponytail-review → 进入 review 模式。
    • 命令前缀同时兼容 /@(Codex)、$ 三种写法。
  2. 识别自然语言停用:整条消息恰好是 stop ponytailnormal mode(忽略大小写和尾部标点)才停用。必须是完整独立命令——早期版本在消息任意位置匹配,导致用户说"加一个 normal mode 开关"时 ponytail 被误关。
  3. Qoder 宿主兼职激活:Qoder 没有 SessionStart 事件,所以这个 hook 在 Qoder 下身兼两职——首条 prompt 时初始化默认模式,之后每条 prompt 都注入完整规则集。

这个脚本还有一个精彩的防御性细节(issue #443):Windows 上 Claude Code 通过 PowerShell if {} 包装器执行 hook,可能吞掉管道输入导致 stdin 的 end 事件永不触发、hook 挂死、冻结整个会话。解法是 stdin error 回调 + 1 秒 setTimeout(...).unref() 兜底,超时后处理已收到的数据并退出。所有生命周期 hook 都遵守这个"永不阻塞"契约。

4. 注入的内容是什么

三个 hook 注入的都是同一份文本,由 ponytail-instructions.js 统一生成:

  • 单一事实源:直接读 skills/ponytail/SKILL.md,去掉 frontmatter,然后按当前强度做行级过滤——强度对照表(| **lite** | ... |)只保留当前模式那一行,工作示例(- lite: "...")也只保留当前模式的。普通规则行原样保留。这样 lite/full/ultra 三档共享一份 skill 文档,不会漂移。
  • 内嵌兜底:skill 文件读不到时,退回脚本里内嵌的一份完整规则文本(含七级阶梯、bug 修复要修根因、ponytail: 注释标记刻意简化、输出格式"代码优先、解释最多三行"、永不简化的安全清单等)。
  • review 模式特殊:它不注入阶梯规则,只声明"行为由 /ponytail-review skill 定义",走 skill 按需触发路径。

5. 状态栏脚本(配套但非 hook)

ponytail-statusline.sh / .ps1 是 Claude Code statusLine 命令,每次状态栏刷新时执行:读 .ponytail-active 标志文件,off/文件不存在就输出空,否则输出绿色 [PONYTAIL][PONYTAIL:LITE] 等徽章(ultra 用琥珀色突出)。它是 hook 写的标志文件的消费方,本身不参与生命周期。

6. 其它宿主的 hook 等价物

ponytail 的多宿主适配不是复制代码,而是同一套 hooks/*.js 共享模块 + 每个宿主一层薄适配:

宿主hook 机制触发时机与 Claude Code 版的差异
GitHub Copilot CLIcopilot-hooks.jsonsessionStart + userPromptSubmitted会话启动、每条 prompt无 SubagentStart 事件,输出用 additionalContext JSON,仅在 SessionStart 生效
Qoderqoder-hooks.json 模板:UserPromptSubmit + PreToolUse(matcher task|Task)每条 prompt、每次 Task 工具调用用 PreToolUse 模拟子代理注入;UserPromptSubmit 兼职激活(见 3.3)
OpenCode插件 ponytail.mjsexperimental.chat.system.transform + command.execute.before每次 LLM 调用前改写系统提示词;斜杠命令执行前持久化模式每回合把规则追加进 system prompt,模式标志存在 ~/.config/opencode/.ponytail-active
pi agentpi-extension/index.jspi.on("session_start")before_agent_startinputagent_start/agent_end会话开始、每个 agent 回合开始前、用户输入、agent 起止before_agent_start 直接改写 systemPrompt 注入;模式从会话 entry 历史恢复
Hermes Agentplugin.yaml 声明 pre_llm_call + pre_gateway_dispatch每次 LLM 调用前、网关分发前共享网关下建议用访问控制限制 /ponytail 命令
Gemini CLI / Antigravity无 hooks故意不提供 hooks/hooks.json:Gemini 会自动加载该路径,而其生命周期事件名不兼容;改为 AGENTS.md 常驻上下文 + commands
Grok Build无 hooksGrok 的 SessionStart 输出无法注入指令,改走 skills 自动触发

7. 设计模式与借鉴价值

调研下来,这套 hooks 体系有几个非常值得抄的设计:

  1. 指令注入,绝不拦截:Claude Code/Codex 适配层没有任何 PreToolUse 拦截、不 block 工具调用、不改写文件。hook 只做"在正确时机把规则放进上下文"这一件事,行为改变完全靠模型遵循注入的指令。副作用风险接近零,卸载即无痕。
  2. 单一状态源:一个 .ponytail-active 标志文件串起激活、跟踪、子代理注入、statusline 四个消费方,跨进程共享状态,简单到不会坏。
  3. 永不阻塞契约:所有 hook 5 秒超时、静默失败、stdin 超时兜底(专门为 Windows PowerShell 吞管道的 bug 打的补丁,issue #443)。hook 崩了最坏结果只是"这一回合没注入规则",绝不冻结会话。
  4. fail-open 的作用域过滤:子代理 matcher 一切不确定情况都回退为注入,宁可冗余注入也不静默丢规则。
  5. 一次性打扰预算:statusline 安装提示用标志文件保证用户最多见到一次,把"有用提示"和"骚扰"的边界量化了。
  6. 一套核心多宿主薄适配:宿主探测(环境变量特征)+ 输出格式适配(每宿主一种 JSON 形态)集中在 ponytail-runtime.js,规则生成集中在 ponytail-instructions.js,新增宿主只需写一层注册表适配。
  7. 文本即产品:注入的规则文本按强度做行级过滤、单一事实源(skill 文档)、内置兜底副本,保证各强度各宿主持久一致(仓库还配了 scripts/check-rule-copies.js 做多副本一致性校验)。

贡献者

The avatar of contributor named as ruan-cat ruan-cat

页面历史

最近更新