Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
36 changes: 25 additions & 11 deletions pretty_please/adapters/claude_code/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,25 @@
pretty-please hook. Existing settings are preserved; only the hooks
section is merged.

Claude Code hooks schema::

{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{"type": "command", "command": "<cmd>"}
]
}
]
}
}

Usage::

python -m pretty_please.adapters.claude_code.install

Or via the CLI entry point (if installed)::
Or via the CLI entry point::

pretty-please install-hook
"""
Expand Down Expand Up @@ -56,20 +70,20 @@ def install(settings_path: Path | None = None) -> Path:
)
return settings_path

hooks: list[dict] = settings.setdefault("hooks", [])
hooks: dict = settings.setdefault("hooks", {})
user_prompt_hooks: list = hooks.setdefault("UserPromptSubmit", [])

# Check if a pretty-please hook already exists
command = _hook_command()
already_installed = any(hook.get("command", "") == command for hook in hooks)

# Check if a pretty-please hook is already present anywhere in the matchers
already_installed = any(
h.get("command") == command
for matcher in user_prompt_hooks
for h in matcher.get("hooks", [])
)

if not already_installed:
hooks.append(
{
"event": "UserPromptSubmit",
"command": command,
}
)
settings["hooks"] = hooks
user_prompt_hooks.append({"hooks": [{"type": "command", "command": command}]})
settings_path.write_text(json.dumps(settings, indent=2))
print(f"pretty-please hook installed in {settings_path}")
else:
Expand Down
7 changes: 6 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ def test_install_hook_writes_settings(self, tmp_path):
path = tmp_path / "settings.json"
run("install-hook", "--path", str(path))
data = json.loads(path.read_text())
assert any(h.get("event") == "UserPromptSubmit" for h in data["hooks"])
matchers = data["hooks"]["UserPromptSubmit"]
assert any(
h.get("type") == "command"
for matcher in matchers
for h in matcher.get("hooks", [])
)

def test_install_hook_codex_exits_zero(self, tmp_path):
path = tmp_path / "hooks.json"
Expand Down
9 changes: 7 additions & 2 deletions tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ def test_installs_into_empty_dir(self, tmp_path):
settings_path = tmp_path / "settings.json"
install(settings_path)
data = json.loads(settings_path.read_text())
assert any(h.get("event") == "UserPromptSubmit" for h in data["hooks"])
matchers = data["hooks"]["UserPromptSubmit"]
assert any(
h.get("type") == "command"
for matcher in matchers
for h in matcher.get("hooks", [])
)

def test_preserves_existing_settings(self, tmp_path):
from pretty_please.adapters.claude_code.install import install
Expand All @@ -28,7 +33,7 @@ def test_idempotent(self, tmp_path):
install(settings_path)
install(settings_path)
data = json.loads(settings_path.read_text())
assert len(data["hooks"]) == 1
assert len(data["hooks"]["UserPromptSubmit"]) == 1

def test_bails_on_corrupt_json(self, tmp_path, capsys):
from pretty_please.adapters.claude_code.install import install
Expand Down
Loading