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
@@ -0,0 +1,303 @@
1
+ # Setting up Mitts in your repository
2
+
3
+ This guide walks you through everything needed to get the Mitts coding bots running in a GitHub repository, from zero to a first successful run. It assumes **the Mitts files are already present in your repo** (workflows, the composite action, the `.github/mitts/` scripts, `.agents/skills/`, and `AGENTS.md`) — extracting/copying them into a fresh repo is covered separately.
4
+
5
+ For *how the system works* (architecture, the hybrid context system, the
6
+ security model), see [`mitts-bots.md`](./mitts-bots.md). This document is the
7
+ operational checklist.
8
+
9
+ > **Time:** ~10 minutes of clicking + CLI. Most of it is one-time GitHub config.
10
+ > You need **admin** on the repo (some steps touch repo Settings and secrets).
11
+
12
+ ---
13
+
14
+ ## At a glance
15
+
16
+ | Step | Where | One-time? | Required? |
17
+ |---|---|---|---|
18
+ | 1. Files present | repo | — | ✅ |
19
+ | 2. LLM endpoint config (vars + secret) | Actions vars/secrets | ✅ | ✅ |
20
+ | 3. Allow Actions to open PRs | Settings → Actions | ✅ | ✅ |
21
+ | 4. Protect `main` (no bot merges) | Settings → Rules | ✅ | ✅ strongly |
22
+ | 5. Gate the release secret behind `main` | Settings → Environments | ✅ | ✅ if you publish |
23
+ | 6. Decide who can trigger the bots | collaborators / org | ongoing | ✅ |
24
+ | 7. Land the workflows on `main` | git | ✅ | ✅ |
25
+ | 8. First run | issue / PR comment | — | ✅ |
26
+ | 9. Optional knobs | Actions vars | anytime | optional |
27
+
28
+ ---
29
+
30
+ ## 1. Confirm the files are in place
31
+
32
+ You should have this tree in the repo (see `mitts-bots.md` → "The pieces" for
33
+ what each does):
34
+
35
+ ```
36
+ AGENTS.md
37
+ .agents/skills/… # keyword-triggered skills
38
+ .github/
39
+ ├── actions/run-mitts/action.yml # shared setup + run (issue + PR bots)
40
+ ├── mitts/
41
+ │ ├── agent_task.py # SDK agent runner
42
+ │ ├── context_builder.py # deterministic prompt builder
43
+ │ ├── recall_tool.py # cross-run memory tool
44
+ │ ├── instructions/{issues,prs}.md # per-surface task templates
45
+ │ └── hooks/block_dangerous.sh # guardrail hook
46
+ └── workflows/
47
+ ├── mitts-pr-review.yml
48
+ ├── mitts-issue.yml
49
+ └── mitts-pr-followup.yml
50
+ ```
51
+
52
+ Nothing here needs editing to run — the scripts locate their siblings and the
53
+ hook relative to their own path, and the workflows reference the composite
54
+ action by path. If your default branch is not `main`, note it — a few steps
55
+ below refer to "the default branch".
56
+
57
+ ---
58
+
59
+ ## 2. Configure your LLM endpoint
60
+
61
+ Mitts talks to **your own OpenAI-compatible endpoint** via LiteLLM. Three values
62
+ are needed. Two are non-secret (**Actions variables**) and one is secret (an
63
+ **Actions secret**).
64
+
65
+ The names below are the exact identifiers the workflows read — don't rename them
66
+ unless you also change the workflows:
67
+
68
+ | Name | Kind | Example | Read by |
69
+ |---|---|---|---|
70
+ | `OPENHANDS_LLM_MODEL` | variable | `openai/gpt-oss-120b` | `mitts-*.yml` → `llm-model` → `LLM_MODEL` |
71
+ | `OPENHANDS_LLM_BASE_URL` | variable | `https://your-endpoint.example/v1` | → `llm-base-url` → `LLM_BASE_URL` |
72
+ | `OPENHANDS_LLM_API_KEY` | **secret** | `sk-…` | → `llm-api-key` → `LLM_API_KEY` |
73
+
74
+ > These keep the `OPENHANDS_` prefix on purpose: they configure the underlying
75
+ > OpenHands SDK / LiteLLM layer, which Mitts wraps but does not rename. All three
76
+ > Mitts workflows read the same three names.
77
+
78
+ Set them with the `gh` CLI (from the repo directory):
79
+
80
+ ```bash
81
+ gh variable set OPENHANDS_LLM_MODEL --body "openai/<your-model-name>"
82
+ gh variable set OPENHANDS_LLM_BASE_URL --body "https://<your-endpoint>/v1"
83
+ gh secret set OPENHANDS_LLM_API_KEY --body "<your-api-key>"
84
+ ```
85
+
86
+ Or in the UI: **Settings → Secrets and variables → Actions** → the **Variables**
87
+ tab for the first two, the **Secrets** tab for the key.
88
+
89
+ Two gotchas that cause almost every first-run failure:
90
+
91
+ - **Use the `openai/` prefix on the model** so LiteLLM treats it as an
92
+ OpenAI-compatible backend (e.g. `openai/gpt-oss-120b`, `openai/llama-3.3-70b`).
93
+ - **The base URL usually needs the `/v1` suffix.** Confirm with your provider —
94
+ some expose the OpenAI-compatible routes at the root, most at `/v1`.
95
+
96
+ Do **not** create a `GITHUB_TOKEN` secret — GitHub Actions injects it
97
+ automatically for every run. The bots authenticate to GitHub with it.
98
+
99
+ ---
100
+
101
+ ## 3. Allow Actions to open pull requests
102
+
103
+ By default a workflow's `GITHUB_TOKEN` can push a branch but **cannot open a
104
+ PR**. The issue → PR bot needs both.
105
+
106
+ **Settings → Actions → General → Workflow permissions:**
107
+
108
+ - Select **Read and write permissions**.
109
+ - Check **Allow GitHub Actions to create and approve pull requests**.
110
+
111
+ Without the checkbox, the issue bot pushes a `mitts/issue-<n>` branch but the
112
+ `gh pr create` step fails.
113
+
114
+ ---
115
+
116
+ ## 4. Protect `main` so the bot can't merge (structural containment)
117
+
118
+ This is the core of the Mitts security model: the agent runs with `contents:
119
+ write` + `pull-requests: write`, and the guarantee that **it can never land code
120
+ on `main` by itself** comes from branch protection, not from trusting the agent.
121
+
122
+ **Settings → Rules → Rulesets** (or classic **Branch protection rules**), on the
123
+ default branch:
124
+
125
+ - **Require a pull request before merging**, with at least **one approving
126
+ review**.
127
+ - **Restrict who can push / bypass** to admins only.
128
+
129
+ The bot uses the built-in `GITHUB_TOKEN`, which is not an admin, so it can open
130
+ PRs but cannot merge them or push to `main` directly. A human approval stays in
131
+ the loop. See `mitts-bots.md` → "Structural containment" for the full reasoning
132
+ (why the behavioral hook is defense-in-depth, not the boundary).
133
+
134
+ > Skipping this is only acceptable on a throwaway/private repo you fully control.
135
+ > Do **not** point the bots at a repo that publishes releases without it — see
136
+ > the next step.
137
+
138
+ ---
139
+
140
+ ## 5. Gate the release secret behind `main` (if you publish)
141
+
142
+ This repo's `ruby-gem.yml` publishes to RubyGems using
143
+ `secrets.RUBYGEMS_AUTH_TOKEN`, and its publish job runs on `push` to
144
+ `refs/heads/main`. To make that secret unreachable to a prompt-injected agent,
145
+ move it behind a branch-scoped **Environment** so it's only readable from the
146
+ default branch.
147
+
148
+ **Settings → Environments → New environment** (e.g. `release`):
149
+
150
+ 1. Add `RUBYGEMS_AUTH_TOKEN` as an **environment secret** (remove it as a plain
151
+ repo secret so it's *only* available via the environment).
152
+ 2. Under **Deployment branches and tags**, choose **Selected branches and tags**
153
+ and allow only the default branch (add a tag pattern too if you tag releases).
154
+ 3. Reference it from the publish job:
155
+
156
+ ```yaml
157
+ jobs:
158
+ publish:
159
+ environment: release
160
+ # …
161
+ ```
162
+
163
+ The chain this creates: no admin merge → nothing lands on `main` → no
164
+ `push`-to-`main` event → no `release` environment access → `RUBYGEMS_AUTH_TOKEN`
165
+ is never exposed. A compromised bot token pushing some *other* branch can't match
166
+ the deployment-branch policy, so the publish job is rejected before the secret is
167
+ readable.
168
+
169
+ > **If you don't publish anything**, you can skip this step — but still do step 4.
170
+ > **Caveat:** this guarantee holds for `push`/`pull_request` triggers. If you add
171
+ > secret-bearing jobs on `workflow_dispatch`, `release`, or `schedule` (which can
172
+ > fire without a merge), gate those with required reviewers on the environment
173
+ > too.
174
+
175
+ ---
176
+
177
+ ## 6. Decide who can trigger the bots
178
+
179
+ The write-capable paths — `mitts-issue`, `mitts-pr-followup`, and the on-demand
180
+ `@mitts review` comment — only run when the commenter's `author_association` is
181
+ `OWNER`, `MEMBER`, or `COLLABORATOR`. Everyone else (`CONTRIBUTOR`, `NONE`,
182
+ `FIRST_TIMER`) is ignored.
183
+
184
+ - **Nothing to configure to be safe** — the gate is already in the workflows.
185
+ - **To let a specific person trigger the bots**, add them as a **repo
186
+ collaborator** (or org member) rather than loosening the gate. On a public
187
+ repo, loosening it would let any stranger drive a write-capable agent with
188
+ `@mitts implement <prompt injection>` and burn your LLM budget.
189
+
190
+ The **automatic** PR review on `opened`/`reopened` is intentionally *ungated* —
191
+ it's read-only (`contents: read`, only posts a review), which is the point of
192
+ reviewing incoming contributions.
193
+
194
+ ---
195
+
196
+ ## 7. Land the workflows on the default branch
197
+
198
+ GitHub only fires `issue_comment` triggers from workflow files that exist **on
199
+ the default branch**. The bots go live only once these are merged to `main` —
200
+ this is a GitHub rule, not a config choice.
201
+
202
+ ```bash
203
+ git checkout -b add-mitts-bots
204
+ git add AGENTS.md .agents/ \
205
+ .github/mitts .github/actions/run-mitts \
206
+ .github/workflows/mitts-*.yml docs/mitts/
207
+ git commit -m "Add Mitts GitHub Actions bots (review, issue, PR follow-up)"
208
+ git push -u origin add-mitts-bots
209
+ gh pr create --fill
210
+ ```
211
+
212
+ Merge that PR. Until it's on the default branch, commenting `@mitts …` does
213
+ nothing (the auto PR-review on `pull_request` will run from a branch, but the
214
+ comment-driven bots will not).
215
+
216
+ ---
217
+
218
+ ## 8. First run
219
+
220
+ Trigger one and watch it:
221
+
222
+ ```bash
223
+ # e.g. comment "@mitts review" on any open PR, then:
224
+ gh run watch
225
+ ```
226
+
227
+ Or open an issue and comment `@mitts what would it take to add X?` (a
228
+ question — no code change) as a low-risk first test.
229
+
230
+ **If the run fails**, the most likely cause is the model string or base URL:
231
+
232
+ - Adjust `OPENHANDS_LLM_MODEL` (try with/without the `openai/` prefix).
233
+ - Toggle the `/v1` suffix on `OPENHANDS_LLM_BASE_URL`.
234
+ - Re-trigger with a new comment.
235
+
236
+ Every run appends a cost/token summary (model, cost, prompt/completion tokens,
237
+ skills loaded) to the **Actions step summary** — open the run in the Actions UI
238
+ and click the summary tab. A run that ends `ERROR`/`STUCK` exits non-zero and the
239
+ workflow posts a failure comment on the issue/PR.
240
+
241
+ ### What to expect on the first bot-opened PR
242
+
243
+ The built-in `GITHUB_TOKEN` does **not** retrigger other workflows, so a PR the
244
+ bot opens won't automatically run `test.yml` — its checks tab may look empty.
245
+ That's expected, not a hang. If you want the bot's PRs to trigger CI, use a PAT
246
+ or GitHub App token instead of the default token.
247
+
248
+ ---
249
+
250
+ ## 9. Optional environment knobs
251
+
252
+ All optional. Set as **Actions variables** (`gh variable set NAME --body VALUE`)
253
+ unless noted. Defaults are fine for most setups.
254
+
255
+ | Variable | Default | What it does |
256
+ |---|---|---|
257
+ | `LLM_MAX_ITER` | `60` | Max agent iterations per run. Lower to cap cost/runtime; raise for harder tasks. |
258
+ | `OPENHANDS_LOAD_PUBLIC_SKILLS` | off | `1`/`true` also loads skills from the `OpenHands/extensions` public registry (adds ~1–2 s for a git clone on first run). |
259
+ | `EXTENSIONS_REF` | `main` | Branch/tag/SHA for the public extensions repo. Pin to a tag for reproducibility once you're happy. |
260
+ | `OPENHANDS_KEEP_RUNS` | `5` | Prior-run subdirs retained per thread before each run. Older ones are pruned so the state artifact stays bounded. |
261
+
262
+ Set automatically by the composite action — **you do not set these**, listed for
263
+ reference:
264
+
265
+ - `OPENHANDS_PERSIST_DIR` — root dir for cross-run event persistence (from
266
+ `runner.temp`).
267
+ - `OPENHANDS_CONVERSATION_KEY` — per-thread key for the artifact (e.g. `pr-42`,
268
+ `issue-7`).
269
+
270
+ The `mitts-pr-review.yml` review action also accepts an `extensions-version:`
271
+ input (currently `main`) — pin it to a tag/SHA alongside `EXTENSIONS_REF` when
272
+ you want reproducible reviews.
273
+
274
+ ---
275
+
276
+ ## Verifying the setup (quick checklist)
277
+
278
+ - [ ] `gh variable list` shows `OPENHANDS_LLM_MODEL` and `OPENHANDS_LLM_BASE_URL`.
279
+ - [ ] `gh secret list` shows `OPENHANDS_LLM_API_KEY`.
280
+ - [ ] Settings → Actions → workflow permissions = **Read and write** + **create PRs** checked.
281
+ - [ ] Default branch requires a PR + 1 approval; bypass restricted to admins.
282
+ - [ ] (If publishing) `RUBYGEMS_AUTH_TOKEN` lives in a branch-scoped Environment, referenced via `environment:`.
283
+ - [ ] `mitts-*.yml` are on the default branch.
284
+ - [ ] A first `@mitts …` comment produced a run in the Actions tab.
285
+
286
+ ---
287
+
288
+ ## Usage reference
289
+
290
+ Once set up, drive the bots from issue/PR comments:
291
+
292
+ | You want to… | Where | Comment |
293
+ |---|---|---|
294
+ | Review a PR | PR | automatic on open/reopen, or `@mitts review` |
295
+ | Turn an issue into a PR | issue | `@mitts implement <optional detail>` |
296
+ | Ask about an issue | issue | `@mitts what would this involve?` (answers, no PR) |
297
+ | Change code on a PR | PR | `@mitts add error handling for nil config` |
298
+ | Ask about a PR | PR | `@mitts why did you use a mutex here?` (uses cross-run memory) |
299
+ | Propose a plan (no code) | issue or PR | `@mitts plan <optional detail>` |
300
+
301
+ Adding repo knowledge later: drop a new `.agents/skills/<name>/SKILL.md` (see the
302
+ `run-tests` skill for the format) — it's picked up automatically on the next run,
303
+ no config change needed.
@@ -0,0 +1,44 @@
1
+
2
+ ## TODO:
3
+ - [x] Add an AGENTS.md — done (repo root; loaded by all bots via `load_available_skills`)
4
+ - [x] Idea: persistence but hybrid with a custom tool where it can fetch its own history if it needs to.
5
+ — done. Deterministic context injection (context_builder.py) + on-demand cross-run
6
+ memory via the recall_prior_reasoning custom tool (recall_tool.py) reading prior-run
7
+ event dirs bridged across runs as GitHub artifacts. No auto-replay (fresh
8
+ conversation_id each run). See docs/mitts/mitts-bots.md → "Hybrid context system".
9
+
10
+ ## Done (harness feature additions)
11
+ - [x] Load AGENTS.md + project skills in agent_task.py via `load_available_skills`
12
+ (issue/PR bots now load AGENTS.md + .agents/skills/ on every run)
13
+ - [x] AgentSkills: added `.agents/skills/run-tests/SKILL.md` as a progressive-disclosure example
14
+ - [x] Sub-agent delegation: `enable_sub_agents=True` + `register_builtins_agents()` wired
15
+ - [x] Guardrail hook: `.github/mitts/hooks/block_dangerous.sh` (force-push, CI, secrets)
16
+ - [x] Cost/status reporting: metrics to $GITHUB_STEP_SUMMARY; ERROR/STUCK → exit 1
17
+ - [x] Plan intent: `@mitts plan` branch in context_builder.py templates
18
+
19
+ ## Tools
20
+
21
+ - Are there existing tools that we can use?
22
+ https://github.com/OpenHands/software-agent-sdk/tree/main/openhands-tools/openhands/tools
23
+
24
+ - Do we need any custom tools?
25
+ https://docs.openhands.dev/sdk/guides/custom-tools
26
+ - [x] Yes — built `recall_prior_reasoning` (.github/mitts/recall_tool.py). Chosen over a skill.
27
+
28
+ ## Skills
29
+
30
+ https://docs.openhands.dev/sdk/guides/skill
31
+ - [x] AGENTS.md loaded via load_available_skills → load_project_skills (all bots)
32
+ - [x] .agents/skills/ directory wired; run-tests SKILL.md added as example
33
+ - Optional public skills via OPENHANDS_LOAD_PUBLIC_SKILLS=1
34
+
35
+ ## Going further
36
+
37
+ https://docs.openhands.dev/sdk/guides/iterative-refinement
38
+
39
+ ### Subagents
40
+
41
+ - [x] Built-in subagents registered via register_builtins_agents()
42
+ - [x] TaskToolSet enabled (enable_sub_agents=True)
43
+ - Source: https://github.com/OpenHands/software-agent-sdk/tree/main/openhands-tools/openhands/tools/preset/subagents
44
+
@@ -1,35 +1,18 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'json'
3
+ require 'claude_hooks'
4
4
  require_relative '../handlers/session_end/cleanup_handler'
5
5
  require_relative '../handlers/session_end/log_session_stats'
6
6
 
7
- begin
8
- # Read input from stdin
9
- input_data = JSON.parse(STDIN.read)
10
-
11
- # Initialize handlers
7
+ ClaudeHooks::CLI.run_hook do |input_data|
12
8
  cleanup_handler = CleanupHandler.new(input_data)
13
9
  log_handler = LogSessionStats.new(input_data)
14
10
 
15
- # Execute handlers
16
11
  cleanup_handler.call
17
12
  log_handler.call
18
13
 
19
- # Merge outputs using the SessionEnd output merger
20
- merged_output = ClaudeHooks::Output::SessionEnd.merge(
14
+ ClaudeHooks::Output::SessionEnd.merge(
21
15
  cleanup_handler.output,
22
16
  log_handler.output
23
- )
24
-
25
- # Output result and exit with appropriate code
26
- merged_output.output_and_exit
27
-
28
- rescue StandardError => e
29
- STDERR.puts JSON.generate({
30
- continue: false,
31
- stopReason: "Hook execution error: #{e.message}",
32
- suppressOutput: false
33
- })
34
- exit 2
35
- end
17
+ ).output_and_exit
18
+ end
@@ -1,40 +1,18 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Example of the NEW simplified entrypoint pattern using output objects
4
- # Compare this to the existing user_prompt_submit.rb to see the difference!
5
-
6
3
  require 'claude_hooks'
7
- require 'json'
8
- # Require the output classes
9
- require_relative '../../../lib/claude_hooks/output/base'
10
- require_relative '../../../lib/claude_hooks/output/user_prompt_submit'
11
4
  require_relative '../handlers/user_prompt_submit/append_rules'
12
5
  require_relative '../handlers/user_prompt_submit/log_user_prompt'
13
6
 
14
- begin
15
- # Read input from stdin
16
- input_data = JSON.parse(STDIN.read)
17
-
18
- # Execute all hook scripts
7
+ ClaudeHooks::CLI.run_hook do |input_data|
19
8
  append_rules = AppendRules.new(input_data)
20
9
  append_rules.call
21
10
 
22
11
  log_user_prompt = LogUserPrompt.new(input_data)
23
12
  log_user_prompt.call
24
13
 
25
- merged_output = ClaudeHooks::Output::UserPromptSubmit.merge(
14
+ ClaudeHooks::Output::UserPromptSubmit.merge(
26
15
  append_rules.output,
27
16
  log_user_prompt.output
28
- )
29
-
30
- merged_output.output_and_exit
31
-
32
- rescue StandardError => e
33
- # Same simple error pattern
34
- STDERR.puts JSON.generate({
35
- continue: false,
36
- stopReason: "Hook execution error: #{e.message} #{e.backtrace.join("\n")}",
37
- suppressOutput: false
38
- })
39
- exit 2
40
- end
17
+ ).output_and_exit
18
+ end
@@ -238,16 +238,10 @@ class GithubGuard < ClaudeHooks::PreToolUse
238
238
  end
239
239
  end
240
240
 
241
- # When running this file directly (for debugging)
242
- if __FILE__ == $PROGRAM_NAME
243
- ClaudeHooks::CLI.run_with_sample_data(GithubGuard) do |data|
244
- data.merge!(
245
- 'session_id' => 'GithubGuardTest',
246
- 'transcript_path' => '',
247
- 'cwd' => Dir.pwd,
248
- 'hook_event_name' => 'PreToolUse',
249
- 'tool_name' => 'mcp__github__create_pull_request',
250
- 'tool_input' => { 'draft' => false },
251
- )
252
- end
253
- end
241
+ # Registered directly in settings.json under PreToolUse. Claude Code runs this file
242
+ # with the hook payload on STDIN. on_error: :block makes the guard fail-closed — a
243
+ # crash blocks the tool instead of silently allowing it.
244
+ #
245
+ # Debug it by piping sample JSON:
246
+ # echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"git push --force"}}' | ruby github_guard.rb
247
+ ClaudeHooks::CLI.run_hook(GithubGuard, on_error: :block)
@@ -10,7 +10,7 @@
10
10
  "hooks": [
11
11
  {
12
12
  "type": "command",
13
- "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/entrypoints/pre_tool_use.rb"
13
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/github_guard.rb"
14
14
  }
15
15
  ]
16
16
  }
@@ -71,10 +71,27 @@ module ClaudeHooks
71
71
  @input_data['hook_event_name'] || @input_data['hookEventName'] || hook_type
72
72
  end
73
73
 
74
+ # Values: default|plan|acceptEdits|auto|dontAsk|bypassPermissions
74
75
  def permission_mode
75
76
  @input_data['permission_mode'] || @input_data['permissionMode'] || 'default'
76
77
  end
77
78
 
79
+ def prompt_id
80
+ @input_data['prompt_id'] || @input_data['promptId']
81
+ end
82
+
83
+ def agent_id
84
+ @input_data['agent_id'] || @input_data['agentId']
85
+ end
86
+
87
+ def agent_type
88
+ @input_data['agent_type'] || @input_data['agentType']
89
+ end
90
+
91
+ def effort
92
+ @input_data.dig('effort', 'level')
93
+ end
94
+
78
95
  def read_transcript
79
96
  unless transcript_path && File.exist?(transcript_path)
80
97
  log "Transcript file not found at #{transcript_path}", level: :warn
@@ -115,6 +132,14 @@ module ClaudeHooks
115
132
  @output_data['hookSpecificOutput'] = nil
116
133
  end
117
134
 
135
+ def terminal_sequence!(seq)
136
+ @output_data['terminalSequence'] = seq
137
+ end
138
+
139
+ def terminal_sequence
140
+ @output_data['terminalSequence']
141
+ end
142
+
118
143
  # System message shown to the user (not to Claude)
119
144
  def system_message!(message)
120
145
  @output_data['systemMessage'] = message