Avoid Gemini loading Claude hook events (#139)

This commit is contained in:
Sonai Biswas
2026-06-19 00:24:43 +02:00
committed by GitHub
parent 0ac987f995
commit 4dad14fac5
7 changed files with 31 additions and 5 deletions
+2 -1
View File
@@ -5,5 +5,6 @@
"author": { "author": {
"name": "Dietrich Gebert", "name": "Dietrich Gebert",
"url": "https://github.com/DietrichGebert" "url": "https://github.com/DietrichGebert"
} },
"hooks": "./hooks/claude-codex-hooks.json"
} }
+1
View File
@@ -11,6 +11,7 @@
"license": "MIT", "license": "MIT",
"keywords": ["yagni", "minimalism", "code-review", "productivity"], "keywords": ["yagni", "minimalism", "code-review", "productivity"],
"skills": "./skills/", "skills": "./skills/",
"hooks": "./hooks/claude-codex-hooks.json",
"interface": { "interface": {
"displayName": "Ponytail", "displayName": "Ponytail",
"shortDescription": "Lazy senior developer mode", "shortDescription": "Lazy senior developer mode",
+1
View File
@@ -163,6 +163,7 @@ gemini extensions install https://github.com/DietrichGebert/ponytail
``` ```
Loads the ruleset as always-on context every session and registers the `/ponytail` commands; the `skills/` ship too, activated when a task needs them. Loads the ruleset as always-on context every session and registers the `/ponytail` commands; the `skills/` ship too, activated when a task needs them.
The Gemini adapter intentionally does not ship a root `hooks/hooks.json`: Gemini auto-loads that path, while Ponytail's lifecycle hooks use Claude/Codex event names.
### Antigravity CLI ### Antigravity CLI
+3 -3
View File
@@ -8,11 +8,11 @@ to load in a given agent.
| Host | Files | Notes | | Host | Files | Notes |
|------|-------|-------| |------|-------|-------|
| Claude Code | `.claude-plugin/`, `commands/`, `hooks/` | Full plugin install with session activation, mode tracking, commands, and statusline support. | | Claude Code | `.claude-plugin/plugin.json`, `commands/`, `hooks/claude-codex-hooks.json`, `hooks/` | Full plugin install with session activation, mode tracking, commands, and statusline support. |
| Codex | `.codex-plugin/plugin.json`, `hooks/hooks.json`, `hooks/`, `skills/` | Plugin install with the same skills plus lifecycle hooks for activation and mode tracking. | | Codex | `.codex-plugin/plugin.json`, `hooks/claude-codex-hooks.json`, `hooks/`, `skills/` | Plugin install with the same skills plus lifecycle hooks for activation and mode tracking. |
| OpenCode | `.opencode/plugins/ponytail.mjs`, `.opencode/command/`, `hooks/`, `skills/` | Server plugin injects the ruleset each turn via `experimental.chat.system.transform` and persists `/ponytail` switches; reuses the shared instruction builder. | | OpenCode | `.opencode/plugins/ponytail.mjs`, `.opencode/command/`, `hooks/`, `skills/` | Server plugin injects the ruleset each turn via `experimental.chat.system.transform` and persists `/ponytail` switches; reuses the shared instruction builder. |
| pi | `pi-extension/`, `skills/`, `hooks/` | Package extension: injects the ruleset each turn through the shared instruction builder and registers the `/ponytail` commands. | | pi | `pi-extension/`, `skills/`, `hooks/` | Package extension: injects the ruleset each turn through the shared instruction builder and registers the `/ponytail` commands. |
| Gemini CLI | `gemini-extension.json`, `AGENTS.md`, `commands/`, `skills/` | Extension manifest points `contextFileName` at `AGENTS.md` for always-on rules, and reuses the existing `commands/*.toml` and `skills/`, which Gemini CLI auto-discovers. | | Gemini CLI | `gemini-extension.json`, `AGENTS.md`, `commands/`, `skills/` | Extension manifest points `contextFileName` at `AGENTS.md` for always-on rules, and reuses the existing `commands/*.toml` and `skills/`, which Gemini CLI auto-discovers. The Claude/Codex hook map is not placed at Gemini's auto-discovered `hooks/hooks.json` path. |
| Cursor | `.cursor/rules/ponytail.mdc` | Always-on project rule. | | Cursor | `.cursor/rules/ponytail.mdc` | Always-on project rule. |
| Windsurf | `.windsurf/rules/ponytail.md` | Project rule. | | Windsurf | `.windsurf/rules/ponytail.md` | Project rule. |
| Cline | `.clinerules/ponytail.md` | Project rule. | | Cline | `.clinerules/ponytail.md` | Project rule. |
+12
View File
@@ -25,6 +25,10 @@ const VERSIONED_MANIFESTS = [
// Gemini auto-discovers these by directory; the manifest is only useful if they exist. // Gemini auto-discovers these by directory; the manifest is only useful if they exist.
const REUSED_COMMANDS = ['commands/ponytail.toml', 'commands/ponytail-review.toml']; const REUSED_COMMANDS = ['commands/ponytail.toml', 'commands/ponytail-review.toml'];
const REUSED_SKILLS = ['skills/ponytail/SKILL.md']; const REUSED_SKILLS = ['skills/ponytail/SKILL.md'];
// Gemini CLI auto-loads this exact path for extension hooks. Ponytail's
// Claude/Codex hook map uses events Gemini does not support, so it must stay
// behind the host-specific plugin manifests instead.
const GEMINI_AUTO_HOOKS = 'hooks/hooks.json';
// Same load-bearing phrases asserted by scripts/check-rule-copies.js: the file // Same load-bearing phrases asserted by scripts/check-rule-copies.js: the file
// contextFileName points at must actually carry the rules, not just exist. // contextFileName points at must actually carry the rules, not just exist.
const RULE_INVARIANTS = [ const RULE_INVARIANTS = [
@@ -77,3 +81,11 @@ test('the commands and skills the adapter reuses are present', () => {
assert.ok(fs.existsSync(path.join(root, rel)), `reused file missing: ${rel}`); assert.ok(fs.existsSync(path.join(root, rel)), `reused file missing: ${rel}`);
} }
}); });
test('Gemini cannot auto-discover Claude/Codex hook events', () => {
assert.equal(
fs.existsSync(path.join(root, GEMINI_AUTO_HOOKS)),
false,
`${GEMINI_AUTO_HOOKS} is auto-loaded by Gemini CLI; keep Claude/Codex hooks on manifest paths`,
);
});
+12 -1
View File
@@ -11,7 +11,11 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const root = path.join(__dirname, '..'); const root = path.join(__dirname, '..');
const HOOKS_JSON = 'hooks/hooks.json'; const HOOKS_JSON = 'hooks/claude-codex-hooks.json';
const HOST_PLUGIN_MANIFESTS = [
'.claude-plugin/plugin.json',
'.codex-plugin/plugin.json',
];
// cmd.exe variable syntax (%FOO%); PowerShell leaves it literal, breaking the path. // cmd.exe variable syntax (%FOO%); PowerShell leaves it literal, breaking the path.
const CMD_VAR_SYNTAX = /%[A-Za-z_][A-Za-z0-9_]*%/; const CMD_VAR_SYNTAX = /%[A-Za-z_][A-Za-z0-9_]*%/;
// Pull the hooks/<script> a command launches, so we can check it exists. // Pull the hooks/<script> a command launches, so we can check it exists.
@@ -46,3 +50,10 @@ test('every hook command points at a script that ships in hooks/', () => {
} }
} }
}); });
test('Claude and Codex manifests point at the shared host-specific hook config', () => {
for (const rel of HOST_PLUGIN_MANIFESTS) {
const manifest = JSON.parse(fs.readFileSync(path.join(root, rel), 'utf8'));
assert.equal(manifest.hooks, `./${HOOKS_JSON}`, `${rel} must not rely on root hooks auto-discovery`);
}
});