Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions engine/hooks/cat-mode-default/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,22 @@ It stays silent when the flag is off or when the prompt already mentions
cat-mode anywhere (a parent that told the subagent to read it gets no
second copy). Same flag resolution as the prompt hook.

The shared registry keeps this hook in `warn` mode. Set
`CATSTACK_HOOK_MODE_CAT_MODE_DEFAULT=off|warn|stop` for a machine-local
override; `stop` turns the `PreToolUse` (Agent) companion into a real block
(exit 2) instead of rewriting the subagent's prompt. Detection or metrics
failures allow the harness action.

## Files

- `detect.py`: flag resolution, typed `/cat-mode` detection, context text.
- `claude_prompt_submit.py`: the Claude entrypoint; fail-open, never denies.
- `claude.prompt.hook.json`: settings fragment `install_claude_hook.py` merges.
- `claude_pretooluse_agent.py` + `claude.agent.hook.json`: the `PreToolUse`
(`Agent`) companion that carries the default into subagent prompts.
- `detect.py`: flag resolution, typed `/cat-mode` detection, context text,
and `detect(event)`, the SDK entrypoint returning `Finding` objects.
- `claude_prompt_submit.py` / `claude_pretooluse_agent.py`: thin calls into
`engine/hooks/_sdk/runtime.py`. The agent finding carries
`output={"updatedInput": ...}`, which the shared renderer emits as
`updatedInput` rather than its generic `additionalContext`.
- `claude.prompt.hook.json` / `claude.agent.hook.json`: settings fragments
`install_claude_hook.py` merges.
- `tests/fixtures/*.json`: one scenario each (fires / silent) with the
environment, optional `.env` content, and payload.
- `tests/fixtures/agent_*.json`: the same for the Agent-tool companion.
Expand Down
14 changes: 14 additions & 0 deletions engine/hooks/cat-mode-default/tests/test_agent_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import json
import os
import sys
import tempfile
import unittest
from contextlib import redirect_stderr, redirect_stdout
from unittest.mock import patch
Expand Down Expand Up @@ -150,6 +151,19 @@ def test_unrelated_prompt_does_not_count(self) -> None:


class FailOpenCase(unittest.TestCase):
def setUp(self) -> None:
self.tmp = tempfile.TemporaryDirectory()
self.metrics_env = patch.dict(
os.environ,
{"CATSTACK_HOOK_METRICS_DIR": self.tmp.name},
clear=False,
)
self.metrics_env.start()

def tearDown(self) -> None:
self.metrics_env.stop()
self.tmp.cleanup()

def test_malformed_stdin_prints_nothing(self) -> None:
out = io.StringIO()
with patch.object(sys, "stdin", io.StringIO("not json")):
Expand Down
13 changes: 13 additions & 0 deletions engine/hooks/cat-mode-default/tests/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,19 @@ def test_flags_missing_install_instead_of_dead_path(self) -> None:


class FailOpenCase(unittest.TestCase):
def setUp(self) -> None:
self.tmp = tempfile.TemporaryDirectory()
self.metrics_env = patch.dict(
os.environ,
{"CATSTACK_HOOK_METRICS_DIR": self.tmp.name},
clear=False,
)
self.metrics_env.start()

def tearDown(self) -> None:
self.metrics_env.stop()
self.tmp.cleanup()

def test_malformed_stdin_prints_nothing(self) -> None:
out = io.StringIO()
with patch.object(sys, "stdin", io.StringIO("not json")):
Expand Down
Loading