claude_hooks 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/skills/ci-monitoring/SKILL.md +103 -0
  3. data/.agents/skills/run-tests/SKILL.md +45 -0
  4. data/AGENTS.md +46 -0
  5. data/CHANGELOG.md +64 -0
  6. data/README.md +303 -239
  7. data/docs/1.0.0_MIGRATION_GUIDE.md +6 -7
  8. data/docs/API/COMMON.md +7 -1
  9. data/docs/API/CONFIG_CHANGE.md +39 -0
  10. data/docs/API/CWD_CHANGED.md +34 -0
  11. data/docs/API/ELICITATION.md +44 -0
  12. data/docs/API/ELICITATION_RESULT.md +43 -0
  13. data/docs/API/FILE_CHANGED.md +37 -0
  14. data/docs/API/INSTRUCTIONS_LOADED.md +30 -0
  15. data/docs/API/MESSAGE_DISPLAY.md +38 -0
  16. data/docs/API/PERMISSION_DENIED.md +37 -0
  17. data/docs/API/PERMISSION_REQUEST.md +11 -8
  18. data/docs/API/POST_COMPACT.md +30 -0
  19. data/docs/API/POST_TOOL_BATCH.md +39 -0
  20. data/docs/API/POST_TOOL_USE_FAILURE.md +31 -0
  21. data/docs/API/PRE_COMPACT.md +10 -2
  22. data/docs/API/PRE_TOOL_USE.md +5 -1
  23. data/docs/API/SETUP.md +37 -0
  24. data/docs/API/STOP_FAILURE.md +19 -0
  25. data/docs/API/SUBAGENT_START.md +40 -0
  26. data/docs/API/TASK_COMPLETED.md +43 -0
  27. data/docs/API/TASK_CREATED.md +43 -0
  28. data/docs/API/TEAMMATE_IDLE.md +40 -0
  29. data/docs/API/USER_PROMPT_EXPANSION.md +43 -0
  30. data/docs/API/WORKTREE_CREATE.md +39 -0
  31. data/docs/API/WORKTREE_REMOVE.md +33 -0
  32. data/docs/PROMPT_BASED_HOOKS.md +386 -0
  33. data/docs/external/claude-hooks-reference.md +2406 -854
  34. data/docs/mitts/ideabox.md +12 -0
  35. data/docs/mitts/prd.md +417 -0
  36. data/docs/mitts/setup.md +303 -0
  37. data/docs/mitts/task.md +44 -0
  38. data/example_dotclaude/hooks/entrypoints/session_end.rb +5 -22
  39. data/example_dotclaude/hooks/entrypoints/user_prompt_submit.rb +4 -26
  40. data/example_dotclaude/hooks/{handlers/pre_tool_use/github_guard.rb → github_guard.rb} +7 -13
  41. data/example_dotclaude/settings.json +1 -1
  42. data/lib/claude_hooks/base.rb +25 -0
  43. data/lib/claude_hooks/cli.rb +84 -149
  44. data/lib/claude_hooks/config_change.rb +28 -0
  45. data/lib/claude_hooks/cwd_changed.rb +28 -0
  46. data/lib/claude_hooks/elicitation.rb +61 -0
  47. data/lib/claude_hooks/elicitation_result.rb +57 -0
  48. data/lib/claude_hooks/file_changed.rb +41 -0
  49. data/lib/claude_hooks/instructions_loaded.rb +23 -0
  50. data/lib/claude_hooks/message_display.rb +58 -0
  51. data/lib/claude_hooks/output/base.rb +46 -1
  52. data/lib/claude_hooks/output/config_change.rb +47 -0
  53. data/lib/claude_hooks/output/cwd_changed.rb +42 -0
  54. data/lib/claude_hooks/output/elicitation.rb +67 -0
  55. data/lib/claude_hooks/output/elicitation_result.rb +63 -0
  56. data/lib/claude_hooks/output/file_changed.rb +42 -0
  57. data/lib/claude_hooks/output/instructions_loaded.rb +19 -0
  58. data/lib/claude_hooks/output/message_display.rb +49 -0
  59. data/lib/claude_hooks/output/permission_denied.rb +42 -0
  60. data/lib/claude_hooks/output/permission_request.rb +50 -25
  61. data/lib/claude_hooks/output/post_compact.rb +18 -0
  62. data/lib/claude_hooks/output/post_tool_batch.rb +51 -0
  63. data/lib/claude_hooks/output/post_tool_use.rb +12 -0
  64. data/lib/claude_hooks/output/post_tool_use_failure.rb +42 -0
  65. data/lib/claude_hooks/output/pre_compact.rb +34 -3
  66. data/lib/claude_hooks/output/pre_tool_use.rb +8 -2
  67. data/lib/claude_hooks/output/session_start.rb +30 -8
  68. data/lib/claude_hooks/output/setup.rb +42 -0
  69. data/lib/claude_hooks/output/stop.rb +15 -0
  70. data/lib/claude_hooks/output/stop_failure.rb +19 -0
  71. data/lib/claude_hooks/output/subagent_start.rb +42 -0
  72. data/lib/claude_hooks/output/task_completed.rb +18 -0
  73. data/lib/claude_hooks/output/task_created.rb +18 -0
  74. data/lib/claude_hooks/output/teammate_idle.rb +18 -0
  75. data/lib/claude_hooks/output/user_prompt_expansion.rb +51 -0
  76. data/lib/claude_hooks/output/worktree_create.rb +60 -0
  77. data/lib/claude_hooks/output/worktree_remove.rb +19 -0
  78. data/lib/claude_hooks/permission_denied.rb +41 -0
  79. data/lib/claude_hooks/permission_request.rb +16 -9
  80. data/lib/claude_hooks/post_compact.rb +23 -0
  81. data/lib/claude_hooks/post_tool_batch.rb +59 -0
  82. data/lib/claude_hooks/post_tool_use.rb +10 -0
  83. data/lib/claude_hooks/post_tool_use_failure.rb +45 -0
  84. data/lib/claude_hooks/pre_compact.rb +7 -0
  85. data/lib/claude_hooks/pre_tool_use.rb +20 -2
  86. data/lib/claude_hooks/session_start.rb +28 -0
  87. data/lib/claude_hooks/setup.rb +25 -0
  88. data/lib/claude_hooks/stop.rb +17 -0
  89. data/lib/claude_hooks/stop_failure.rb +27 -0
  90. data/lib/claude_hooks/subagent_start.rb +23 -0
  91. data/lib/claude_hooks/subagent_stop.rb +5 -1
  92. data/lib/claude_hooks/task_completed.rb +35 -0
  93. data/lib/claude_hooks/task_created.rb +35 -0
  94. data/lib/claude_hooks/teammate_idle.rb +23 -0
  95. data/lib/claude_hooks/user_prompt_expansion.rb +45 -0
  96. data/lib/claude_hooks/version.rb +1 -1
  97. data/lib/claude_hooks/worktree_create.rb +27 -0
  98. data/lib/claude_hooks/worktree_remove.rb +19 -0
  99. data/lib/claude_hooks.rb +40 -0
  100. metadata +71 -4
  101. data/example_dotclaude/hooks/entrypoints/pre_tool_use.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7cd63a0fbf165b912baa3fab487e056f05bc10d29b77123e2029ef1dce385a4
4
- data.tar.gz: '054837c4d15be8b9d248aa81db5966d80c2c5e0f9a322c622bdf781bf8d0af20'
3
+ metadata.gz: 4ff845df334960263a1d36b01aeba57393609c59acbc63424572c9d7fdca7999
4
+ data.tar.gz: 70029c193102d9a63b6d6e5ff8361fcf42e506bff26435c907ec4a06010a571a
5
5
  SHA512:
6
- metadata.gz: 38ef48d0b9cb24d30522f213302d5a3af48fca24ecd90af259b7d6a034fcbd8ddc706c0c2783db8b5372fb59c3d7a4429b7cc7523cd0299e7ed2a7b8cb8b023e
7
- data.tar.gz: 82cdb4b713646492a92a1bae0143a7051fe2bc4f6afe0ad7394bb6f7d50704035a8ddd863bb1b4d12885867859cd6b5b743c618d2fc87682e26918150162b25a
6
+ metadata.gz: 28a4548cafd93c7f5d5c9d1f577399d8e90503c2e4ae1fa279a9723234045848c0b3bdbb1c41df9f0808d375eb5d345a5210d9db46fe74ba3332fb2394920530
7
+ data.tar.gz: e2555f3731aad5bacb7e572c9630fa9e39721b625684664bc2ea41d9b0579af146f697b10a6d99410368140ebb7cd37de53b2bd195e2a2c13a5100722502fcd2
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: ci-monitoring
3
+ description: >
4
+ How to wait for GitHub Actions CI to finish on a PR after pushing commits,
5
+ without hanging forever or giving up too early. Use this skill whenever you
6
+ have pushed to a PR branch and need to confirm the checks pass before
7
+ reporting back or approving.
8
+ triggers:
9
+ - ci
10
+ - checks
11
+ - status check
12
+ - wait for ci
13
+ - green
14
+ - pipeline
15
+ ---
16
+
17
+ # Waiting for CI on a PR
18
+
19
+ After you push commits to a PR branch, the PR's checks (`test.yml`, `ruby-gem.yml`)
20
+ re-run. You often need to confirm they pass before you comment "done" or before a
21
+ review approves. This is easy to get wrong in two opposite ways — hanging forever,
22
+ or declaring success while checks are still pending. Follow this pattern.
23
+
24
+ ## Do NOT use the blocking watchers
25
+
26
+ These hang until completion with no timeout and will burn the whole run's time
27
+ budget (the agent's bash step is capped at ~10 minutes):
28
+
29
+ ```bash
30
+ gh run watch # ❌ blocks indefinitely
31
+ gh pr checks --watch # ❌ blocks indefinitely
32
+ ```
33
+
34
+ ## Poll `statusCheckRollup` in a bounded foreground loop
35
+
36
+ Query the PR's aggregated check state and sleep between polls. Use
37
+ `statusCheckRollup`, **not** `gh pr checks --required`: the latter only returns
38
+ contexts that are *already registered*, so a check that registers late (a matrix
39
+ job, or a summary job with a long `needs:` list) can make it exit green early and
40
+ miss a failure.
41
+
42
+ ```bash
43
+ PR=<pr-number>
44
+ for i in $(seq 1 9); do
45
+ # One JSON blob with every check's name, status, and conclusion.
46
+ rollup=$(gh pr view "$PR" --json statusCheckRollup \
47
+ --jq '.statusCheckRollup[] | {name: .name, status: .status, conclusion: .conclusion}')
48
+
49
+ # Are any checks still running / queued?
50
+ pending=$(echo "$rollup" | jq -sr '[.[] | select(.status != "COMPLETED")] | length')
51
+
52
+ if [ "$pending" -eq 0 ]; then
53
+ echo "All checks completed."
54
+ break
55
+ fi
56
+ echo "Attempt $i/9: $pending check(s) still running; sleeping 60s…"
57
+ sleep 60
58
+ done
59
+ ```
60
+
61
+ That is 9 × 60s = ~9 minutes, which fits inside the bash step cap. If checks are
62
+ still pending after the loop, say so explicitly ("CI was still running when I
63
+ checked — see the PR checks tab") rather than claiming success.
64
+
65
+ ## Decide pass/fail from the final rollup
66
+
67
+ A check passed only when `status == "COMPLETED"` **and**
68
+ `conclusion == "SUCCESS"` (or `NEUTRAL`/`SKIPPED`). Treat `FAILURE`,
69
+ `TIMED_OUT`, `CANCELLED`, `ACTION_REQUIRED`, or `STALE` as failing.
70
+
71
+ ```bash
72
+ failed=$(gh pr view "$PR" --json statusCheckRollup \
73
+ --jq '[.statusCheckRollup[]
74
+ | select(.conclusion != null and .conclusion != "SUCCESS"
75
+ and .conclusion != "NEUTRAL" and .conclusion != "SKIPPED")]
76
+ | length')
77
+ if [ "$failed" -gt 0 ]; then
78
+ echo "CI failed — inspect the failing job before reporting done."
79
+ fi
80
+ ```
81
+
82
+ Do **not** treat `mergeStateStatus == BLOCKED` as a CI failure — it's a catch-all
83
+ (branch protection, pending review, etc.), not a check result.
84
+
85
+ ## When a check fails
86
+
87
+ - Read the failing job's logs (`gh run view <run-id> --log-failed`) and fix the
88
+ cause. For this Ruby gem, most failures are the test suite — see the
89
+ `run-tests` skill.
90
+ - Re-run only the failed jobs if the failure looks transient (rare here):
91
+ `gh run rerun <run-id> --failed`.
92
+ - Never mark a PR done or approve over red CI. If you approve, the empty-body
93
+ approval reads as endorsing a broken build.
94
+
95
+ ## Notes specific to this repo
96
+
97
+ - Relevant workflows on a PR: **`test.yml`** (Ruby 3.2 + 3.3 matrix — the one that
98
+ matters for correctness) and **`ruby-gem.yml`** (its publish job is gated on
99
+ `push` to `main`, so on a PR it only builds — a failure there is still worth
100
+ reading).
101
+ - A PR the bot opened may **not** auto-run these workflows: the built-in
102
+ `GITHUB_TOKEN` does not retrigger other workflows. If the checks tab is empty on
103
+ a bot-opened PR, that's expected, not a hang — note it and move on.
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: run-tests
3
+ description: >
4
+ How to install dependencies and run the claude_hooks test suite.
5
+ Use this skill whenever you need to verify that code changes pass the tests
6
+ before committing.
7
+ triggers:
8
+ - test
9
+ - tests
10
+ - spec
11
+ - run_all_tests
12
+ ---
13
+
14
+ # Running the claude_hooks Test Suite
15
+
16
+ This is a **Ruby gem**. Always run the full test suite after any code change and
17
+ before committing.
18
+
19
+ ## Install dependencies
20
+
21
+ ```bash
22
+ bundle install
23
+ ```
24
+
25
+ ## Run all tests
26
+
27
+ ```bash
28
+ ruby test/run_all_tests.rb
29
+ ```
30
+
31
+ The suite must exit 0 before you commit. All files matching `test/test_*.rb` are
32
+ included automatically.
33
+
34
+ ## Adding or updating tests
35
+
36
+ - Place new test files in `test/` named `test_<feature>.rb`.
37
+ - Use plain `Test::Unit` / `Minitest` — no RSpec.
38
+ - Run the full suite, not just the new file, to catch regressions.
39
+
40
+ ## Common failure patterns
41
+
42
+ - **Missing gem**: run `bundle install` again; check `Gemfile.lock` was updated.
43
+ - **LoadError**: verify the `require` path matches the file under `lib/`.
44
+ - **Unexpected output**: the hooks write to `$stderr`; redirect if your test
45
+ captures stdout only.
data/AGENTS.md ADDED
@@ -0,0 +1,46 @@
1
+ # Agent guidance — claude_hooks
2
+
3
+ This file is loaded by the coding agents that run in this repo's GitHub Actions
4
+ bots. It is picked up via `load_available_skills` (which internally calls
5
+ `load_project_skills`, the loader that ingests `AGENTS.md`) in
6
+ `agent_task.py` (issue and PR bots) and by the `pr-review` composite action.
7
+ Keep it short and factual.
8
+
9
+ ## What this project is
10
+
11
+ `claude_hooks` is a **Ruby gem** — a DSL for creating [Claude Code](https://docs.claude.com/en/docs/claude-code)
12
+ hooks (logging, security checks, workflow automation) with composable hook scripts.
13
+ Pure Ruby, no Rails. Public repo: https://github.com/gabriel-dehan/claude_hooks
14
+
15
+ ## Layout
16
+
17
+ - `lib/claude_hooks/` — the gem. One file per hook type (`pre_tool_use.rb`,
18
+ `post_tool_use.rb`, `user_prompt_submit.rb`, `session_start.rb`, etc.), plus
19
+ `base.rb`, `configuration.rb`, `cli.rb`, `logger.rb`, `version.rb`.
20
+ - `test/` — plain Ruby tests (`test_*.rb`), no RSpec.
21
+ - `docs/` — human docs, including `docs/mitts/` for the bot setup itself.
22
+
23
+ ## Running tests
24
+
25
+ ```bash
26
+ bundle install
27
+ ruby test/run_all_tests.rb
28
+ ```
29
+
30
+ Run this before committing any code change; it must pass. Add or update tests in
31
+ `test/` when you change behavior.
32
+
33
+ ## Conventions
34
+
35
+ - Match the surrounding code's style; this is a small, idiomatic Ruby gem.
36
+ - Keep changes minimal and focused on the request.
37
+ - Follow SemVer; note user-facing changes in `CHANGELOG.md`. `v1.0.0` introduced
38
+ breaking changes — check the changelog before assuming an API.
39
+ - Never commit secrets; never modify `.github/workflows/` permissions or CI secrets
40
+ unless the task is explicitly about the workflows.
41
+
42
+ ## For PR review specifically
43
+
44
+ Verify that behavior changes come with tests and that `ruby test/run_all_tests.rb`
45
+ would still pass. Flag anything that breaks the documented DSL API without a
46
+ CHANGELOG note.
data/CHANGELOG.md CHANGED
@@ -5,6 +5,70 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.1] - 2026-07-15
9
+
10
+ ### Added
11
+
12
+ - **`CLI.run_hook` — new primary entrypoint method** replacing `CLI.entrypoint`. Reads JSON from STDIN, runs the hook, and exits with the correct code and stream for the hook type.
13
+ - Accepts `on_error: :allow` (default, exit 1 — non-blocking) or `on_error: :block` (exit 2 — blocking). Use `:block` for security/policy hooks where a crash should never silently pass through.
14
+ - Supports a class form (`CLI.run_hook(MyHook)`) and a block form for multi-handler merging.
15
+
16
+ ### Changed
17
+
18
+ - **`CLI.entrypoint` deprecated** in favour of `CLI.run_hook`. It remains as a delegating alias with a deprecation warning and will be removed in a future minor version.
19
+ - **`CLI.run_hook` (old)** — the previous public `run_hook(hook_class, input_data)` helper used internally by `test_runner`/`run_with_sample_data` has been renamed to `run_hook_with_data` and made private. It was never intended as part of the public API.
20
+ - **Error output stream** — `CLI.run_hook` / `CLI.entrypoint` now write errors exclusively to stderr (previously the `:allow` path also wrote to stdout). On the `on_error: :block` (exit 2) path the message is written as **plain text**, not JSON, since Claude Code shows exit-2 stderr to the model verbatim and never parses it as JSON. The `:allow` (exit 1) path still writes the JSON error object to stderr.
21
+ - **README and examples** updated throughout to use `CLI.run_hook`.
22
+
23
+ ### Notes
24
+
25
+ - No hook class or output class changes — existing hook scripts continue to work without modification
26
+ - `CLI.entrypoint` still works; only a deprecation warning is emitted
27
+ - 3 new tests covering `on_error: :block` (hook error → exit 2, invalid JSON → exit 2) and the deprecation warning
28
+
29
+ ## [1.2.0] - 2026-07-14
30
+
31
+ ### Added
32
+
33
+ - **20 new hook event classes** bringing full parity with the Claude Code Hooks spec v1.2.0 (30 events total):
34
+ - **Context-only**: `Setup`, `SubagentStart`
35
+ - **Blocking (top-level `decision`)**: `UserPromptExpansion`, `PostToolBatch`, `ConfigChange`
36
+ - **Blocking (exit-2 / `continue:false`)**: `TaskCreated`, `TaskCompleted`, `TeammateIdle`
37
+ - **Non-blocking**: `PostToolUseFailure`, `StopFailure`, `PostCompact`, `CwdChanged`, `FileChanged`, `InstructionsLoaded`, `WorktreeRemove`
38
+ - **JSON-API special**: `PermissionDenied`, `Elicitation`, `ElicitationResult`, `WorktreeCreate`
39
+ - **Display**: `MessageDisplay`
40
+
41
+ - **New common input readers** (available on all hook instances):
42
+ - `prompt_id`, `agent_id`, `agent_type`, `effort`
43
+ - `permission_mode` now documents `'auto'` as a valid value
44
+
45
+ - **`terminal_sequence!(seq)`** builder on all hooks; `terminal_sequence` accessor on output objects; `terminalSequence` (last non-nil wins) in `Output::Base.merge`
46
+
47
+ - **`PreToolUse` additions**: `defer_permission!`, `update_input!(hash)`, `add_additional_context!`; new output accessor `deferred?`
48
+
49
+ - **`PostToolUse` additions**: `update_tool_output!(value)`, `update_mcp_tool_output!(value)`; output accessors `updated_tool_output`, `updated_mcp_tool_output`, `output_updated?`
50
+
51
+ - **`SessionStart` additions**: builders `session_title!`, `initial_user_message!`, `watch_paths!(array)`, `reload_skills!(bool)`; matching output accessors `session_title`, `initial_user_message`, `watch_paths`, `reload_skills?`; input readers `model`, `session_title`
52
+
53
+ - **`Stop` / `SubagentStop` additions**: input readers `last_assistant_message`, `background_tasks`, `session_crons`; `add_additional_context!` builder + `output.additional_context` accessor; `SubagentStop` gains `agent_transcript_path`
54
+
55
+ - **`PreCompact` additions**: `block!(reason)` builder; `Output::PreCompact` gains `decision`, `reason`, `blocked?` accessors
56
+
57
+ - **`PostToolBatch` convenience**: `succeeded_calls` / `failed_calls` partitions over `tool_calls` (success derived from the entry's `tool_response`)
58
+
59
+ ### Changed
60
+
61
+ - **`PermissionRequest` emitted JSON shape** — builders now emit `hookSpecificOutput.decision = { behavior, message?, updatedInput?, updatedPermissions?, interrupt? }` instead of flat `permissionDecision`/`permissionDecisionReason` keys. **Backward-compatible: no hook code changes required** — builder signatures (`allow_permission!`, `deny_permission!`, `update_input_and_allow!`) are unchanged; read-side accessors (`permission_decision`, `allowed?`, `denied?`, etc.) retain legacy fallback for any stored flat JSON.
62
+
63
+ - **`PreToolUse` merge precedence** corrected to `deny > defer > ask > allow` (was `deny > ask > allow`).
64
+
65
+ ### Notes
66
+
67
+ - All changes are additive — existing hooks continue to work without modification
68
+ - Ships as minor `1.2.0` (no source-level breaks for users)
69
+ - Total hook event classes: 30 (added 20 new classes, including `MessageDisplay`)
70
+ - All 13 test files pass (0 failures, 0 errors)
71
+
8
72
  ## [1.1.0] - 2026-01-04
9
73
 
10
74
  ### Added