claude_hooks 1.0.2 → 1.2.0
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.
- checksums.yaml +4 -4
- data/.agents/skills/ci-monitoring/SKILL.md +103 -0
- data/.agents/skills/run-tests/SKILL.md +45 -0
- data/.claude/auto-fix.md +7 -0
- data/.claude/settings.local.json +21 -0
- data/AGENTS.md +46 -0
- data/CHANGELOG.md +93 -0
- data/README.md +159 -21
- data/docs/1.1.0_UPGRADE_GUIDE.md +269 -0
- data/docs/API/COMMON.md +7 -0
- data/docs/API/CONFIG_CHANGE.md +39 -0
- data/docs/API/CWD_CHANGED.md +34 -0
- data/docs/API/ELICITATION.md +44 -0
- data/docs/API/ELICITATION_RESULT.md +43 -0
- data/docs/API/FILE_CHANGED.md +37 -0
- data/docs/API/INSTRUCTIONS_LOADED.md +30 -0
- data/docs/API/MESSAGE_DISPLAY.md +38 -0
- data/docs/API/NOTIFICATION.md +1 -0
- data/docs/API/PERMISSION_DENIED.md +37 -0
- data/docs/API/PERMISSION_REQUEST.md +199 -0
- data/docs/API/POST_COMPACT.md +30 -0
- data/docs/API/POST_TOOL_BATCH.md +39 -0
- data/docs/API/POST_TOOL_USE.md +1 -0
- data/docs/API/POST_TOOL_USE_FAILURE.md +31 -0
- data/docs/API/PRE_COMPACT.md +10 -2
- data/docs/API/PRE_TOOL_USE.md +9 -1
- data/docs/API/SETUP.md +37 -0
- data/docs/API/STOP_FAILURE.md +19 -0
- data/docs/API/SUBAGENT_START.md +40 -0
- data/docs/API/TASK_COMPLETED.md +43 -0
- data/docs/API/TASK_CREATED.md +43 -0
- data/docs/API/TEAMMATE_IDLE.md +40 -0
- data/docs/API/USER_PROMPT_EXPANSION.md +43 -0
- data/docs/API/WORKTREE_CREATE.md +39 -0
- data/docs/API/WORKTREE_REMOVE.md +33 -0
- data/docs/PROMPT_BASED_HOOKS.md +386 -0
- data/docs/external/claude-hooks-reference.md +2846 -18
- data/docs/mitts/ideabox.md +12 -0
- data/docs/mitts/prd.md +417 -0
- data/docs/mitts/setup.md +303 -0
- data/docs/mitts/task.md +44 -0
- data/lib/claude_hooks/base.rb +30 -1
- data/lib/claude_hooks/config_change.rb +28 -0
- data/lib/claude_hooks/cwd_changed.rb +28 -0
- data/lib/claude_hooks/elicitation.rb +61 -0
- data/lib/claude_hooks/elicitation_result.rb +57 -0
- data/lib/claude_hooks/file_changed.rb +41 -0
- data/lib/claude_hooks/instructions_loaded.rb +23 -0
- data/lib/claude_hooks/message_display.rb +58 -0
- data/lib/claude_hooks/notification.rb +5 -1
- data/lib/claude_hooks/output/base.rb +48 -1
- data/lib/claude_hooks/output/config_change.rb +47 -0
- data/lib/claude_hooks/output/cwd_changed.rb +42 -0
- data/lib/claude_hooks/output/elicitation.rb +67 -0
- data/lib/claude_hooks/output/elicitation_result.rb +63 -0
- data/lib/claude_hooks/output/file_changed.rb +42 -0
- data/lib/claude_hooks/output/instructions_loaded.rb +19 -0
- data/lib/claude_hooks/output/message_display.rb +49 -0
- data/lib/claude_hooks/output/permission_denied.rb +42 -0
- data/lib/claude_hooks/output/permission_request.rb +120 -0
- data/lib/claude_hooks/output/post_compact.rb +18 -0
- data/lib/claude_hooks/output/post_tool_batch.rb +51 -0
- data/lib/claude_hooks/output/post_tool_use.rb +12 -0
- data/lib/claude_hooks/output/post_tool_use_failure.rb +42 -0
- data/lib/claude_hooks/output/pre_compact.rb +34 -3
- data/lib/claude_hooks/output/pre_tool_use.rb +26 -2
- data/lib/claude_hooks/output/session_start.rb +30 -8
- data/lib/claude_hooks/output/setup.rb +42 -0
- data/lib/claude_hooks/output/stop.rb +15 -0
- data/lib/claude_hooks/output/stop_failure.rb +19 -0
- data/lib/claude_hooks/output/subagent_start.rb +42 -0
- data/lib/claude_hooks/output/task_completed.rb +18 -0
- data/lib/claude_hooks/output/task_created.rb +18 -0
- data/lib/claude_hooks/output/teammate_idle.rb +18 -0
- data/lib/claude_hooks/output/user_prompt_expansion.rb +51 -0
- data/lib/claude_hooks/output/worktree_create.rb +60 -0
- data/lib/claude_hooks/output/worktree_remove.rb +19 -0
- data/lib/claude_hooks/permission_denied.rb +41 -0
- data/lib/claude_hooks/permission_request.rb +63 -0
- data/lib/claude_hooks/post_compact.rb +23 -0
- data/lib/claude_hooks/post_tool_batch.rb +59 -0
- data/lib/claude_hooks/post_tool_use.rb +15 -1
- data/lib/claude_hooks/post_tool_use_failure.rb +45 -0
- data/lib/claude_hooks/pre_compact.rb +7 -0
- data/lib/claude_hooks/pre_tool_use.rb +34 -1
- data/lib/claude_hooks/session_start.rb +28 -0
- data/lib/claude_hooks/setup.rb +25 -0
- data/lib/claude_hooks/stop.rb +17 -0
- data/lib/claude_hooks/stop_failure.rb +27 -0
- data/lib/claude_hooks/subagent_start.rb +23 -0
- data/lib/claude_hooks/subagent_stop.rb +5 -1
- data/lib/claude_hooks/task_completed.rb +35 -0
- data/lib/claude_hooks/task_created.rb +35 -0
- data/lib/claude_hooks/teammate_idle.rb +23 -0
- data/lib/claude_hooks/user_prompt_expansion.rb +45 -0
- data/lib/claude_hooks/version.rb +1 -1
- data/lib/claude_hooks/worktree_create.rb +27 -0
- data/lib/claude_hooks/worktree_remove.rb +19 -0
- data/lib/claude_hooks.rb +42 -0
- metadata +76 -2
data/docs/mitts/setup.md
ADDED
|
@@ -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.
|
data/docs/mitts/task.md
ADDED
|
@@ -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
|
+
|
data/lib/claude_hooks/base.rb
CHANGED
|
@@ -9,7 +9,7 @@ module ClaudeHooks
|
|
|
9
9
|
# Base class for Claude Code hook scripts
|
|
10
10
|
class Base
|
|
11
11
|
# Common input fields for all hook types
|
|
12
|
-
COMMON_INPUT_FIELDS = %w[session_id transcript_path cwd hook_event_name].freeze
|
|
12
|
+
COMMON_INPUT_FIELDS = %w[session_id transcript_path cwd hook_event_name permission_mode].freeze
|
|
13
13
|
|
|
14
14
|
# Override in subclasses to specify hook type
|
|
15
15
|
def self.hook_type
|
|
@@ -71,6 +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
|
|
75
|
+
def permission_mode
|
|
76
|
+
@input_data['permission_mode'] || @input_data['permissionMode'] || 'default'
|
|
77
|
+
end
|
|
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
|
+
|
|
74
95
|
def read_transcript
|
|
75
96
|
unless transcript_path && File.exist?(transcript_path)
|
|
76
97
|
log "Transcript file not found at #{transcript_path}", level: :warn
|
|
@@ -111,6 +132,14 @@ module ClaudeHooks
|
|
|
111
132
|
@output_data['hookSpecificOutput'] = nil
|
|
112
133
|
end
|
|
113
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
|
+
|
|
114
143
|
# System message shown to the user (not to Claude)
|
|
115
144
|
def system_message!(message)
|
|
116
145
|
@output_data['systemMessage'] = message
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module ClaudeHooks
|
|
6
|
+
class ConfigChange < Base
|
|
7
|
+
def self.hook_type
|
|
8
|
+
'ConfigChange'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.input_fields
|
|
12
|
+
%w[source]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def source
|
|
16
|
+
@input_data['source']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def file_path
|
|
20
|
+
@input_data['file_path'] || @input_data['filePath']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def block!(reason = '')
|
|
24
|
+
@output_data['decision'] = 'block'
|
|
25
|
+
@output_data['reason'] = reason
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module ClaudeHooks
|
|
6
|
+
class CwdChanged < Base
|
|
7
|
+
def self.hook_type
|
|
8
|
+
'CwdChanged'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.input_fields
|
|
12
|
+
%w[old_cwd new_cwd]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def old_cwd
|
|
16
|
+
@input_data['old_cwd'] || @input_data['oldCwd']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def new_cwd
|
|
20
|
+
@input_data['new_cwd'] || @input_data['newCwd']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def watch_paths!(paths)
|
|
24
|
+
@output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
|
|
25
|
+
@output_data['hookSpecificOutput']['watchPaths'] = Array(paths)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module ClaudeHooks
|
|
6
|
+
class Elicitation < Base
|
|
7
|
+
def self.hook_type
|
|
8
|
+
'Elicitation'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.input_fields
|
|
12
|
+
%w[mcp_server_name message]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def mcp_server_name
|
|
16
|
+
@input_data['mcp_server_name'] || @input_data['mcpServerName']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def message
|
|
20
|
+
@input_data['message']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def mode
|
|
24
|
+
@input_data['mode']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def url
|
|
28
|
+
@input_data['url']
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def elicitation_id
|
|
32
|
+
@input_data['elicitation_id'] || @input_data['elicitationId']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def requested_schema
|
|
36
|
+
@input_data['requested_schema'] || @input_data['requestedSchema']
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def accept!(content = {})
|
|
40
|
+
@output_data['hookSpecificOutput'] = {
|
|
41
|
+
'hookEventName' => hook_event_name,
|
|
42
|
+
'action' => 'accept',
|
|
43
|
+
'content' => content
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def decline!
|
|
48
|
+
@output_data['hookSpecificOutput'] = {
|
|
49
|
+
'hookEventName' => hook_event_name,
|
|
50
|
+
'action' => 'decline'
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cancel!
|
|
55
|
+
@output_data['hookSpecificOutput'] = {
|
|
56
|
+
'hookEventName' => hook_event_name,
|
|
57
|
+
'action' => 'cancel'
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module ClaudeHooks
|
|
6
|
+
class ElicitationResult < Base
|
|
7
|
+
def self.hook_type
|
|
8
|
+
'ElicitationResult'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.input_fields
|
|
12
|
+
%w[mcp_server_name action]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def mcp_server_name
|
|
16
|
+
@input_data['mcp_server_name'] || @input_data['mcpServerName']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def action
|
|
20
|
+
@input_data['action']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def mode
|
|
24
|
+
@input_data['mode']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def elicitation_id
|
|
28
|
+
@input_data['elicitation_id'] || @input_data['elicitationId']
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def content
|
|
32
|
+
@input_data['content']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def accept!(content = {})
|
|
36
|
+
@output_data['hookSpecificOutput'] = {
|
|
37
|
+
'hookEventName' => hook_event_name,
|
|
38
|
+
'action' => 'accept',
|
|
39
|
+
'content' => content
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def decline!
|
|
44
|
+
@output_data['hookSpecificOutput'] = {
|
|
45
|
+
'hookEventName' => hook_event_name,
|
|
46
|
+
'action' => 'decline'
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def cancel!
|
|
51
|
+
@output_data['hookSpecificOutput'] = {
|
|
52
|
+
'hookEventName' => hook_event_name,
|
|
53
|
+
'action' => 'cancel'
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module ClaudeHooks
|
|
6
|
+
class FileChanged < Base
|
|
7
|
+
def self.hook_type
|
|
8
|
+
'FileChanged'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.input_fields
|
|
12
|
+
%w[file_path event]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def file_path
|
|
16
|
+
@input_data['file_path'] || @input_data['filePath']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Values: change | add | unlink
|
|
20
|
+
def event
|
|
21
|
+
@input_data['event']
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def created?
|
|
25
|
+
event == 'add'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def modified?
|
|
29
|
+
event == 'change'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def deleted?
|
|
33
|
+
event == 'unlink'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def watch_paths!(paths)
|
|
37
|
+
@output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
|
|
38
|
+
@output_data['hookSpecificOutput']['watchPaths'] = Array(paths)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module ClaudeHooks
|
|
6
|
+
class InstructionsLoaded < Base
|
|
7
|
+
def self.hook_type
|
|
8
|
+
'InstructionsLoaded'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.input_fields
|
|
12
|
+
%w[file_path load_reason]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def file_path
|
|
16
|
+
@input_data['file_path'] || @input_data['filePath']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def load_reason
|
|
20
|
+
@input_data['load_reason'] || @input_data['loadReason']
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|