From b18da054defae74cf141a3fa06e70fc2addbf2f1 Mon Sep 17 00:00:00 2001 From: Reva Gomes Date: Wed, 12 Aug 2026 10:27:04 +0200 Subject: [PATCH 1/5] feat(kiro): add isKiro host detection and raw stdout output to hook runtime. --- hooks/ponytail-activate.js | 5 +++-- hooks/ponytail-runtime.js | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/hooks/ponytail-activate.js b/hooks/ponytail-activate.js index d54fbe4a..3b17cab8 100644 --- a/hooks/ponytail-activate.js +++ b/hooks/ponytail-activate.js @@ -14,6 +14,7 @@ const { clearMode, isCodex, isCopilot, + isKiro, setMode, writeHookOutput, } = require('./ponytail-runtime'); @@ -26,7 +27,7 @@ const mode = getDefaultMode(); // "off" mode — skip activation entirely, don't write flag or emit rules if (mode === 'off') { clearMode(); - const hookOutput = (isCodex || isCopilot) ? '' : 'OK'; + const hookOutput = (isCodex || isCopilot || isKiro) ? '' : 'OK'; writeHookOutput('SessionStart', 'off', hookOutput); process.exit(0); } @@ -42,7 +43,7 @@ try { let output = getPonytailInstructions(mode); // 3. Detect missing statusline config — nudge Claude to help set it up -if (!isCodex && !isCopilot) try { +if (!isCodex && !isCopilot && !isKiro) try { let hasStatusline = false; if (fs.existsSync(settingsPath)) { // Strip UTF-8 BOM some editors prepend on Windows (breaks JSON.parse) diff --git a/hooks/ponytail-runtime.js b/hooks/ponytail-runtime.js index db0c4776..3bfeb9be 100644 --- a/hooks/ponytail-runtime.js +++ b/hooks/ponytail-runtime.js @@ -20,6 +20,8 @@ const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA) || isVsCodeCopilotRoot(process.env.CLAUDE_PLUGIN_ROOT); const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA); const isQoder = !isCopilot && !isCodex && Boolean(process.env.QODER_SESSION_ID); +const isKiro = !isCopilot && !isCodex && !isQoder && + (process.env.PONYTAIL_HOST || '').toLowerCase() === 'kiro'; let stateDir = getClaudeDir(); if (isCodex) stateDir = process.env.PLUGIN_DATA; @@ -27,6 +29,8 @@ if (isCodex) stateDir = process.env.PLUGIN_DATA; // getClaudeDir() rather than building a path from undefined. if (isCopilot) stateDir = process.env.COPILOT_PLUGIN_DATA || getClaudeDir(); if (isQoder) stateDir = path.join(os.homedir(), '.qoder'); +// Kiro: state lives under KIRO_HOME (documented profile redirect) or ~/.kiro. +if (isKiro) stateDir = process.env.KIRO_HOME || path.join(os.homedir(), '.kiro'); const statePath = path.join(stateDir, STATE_FILE); @@ -79,6 +83,12 @@ function writeHookOutput(event, mode, context = '') { process.stdout.write(JSON.stringify(output)); return; } + // Kiro: raw stdout is forwarded as context for SessionStart and + // UserPromptSubmit hooks (exit 0). Same semantics as native Claude Code. + if (isKiro) { + process.stdout.write(context); + return; + } // Native Claude: SessionStart accepts raw stdout, but SubagentStart needs the // hookSpecificOutput JSON form or the context is dropped. if (event === 'SubagentStart') { @@ -93,6 +103,7 @@ module.exports = { clearMode, isCodex, isCopilot, + isKiro, isQoder, readMode, setMode, From d10eafcaf5a6e5d8bdb1b6727b36e3258f1d8bd0 Mon Sep 17 00:00:00 2001 From: Reva Gomes Date: Wed, 12 Aug 2026 10:27:10 +0200 Subject: [PATCH 2/5] feat(kiro): add Kiro v2 hook files for session activation, mode tracking, and subagent injection. --- .kiro/hooks/ponytail-activate.json | 13 +++++++++++ .kiro/hooks/ponytail-mode-tracker.json | 13 +++++++++++ .kiro/hooks/ponytail-subagent.json | 14 ++++++++++++ hooks/kiro-hooks.json | 31 ++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 .kiro/hooks/ponytail-activate.json create mode 100644 .kiro/hooks/ponytail-mode-tracker.json create mode 100644 .kiro/hooks/ponytail-subagent.json create mode 100644 hooks/kiro-hooks.json diff --git a/.kiro/hooks/ponytail-activate.json b/.kiro/hooks/ponytail-activate.json new file mode 100644 index 00000000..859cd409 --- /dev/null +++ b/.kiro/hooks/ponytail-activate.json @@ -0,0 +1,13 @@ +{ + "version": "v1", + "hooks": [ + { + "name": "Ponytail session activation", + "trigger": "SessionStart", + "action": { + "type": "command", + "command": "PONYTAIL_HOST=kiro node hooks/ponytail-activate.js" + } + } + ] +} diff --git a/.kiro/hooks/ponytail-mode-tracker.json b/.kiro/hooks/ponytail-mode-tracker.json new file mode 100644 index 00000000..4d614504 --- /dev/null +++ b/.kiro/hooks/ponytail-mode-tracker.json @@ -0,0 +1,13 @@ +{ + "version": "v1", + "hooks": [ + { + "name": "Ponytail mode tracker", + "trigger": "UserPromptSubmit", + "action": { + "type": "command", + "command": "PONYTAIL_HOST=kiro node hooks/ponytail-mode-tracker.js" + } + } + ] +} diff --git a/.kiro/hooks/ponytail-subagent.json b/.kiro/hooks/ponytail-subagent.json new file mode 100644 index 00000000..5869085b --- /dev/null +++ b/.kiro/hooks/ponytail-subagent.json @@ -0,0 +1,14 @@ +{ + "version": "v1", + "hooks": [ + { + "name": "Ponytail subagent injection", + "trigger": "PreToolUse", + "matcher": "orchestrate_subagent", + "action": { + "type": "command", + "command": "PONYTAIL_HOST=kiro node hooks/ponytail-subagent.js" + } + } + ] +} diff --git a/hooks/kiro-hooks.json b/hooks/kiro-hooks.json new file mode 100644 index 00000000..5350f72b --- /dev/null +++ b/hooks/kiro-hooks.json @@ -0,0 +1,31 @@ +{ + "_comment": "Reference template — copy the files from .kiro/hooks/ into your project's .kiro/hooks/ or ~/.kiro/hooks/ for global activation. Replace PONYTAIL_DIR with the path to your ponytail checkout if hooks are not relative to the project root.", + "version": "v1", + "hooks": [ + { + "name": "Ponytail session activation", + "trigger": "SessionStart", + "action": { + "type": "command", + "command": "PONYTAIL_HOST=kiro node PONYTAIL_DIR/hooks/ponytail-activate.js" + } + }, + { + "name": "Ponytail mode tracker", + "trigger": "UserPromptSubmit", + "action": { + "type": "command", + "command": "PONYTAIL_HOST=kiro node PONYTAIL_DIR/hooks/ponytail-mode-tracker.js" + } + }, + { + "name": "Ponytail subagent injection", + "trigger": "PreToolUse", + "matcher": "orchestrate_subagent", + "action": { + "type": "command", + "command": "PONYTAIL_HOST=kiro node PONYTAIL_DIR/hooks/ponytail-subagent.js" + } + } + ] +} From f96b299a70b53357591a91c78d7f8f0cda03fdc0 Mon Sep 17 00:00:00 2001 From: Reva Gomes Date: Wed, 12 Aug 2026 10:27:16 +0200 Subject: [PATCH 3/5] docs: add Kiro CLI install section and update agent portability map. --- README.md | 22 ++++++++++++++++++++-- docs/agent-portability.md | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c57811a0..98f74885 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,23 @@ Qoder auto-loads `AGENTS.md` from the repo root as always-on context, so running For full plugin-tier support (automatic mode activation + ruleset injection on every prompt), add the hooks from [`hooks/qoder-hooks.json`](hooks/qoder-hooks.json) to your `.qoder/settings.json`. Replace `PONYTAIL_DIR` with the path to your ponytail checkout. Qoder's `UserPromptSubmit` hook activates the default mode on first prompt and injects the ruleset every turn; `PreToolUse` with `task|Task` matcher injects the ruleset into subagents. Level switches (`/ponytail lite|full|ultra|off`) work automatically. +### Kiro CLI + +Copy the hook files from this repo into your project or globally: + +```bash +# Per-project (from a ponytail checkout): +cp -r .kiro/hooks/ /path/to/your/project/.kiro/hooks/ + +# Global (all Kiro sessions): +mkdir -p ~/.kiro/hooks +cp .kiro/hooks/ponytail-*.json ~/.kiro/hooks/ +``` + +The hooks reference the `hooks/` scripts relative to the project root, so either clone ponytail into your project or update the paths in the JSON files to point at your checkout (see [`hooks/kiro-hooks.json`](hooks/kiro-hooks.json) for a template with `PONYTAIL_DIR` placeholders). + +This gives full hook support: session activation, mode switching (`/ponytail lite|full|ultra|off`), and subagent injection via `PreToolUse`. The steering file (`.kiro/steering/ponytail.md`) remains as an instruction-only fallback — copy it to `~/.kiro/steering/` for always-on rules without hooks. + ### Antigravity CLI Google is renaming Gemini CLI to Antigravity CLI (the `agy` binary); the same extension installs there: @@ -273,7 +290,7 @@ While active, the ruleset is also injected into every subagent spawned via the A Cursor, Windsurf, Cline, GitHub Copilot Chat (the VS Code, JetBrains, and Visual Studio editor extension, not the standalone Copilot CLI covered under [Install](#install)), Aider, Kiro, Zed, CodeWhale, Swival, Qoder: copy the matching rules file from this repo ([`.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/), [`.qoder/rules/`](.qoder/rules/)). -Kiro: copy `.kiro/steering/ponytail.md` to `~/.kiro/steering/` (global) or `.kiro/steering/` in your project. +Kiro: copy `.kiro/steering/ponytail.md` to `~/.kiro/steering/` (global) or `.kiro/steering/` in your project. For full hook support (mode switching, subagent injection), see [Kiro CLI](#kiro-cli) under Install. GitHub Copilot CLI fallback (instruction-only mode): it reads `AGENTS.md` and `.github/copilot-instructions.md` in a project, or copy the rules into `~/.copilot/copilot-instructions.md` to run ponytail in every project. This path keeps always-on guidance, but does not add plugin mode switches or hooks. @@ -296,6 +313,7 @@ Which files map to which agent: [Agent portability](docs/agent-portability.md). | Devin CLI | `devin plugins remove ponytail` | | Grok Build | `grok plugin uninstall ponytail` | | Pi agent | `pi uninstall ponytail` | +| Kiro | Delete `.kiro/hooks/ponytail-*.json` and `.kiro/steering/ponytail.md` | | Cursor / Windsurf / Cline / Qoder / etc. | Delete the copied rule file | These remove the plugin's own files. They leave behind a small amount of state ponytail writes outside the plugin folder: the mode flag, `~/.config/ponytail/config.json`, and (if you accepted the setup nudge) a `statusLine` entry in `~/.claude/settings.json`. Run `node scripts/uninstall.js` to clean those up too. **Run it before the host remove command above** — the script is itself a plugin file, so removing the plugin first deletes it (or run it from a separate clone of this repo). It only removes the statusLine entry if it points at ponytail's own script, so a statusline you set up yourself is left untouched. @@ -311,7 +329,7 @@ These remove the plugin's own files. They leave behind a small amount of state p | `/ponytail-gain` | Show the measured impact scoreboard (less code, less cost, more speed) from the benchmark. | | `/ponytail-help` | Quick reference for the commands above. | -Commands need a skill-capable host (Claude Code, Codex, Devin CLI, OpenCode, Gemini, pi, Swival, Hermes Agent, Qoder, Grok Build). In Codex they're skills, invoke with `@` (`@ponytail-review`). The instruction-only adapters (Cursor, Windsurf, Cline, Copilot, Kiro, Antigravity) load the always-on ruleset without the commands. +Commands need a skill-capable host (Claude Code, Codex, Devin CLI, OpenCode, Gemini, pi, Swival, Hermes Agent, Qoder, Grok Build). In Codex they're skills, invoke with `@` (`@ponytail-review`). The instruction-only adapters (Cursor, Windsurf, Cline, Copilot, Antigravity) load the always-on ruleset without the commands. Kiro with hooks supports mode switching (`/ponytail lite|full|ultra|off`) but not the slash-command skills. ## Development diff --git a/docs/agent-portability.md b/docs/agent-portability.md index 08adfcdf..8a5681ec 100644 --- a/docs/agent-portability.md +++ b/docs/agent-portability.md @@ -27,7 +27,7 @@ to load in a given agent. | JetBrains Junie | `AGENTS.md` | Junie reads `AGENTS.md` once you point it there in Settings → Tools → Junie → Project Settings → Guidelines Path (not automatic yet); this repo ships `AGENTS.md`, and `.junie/guidelines.md` is Junie's legacy path. Instruction-tier. | | Amp (Sourcegraph) | `AGENTS.md` | Amp reads `AGENTS.md` from the working directory and parent directories up to `$HOME` (plus global config like `~/.config/amp/AGENTS.md`); falls back to `AGENT.md`/`CLAUDE.md`. Instruction-tier. | | Jules (Google) | `AGENTS.md` | Jules automatically reads `AGENTS.md` from the repository root. Instruction-tier. | -| Kiro | `.kiro/steering/ponytail.md` | Steering rule; copy globally or into a project. | +| Kiro | `.kiro/steering/ponytail.md`, `.kiro/hooks/ponytail-*.json`, `hooks/kiro-hooks.json`, `hooks/` | Instruction-only (steering file) or full hook support (v2 hooks for session activation, mode tracking, and subagent injection). Copy `.kiro/hooks/` into your project or `~/.kiro/hooks/` for global activation. Hooks wire to the shared `hooks/` scripts via `PONYTAIL_HOST=kiro`. | | Qoder | `.qoder/rules/ponytail.md`, `.qoder-plugin/plugin.json`, `hooks/qoder-hooks.json`, `skills/`, `AGENTS.md` | Qoder auto-loads `AGENTS.md` as always-on context; `.qoder/rules/ponytail.md` provides per-project rules; the plugin manifest points at `skills/` for the six ponytail skills (invoked as `/ponytail`, `/ponytail-review`, etc. via the Skill system). Full plugin-tier: `hooks/qoder-hooks.json` template registers `UserPromptSubmit` (mode activation + ruleset injection) and `PreToolUse` with `task|Task` matcher (subagent injection). Instruction-tier works from repo root with zero setup via `AGENTS.md`. | | Zed | `AGENTS.md` | Auto-includes `AGENTS.md` from the worktree root as one of its default rule files for the Agent Panel. Instruction-tier. | | Generic agents | `AGENTS.md` or `skills/*/SKILL.md` | Copy the compact rule file or load the skill files directly. | From 5dde7b05dc342adcca06058f329519a356b61d46 Mon Sep 17 00:00:00 2001 From: Reva Gomes Date: Wed, 12 Aug 2026 11:51:15 +0200 Subject: [PATCH 4/5] fix(kiro): remove non-functional PreToolUse subagent hook and document limitations. Kiro's PreToolUse stdout is for permission decisions, not context injection. The subagent hook was silently broken. Removed it and documented that subagent injection awaits a future Kiro trigger. Also noted Windows limitation (inline env var syntax requires POSIX shell) and added _note fields to shipped hook files clarifying relative path assumption. --- .kiro/hooks/ponytail-activate.json | 1 + .kiro/hooks/ponytail-mode-tracker.json | 1 + .kiro/hooks/ponytail-subagent.json | 14 -------------- README.md | 4 ++-- docs/agent-portability.md | 2 +- hooks/kiro-hooks.json | 11 +---------- 6 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 .kiro/hooks/ponytail-subagent.json diff --git a/.kiro/hooks/ponytail-activate.json b/.kiro/hooks/ponytail-activate.json index 859cd409..c112d3bd 100644 --- a/.kiro/hooks/ponytail-activate.json +++ b/.kiro/hooks/ponytail-activate.json @@ -1,5 +1,6 @@ { "version": "v1", + "_note": "Paths are relative to the project root. These files work when ponytail is cloned as the project. For other projects, use hooks/kiro-hooks.json as a template with absolute paths.", "hooks": [ { "name": "Ponytail session activation", diff --git a/.kiro/hooks/ponytail-mode-tracker.json b/.kiro/hooks/ponytail-mode-tracker.json index 4d614504..dd4946f5 100644 --- a/.kiro/hooks/ponytail-mode-tracker.json +++ b/.kiro/hooks/ponytail-mode-tracker.json @@ -1,5 +1,6 @@ { "version": "v1", + "_note": "Paths are relative to the project root. These files work when ponytail is cloned as the project. For other projects, use hooks/kiro-hooks.json as a template with absolute paths.", "hooks": [ { "name": "Ponytail mode tracker", diff --git a/.kiro/hooks/ponytail-subagent.json b/.kiro/hooks/ponytail-subagent.json deleted file mode 100644 index 5869085b..00000000 --- a/.kiro/hooks/ponytail-subagent.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": "v1", - "hooks": [ - { - "name": "Ponytail subagent injection", - "trigger": "PreToolUse", - "matcher": "orchestrate_subagent", - "action": { - "type": "command", - "command": "PONYTAIL_HOST=kiro node hooks/ponytail-subagent.js" - } - } - ] -} diff --git a/README.md b/README.md index 98f74885..b60bb552 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ cp .kiro/hooks/ponytail-*.json ~/.kiro/hooks/ The hooks reference the `hooks/` scripts relative to the project root, so either clone ponytail into your project or update the paths in the JSON files to point at your checkout (see [`hooks/kiro-hooks.json`](hooks/kiro-hooks.json) for a template with `PONYTAIL_DIR` placeholders). -This gives full hook support: session activation, mode switching (`/ponytail lite|full|ultra|off`), and subagent injection via `PreToolUse`. The steering file (`.kiro/steering/ponytail.md`) remains as an instruction-only fallback — copy it to `~/.kiro/steering/` for always-on rules without hooks. +This gives hook support: session activation and mode switching (`/ponytail lite|full|ultra|off`). Subagent injection is not supported (Kiro's `PreToolUse` stdout is for permission decisions, not context injection; a future Kiro trigger may enable it). The steering file (`.kiro/steering/ponytail.md`) remains as an instruction-only fallback — copy it to `~/.kiro/steering/` for always-on rules without hooks. Hook commands use inline env var syntax (`VAR=val node ...`), which requires a POSIX shell (Linux/macOS); Windows support depends on Kiro adding per-platform command fields. ### Antigravity CLI @@ -329,7 +329,7 @@ These remove the plugin's own files. They leave behind a small amount of state p | `/ponytail-gain` | Show the measured impact scoreboard (less code, less cost, more speed) from the benchmark. | | `/ponytail-help` | Quick reference for the commands above. | -Commands need a skill-capable host (Claude Code, Codex, Devin CLI, OpenCode, Gemini, pi, Swival, Hermes Agent, Qoder, Grok Build). In Codex they're skills, invoke with `@` (`@ponytail-review`). The instruction-only adapters (Cursor, Windsurf, Cline, Copilot, Antigravity) load the always-on ruleset without the commands. Kiro with hooks supports mode switching (`/ponytail lite|full|ultra|off`) but not the slash-command skills. +Commands need a skill-capable host (Claude Code, Codex, Devin CLI, OpenCode, Gemini, pi, Swival, Hermes Agent, Qoder, Grok Build). In Codex they're skills, invoke with `@` (`@ponytail-review`). The instruction-only adapters (Cursor, Windsurf, Cline, Copilot, Antigravity) load the always-on ruleset without the commands. Kiro with hooks supports mode switching (`/ponytail lite|full|ultra|off`) but not the slash-command skills or subagent injection. ## Development diff --git a/docs/agent-portability.md b/docs/agent-portability.md index 8a5681ec..c707357f 100644 --- a/docs/agent-portability.md +++ b/docs/agent-portability.md @@ -27,7 +27,7 @@ to load in a given agent. | JetBrains Junie | `AGENTS.md` | Junie reads `AGENTS.md` once you point it there in Settings → Tools → Junie → Project Settings → Guidelines Path (not automatic yet); this repo ships `AGENTS.md`, and `.junie/guidelines.md` is Junie's legacy path. Instruction-tier. | | Amp (Sourcegraph) | `AGENTS.md` | Amp reads `AGENTS.md` from the working directory and parent directories up to `$HOME` (plus global config like `~/.config/amp/AGENTS.md`); falls back to `AGENT.md`/`CLAUDE.md`. Instruction-tier. | | Jules (Google) | `AGENTS.md` | Jules automatically reads `AGENTS.md` from the repository root. Instruction-tier. | -| Kiro | `.kiro/steering/ponytail.md`, `.kiro/hooks/ponytail-*.json`, `hooks/kiro-hooks.json`, `hooks/` | Instruction-only (steering file) or full hook support (v2 hooks for session activation, mode tracking, and subagent injection). Copy `.kiro/hooks/` into your project or `~/.kiro/hooks/` for global activation. Hooks wire to the shared `hooks/` scripts via `PONYTAIL_HOST=kiro`. | +| Kiro | `.kiro/steering/ponytail.md`, `.kiro/hooks/ponytail-*.json`, `hooks/kiro-hooks.json`, `hooks/` | Instruction-only (steering file) or hook support (v2 hooks for session activation and mode tracking). Copy `.kiro/hooks/` into your project or `~/.kiro/hooks/` for global activation. Hooks wire to the shared `hooks/` scripts via `PONYTAIL_HOST=kiro`. Subagent injection not supported (Kiro's PreToolUse is for permission decisions only). | | Qoder | `.qoder/rules/ponytail.md`, `.qoder-plugin/plugin.json`, `hooks/qoder-hooks.json`, `skills/`, `AGENTS.md` | Qoder auto-loads `AGENTS.md` as always-on context; `.qoder/rules/ponytail.md` provides per-project rules; the plugin manifest points at `skills/` for the six ponytail skills (invoked as `/ponytail`, `/ponytail-review`, etc. via the Skill system). Full plugin-tier: `hooks/qoder-hooks.json` template registers `UserPromptSubmit` (mode activation + ruleset injection) and `PreToolUse` with `task|Task` matcher (subagent injection). Instruction-tier works from repo root with zero setup via `AGENTS.md`. | | Zed | `AGENTS.md` | Auto-includes `AGENTS.md` from the worktree root as one of its default rule files for the Agent Panel. Instruction-tier. | | Generic agents | `AGENTS.md` or `skills/*/SKILL.md` | Copy the compact rule file or load the skill files directly. | diff --git a/hooks/kiro-hooks.json b/hooks/kiro-hooks.json index 5350f72b..3230010c 100644 --- a/hooks/kiro-hooks.json +++ b/hooks/kiro-hooks.json @@ -1,5 +1,5 @@ { - "_comment": "Reference template — copy the files from .kiro/hooks/ into your project's .kiro/hooks/ or ~/.kiro/hooks/ for global activation. Replace PONYTAIL_DIR with the path to your ponytail checkout if hooks are not relative to the project root.", + "_comment": "Reference template — copy the individual hook entries into your project's .kiro/hooks/ or ~/.kiro/hooks/ for global activation. Replace PONYTAIL_DIR with the absolute path to your ponytail checkout. Subagent injection is not supported: Kiro's PreToolUse stdout is for permission decisions, not context injection.", "version": "v1", "hooks": [ { @@ -17,15 +17,6 @@ "type": "command", "command": "PONYTAIL_HOST=kiro node PONYTAIL_DIR/hooks/ponytail-mode-tracker.js" } - }, - { - "name": "Ponytail subagent injection", - "trigger": "PreToolUse", - "matcher": "orchestrate_subagent", - "action": { - "type": "command", - "command": "PONYTAIL_HOST=kiro node PONYTAIL_DIR/hooks/ponytail-subagent.js" - } } ] } From 7fdd6791f374471d5ac0f21a4d9227478f5f9170 Mon Sep 17 00:00:00 2001 From: Reva Gomes Date: Wed, 12 Aug 2026 11:51:23 +0200 Subject: [PATCH 5/5] test(kiro): add hook tests for isKiro detection, state dir, raw stdout, and host collision. --- tests/hooks.test.js | 67 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/hooks.test.js b/tests/hooks.test.js index 44ee9222..cb13c628 100644 --- a/tests/hooks.test.js +++ b/tests/hooks.test.js @@ -489,4 +489,71 @@ try { if (prevEnvModeRev === undefined) delete process.env.PONYTAIL_DEFAULT_MODE; else process.env.PONYTAIL_DEFAULT_MODE = prevEnvModeRev; } +// --- Kiro host support --- +// isKiro detection via PONYTAIL_HOST=kiro, state at ~/.kiro/, raw stdout output. +{ + const kiroHome = path.join(temp, 'kiro-home'); + fs.mkdirSync(path.join(kiroHome, '.kiro'), { recursive: true }); + const kiroEnv = { + HOME: kiroHome, + USERPROFILE: kiroHome, + PONYTAIL_HOST: 'kiro', + PONYTAIL_DEFAULT_MODE: 'full', + }; + const kiroState = path.join(kiroHome, '.kiro', '.ponytail-active'); + + // SessionStart: activates mode, writes raw stdout (not JSON) + let r = run('ponytail-activate.js', kiroEnv); + assert.equal(r.status, 0, 'kiro activate exit: ' + r.stderr); + assert.equal(fs.readFileSync(kiroState, 'utf8'), 'full'); + assert.match(r.stdout, /PONYTAIL MODE ACTIVE — level: full/); + // Raw text, not JSON + assert.throws(() => JSON.parse(r.stdout), 'Kiro output must be raw text, not JSON'); + + // Mode switch via UserPromptSubmit + r = run('ponytail-mode-tracker.js', kiroEnv, JSON.stringify({ prompt: '/ponytail ultra' })); + assert.equal(r.status, 0, 'kiro mode switch exit: ' + r.stderr); + assert.equal(fs.readFileSync(kiroState, 'utf8'), 'ultra'); + assert.match(r.stdout, /PONYTAIL MODE CHANGED — level: ultra/); + assert.throws(() => JSON.parse(r.stdout), 'Kiro mode output must be raw text, not JSON'); + + // Mode off clears state and emits PONYTAIL MODE OFF + r = run('ponytail-mode-tracker.js', kiroEnv, JSON.stringify({ prompt: '/ponytail off' })); + assert.equal(r.status, 0, 'kiro off exit: ' + r.stderr); + assert.equal(fs.existsSync(kiroState), false); + assert.match(r.stdout, /PONYTAIL MODE OFF/); + + // "off" activation emits empty string (not 'OK') + const kiroOffEnv = { ...kiroEnv, PONYTAIL_DEFAULT_MODE: 'off' }; + r = run('ponytail-activate.js', kiroOffEnv); + assert.equal(r.status, 0, 'kiro off activate: ' + r.stderr); + assert.equal(r.stdout, '', 'Kiro off mode must emit empty stdout'); + + // KIRO_HOME override for state dir + const kiroHomeOverride = path.join(temp, 'kiro-custom'); + fs.mkdirSync(kiroHomeOverride, { recursive: true }); + const kiroCustomEnv = { ...kiroEnv, KIRO_HOME: kiroHomeOverride, PONYTAIL_DEFAULT_MODE: 'lite' }; + r = run('ponytail-activate.js', kiroCustomEnv); + assert.equal(r.status, 0, 'kiro KIRO_HOME activate: ' + r.stderr); + assert.equal(fs.readFileSync(path.join(kiroHomeOverride, '.ponytail-active'), 'utf8'), 'lite'); + + // isKiro is false when PONYTAIL_HOST is not set (falls through to native Claude) + const notKiroEnv = { HOME: kiroHome, USERPROFILE: kiroHome, PONYTAIL_DEFAULT_MODE: 'full' }; + r = run('ponytail-activate.js', notKiroEnv); + assert.equal(r.status, 0, 'non-kiro activate: ' + r.stderr); + // State goes to ~/.claude, not ~/.kiro + assert.equal( + fs.readFileSync(path.join(kiroHome, '.claude', '.ponytail-active'), 'utf8'), + 'full', + ); + + // Host-collision: PONYTAIL_HOST=kiro + PLUGIN_DATA set → isCodex wins (Codex takes precedence) + const collisionEnv = { ...kiroEnv, PLUGIN_DATA: pluginData }; + r = run('ponytail-activate.js', collisionEnv); + assert.equal(r.status, 0, 'collision exit: ' + r.stderr); + let collisionOutput; + try { collisionOutput = JSON.parse(r.stdout); } catch (e) { collisionOutput = null; } + assert.notEqual(collisionOutput, null, 'Codex must win over PONYTAIL_HOST=kiro; output must be JSON'); +} + console.log('hook compatibility checks passed');