Skip to content
Closed
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ Full sourcing notes, including what was left out and why: [docs/provenance.md](d
| `new-file-callout` | A new untracked file at the repo root or under `scripts/`: the reply must name it and say why. |
| `agent-relay-attribution` | Advisory: facts relayed from a subagent's report must say so or be re-verified. |
| `scratchpad-collision` | Two agents writing the same scratchpad file within ten minutes: use a uniquely named file. |
| `ui-input-guard` | Synthetic keystrokes, clicks, or screen recording aimed at the user's own session: blocked unless a hands-off window is open, the screen is unlocked, and the user is idle. |
| `hook-freshness` | Advisory: the catstack checkout behind `~/.claude/hooks` is off `main` or behind `origin/main`, so merged hook fixes are not live on this machine. |
| `auto-pr` | catstack itself changed: tell the agent to open a PR, no request needed. |
| `cat-mode-default` | Every investigation or execution prompt, and every subagent prompt sent through the Agent tool: apply `cat-mode` without typing `/cat-mode`. Off unless `CATSTACK_CAT_MODE_DEFAULT=1` (env or `.env`; see `engine/hooks/cat-mode-default/README.md`). |
| `plan-discipline` | **Not installed yet** (needs Agent mode): block product `.py` writes after a declined SwitchMode; require "How we test" on new-module plans; no eval numbers without a verifying run; warn on semantic plan-churn. Spec: `engine/hooks/plan-discipline/README.md`. |
Expand Down
2 changes: 2 additions & 0 deletions docs/ecosystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ again.
| `new-file-callout` | hook |
| `agent-relay-attribution` | hook (advisory) |
| `scratchpad-collision` | hook |
| `ui-input-guard` | hook |
| `hook-freshness` | hook (advisory) |
| `engine/CLAUDE.core.md` | global hand-written Claude rules |
| `scripts/`, `always-on/`, `cursor/rules/` (repo root), root `install.sh` | runtime (engine-owned entrypoints at root for CI) |

Expand Down
6 changes: 6 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ link_item "hedge-runs-prove-it" "$REPO_DIR/engine/hooks/hedge-runs-prove-it" "$H
link_item "new-file-callout" "$REPO_DIR/engine/hooks/new-file-callout" "$HOME/.claude/hooks/new-file-callout"
link_item "agent-relay-attribution" "$REPO_DIR/engine/hooks/agent-relay-attribution" "$HOME/.claude/hooks/agent-relay-attribution"
link_item "scratchpad-collision" "$REPO_DIR/engine/hooks/scratchpad-collision" "$HOME/.claude/hooks/scratchpad-collision"
link_item "ui-input-guard" "$REPO_DIR/engine/hooks/ui-input-guard" "$HOME/.claude/hooks/ui-input-guard"
link_item "hook-freshness" "$REPO_DIR/engine/hooks/hook-freshness" "$HOME/.claude/hooks/hook-freshness"

echo "--- cursor hooks dir (\$HOME/.cursor/hooks) ---"
mkdir -p "$HOME/.cursor/hooks"
Expand All @@ -250,6 +252,7 @@ link_item "pr-schema-gate" "$REPO_DIR/engine/hooks/pr-schema-gate" "$HOME/.curso
link_item "wrong-check-reflect" "$REPO_DIR/engine/hooks/wrong-check-reflect" "$HOME/.cursor/hooks/wrong-check-reflect"
link_item "build-the-lever" "$REPO_DIR/engine/hooks/build-the-lever" "$HOME/.cursor/hooks/build-the-lever"
link_item "repeat-error-stop" "$REPO_DIR/engine/hooks/repeat-error-stop" "$HOME/.cursor/hooks/repeat-error-stop"
link_item "ui-input-guard" "$REPO_DIR/engine/hooks/ui-input-guard" "$HOME/.cursor/hooks/ui-input-guard"

echo "--- codex hooks (\$HOME/.codex/hooks) ---"
mkdir -p "$HOME/.codex/hooks"
Expand All @@ -260,6 +263,7 @@ link_item "pr-schema-gate" "$REPO_DIR/engine/hooks/pr-schema-gate" "$HOME/.codex
link_item "wrong-check-reflect" "$REPO_DIR/engine/hooks/wrong-check-reflect" "$HOME/.codex/hooks/wrong-check-reflect"
link_item "build-the-lever" "$REPO_DIR/engine/hooks/build-the-lever" "$HOME/.codex/hooks/build-the-lever"
link_item "repeat-error-stop" "$REPO_DIR/engine/hooks/repeat-error-stop" "$HOME/.codex/hooks/repeat-error-stop"
link_item "ui-input-guard" "$REPO_DIR/engine/hooks/ui-input-guard" "$HOME/.codex/hooks/ui-input-guard"

# Deleted worktrees leave symlinks behind that point into this repo but at a
# path that no longer exists (e.g. .worktrees/<gone>/engine/hooks/<name>).
Expand Down Expand Up @@ -334,6 +338,8 @@ python3 "$REPO_DIR/engine/hooks/hedge-runs-prove-it/install_claude_hook.py"
python3 "$REPO_DIR/engine/hooks/new-file-callout/install_claude_hook.py"
python3 "$REPO_DIR/engine/hooks/agent-relay-attribution/install_claude_hook.py"
python3 "$REPO_DIR/engine/hooks/scratchpad-collision/install_claude_hook.py"
python3 "$REPO_DIR/engine/hooks/ui-input-guard/install_claude_hook.py"
python3 "$REPO_DIR/engine/hooks/hook-freshness/install_claude_hook.py"
python3 "$REPO_DIR/scripts/prune_dead_hook_entries.py"

echo "--- cursor bug-complaint-leak merge (\$HOME/.cursor/hooks.json) ---"
Expand Down
18 changes: 18 additions & 0 deletions tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ def test_hedge_runs_prove_it_linked_and_stop_wired_for_claude(self):
stop = [h["command"] for e in settings["hooks"]["Stop"] for h in e["hooks"]]
self.assertTrue(any("hedge-runs-prove-it/claude_stop_check.py" in c for c in stop), stop)

def test_ui_input_guard_linked_and_pretooluse_wired_for_claude(self):
target = os.path.join(self.fake_home, ".claude", "hooks", "ui-input-guard")
self.assertTrue(os.path.islink(target), target)
self.assertEqual(os.readlink(target), hook_src("ui-input-guard"))
commands = self._claude_hook_commands("PreToolUse")
self.assertTrue(
any("ui-input-guard/claude_pretooluse_check.py" in c for c in commands), commands
)

def test_hook_freshness_linked_and_prompt_wired_for_claude(self):
target = os.path.join(self.fake_home, ".claude", "hooks", "hook-freshness")
self.assertTrue(os.path.islink(target), target)
self.assertEqual(os.readlink(target), hook_src("hook-freshness"))
commands = self._claude_hook_commands("UserPromptSubmit")
self.assertTrue(
any("hook-freshness/claude_prompt_submit.py" in c for c in commands), commands
)

def test_new_file_callout_linked_and_stop_wired_for_claude(self):
target = os.path.join(self.fake_home, ".claude", "hooks", "new-file-callout")
self.assertTrue(os.path.islink(target), target)
Expand Down
Loading