Skip to content

Commit ae06454

Browse files
authored
Merge pull request #84 from ClaydeCode/docs/sync-claude-md-unified-work-flow
docs: sync CLAUDE.md with unified event-driven work flow
2 parents 9ba2dbc + 8644823 commit ae06454

1 file changed

Lines changed: 64 additions & 102 deletions

File tree

CLAUDE.md

Lines changed: 64 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ src/clayde/
5353
git.py # ensure_repo() — clone or update repos under REPOS_DIR
5454
safety.py # Content filtering & plan approval: is_comment_visible(),
5555
# filter_comments(), is_issue_visible(),
56-
# has_visible_content(), is_plan_approved()
56+
# get_new_visible_comments(), has_visible_content()
5757
responses.py # Pydantic response models + parse_response() for structured JSON
5858
claude.py # invoke_claude(prompt, repo_path) — dual backend:
5959
# ApiBackend (Anthropic SDK tool-use loop) or
@@ -62,17 +62,11 @@ src/clayde/
6262
# FileSpanExporter (JSONL)
6363
orchestrator.py # main() — single cycle, run_loop() — container entry point
6464
prompts/
65-
preliminary_plan.j2 # Jinja2 template for short preliminary plan
66-
thorough_plan.j2 # Jinja2 template for detailed thorough plan
67-
update_plan.j2 # Jinja2 template for updating a plan on new comments
68-
implement.j2 # Jinja2 template for implement prompt
69-
address_review.j2 # Jinja2 template for addressing PR review comments
70-
plan.j2 # Legacy template (kept for reference)
65+
work.j2 # Jinja2 template for the unified work prompt
7166
tasks/
7267
__init__.py
73-
plan.py # run_preliminary(url), run_thorough(url), run_update(url, phase)
74-
implement.py # run(issue_url) — implement + open PR + assign reviewer
75-
review.py # run(issue_url) — address PR review comments
68+
work.py # run(issue_url) — unified: Claude decides next action
69+
# (ask, plan, implement, open PR, or address review)
7670
webhook/
7771
__init__.py
7872
app.py # FastAPI app, /webhook/pebble, /health, OTel enqueue span
@@ -129,43 +123,36 @@ Config is loaded via `get_settings()` (singleton). `GH_TOKEN` is exported at sta
129123

130124
---
131125

132-
## State Machine
133-
134-
Issue lifecycle stored in `state.json` under `{"issues": {"<html_url>": {...}}}`.
135-
136-
```
137-
(none) → preliminary_planning → awaiting_preliminary_approval
138-
→ planning → awaiting_plan_approval → implementing → pr_open → done
139-
↘ failed
140-
```
141-
142-
New comments in `awaiting_preliminary_approval` or `awaiting_plan_approval`
143-
trigger plan updates (edit existing plan comment + post change summary).
144-
145-
PR reviews in `pr_open` trigger `addressing_review` → back to `pr_open`.
146-
147-
| Status | Meaning |
148-
|--------|---------|
149-
| `preliminary_planning` | Claude is producing a short preliminary plan |
150-
| `awaiting_preliminary_approval` | Preliminary plan posted; waiting for 👍 |
151-
| `planning` | Claude is producing a thorough implementation plan |
152-
| `awaiting_plan_approval` | Thorough plan posted; waiting for 👍 |
153-
| `implementing` | Claude is implementing the approved plan |
154-
| `pr_open` | PR exists; monitoring for review comments |
155-
| `addressing_review` | Claude is addressing review comments |
156-
| `done` | PR approved or complete; issue finished |
157-
| `failed` | Error during any phase; cleared manually to retry |
158-
| `interrupted` | Claude usage/rate limit hit mid-task; retried automatically |
159-
160-
State entries store: `owner`, `repo`, `number`, `preliminary_comment_id`,
161-
`plan_comment_id`, `pr_url`, `branch_name`, `last_seen_comment_id`,
162-
`last_seen_review_id`.
163-
164-
Interrupted entries also store: `interrupted_phase` (`"preliminary_planning"`,
165-
`"planning"`, `"implementing"`, or `"addressing_review"`).
166-
167-
Backward compatibility: old `awaiting_approval` status is mapped to
168-
`awaiting_plan_approval`.
126+
## Work Loop (event-driven)
127+
128+
There is no rigid status state machine. Each tick, the orchestrator iterates
129+
the issues assigned to the bot and, for each, decides whether anything has
130+
happened since last cycle. If so, it hands the issue to the unified **work
131+
task**, which lets Claude choose the next action — ask questions, post a
132+
plan, implement, open a PR, or address review comments.
133+
134+
Per-issue state is stored in `state.json` under
135+
`{"issues": {"<html_url>": {...}}}`. Fields written by the current code:
136+
137+
| Field | Meaning |
138+
|-------|---------|
139+
| `owner`, `repo`, `number` | Issue identity |
140+
| `issue_title` | Title (for log labels) |
141+
| `branch_name` | Working branch (`clayde/issue-<N>` by default) |
142+
| `pr_url` | PR opened for this issue, once detected via `find_open_pr()` |
143+
| `in_progress` | `True` while the work task runs; a crash leaves it set so the next cycle retries |
144+
| `last_seen_at` | ISO-UTC timestamp of the last completed cycle; used to detect new activity |
145+
146+
**Activity detection** (`_handle_issue`): the work task is invoked when any of
147+
`in_progress` is set (retry), `last_seen_at` is `None` (never processed),
148+
there are new whitelist-visible comments, or there is new PR review activity
149+
(inline comments or a review body). A pure PR approval with no comments does
150+
**not** invoke Claude — it just advances `last_seen_at`.
151+
152+
**Limits & retries**: `UsageLimitError` / `InvocationTimeoutError` from Claude
153+
leave `in_progress=True` so the next cycle retries automatically. Other
154+
exceptions clear `in_progress` and log the error. Closed issues are pruned
155+
from state at the start of each tick.
169156

170157
---
171158

@@ -182,8 +169,9 @@ but:
182169
2. **No visible content** → issue is skipped. If the issue body and all
183170
comments are from non-whitelisted users without any whitelisted 👍, there
184171
is nothing for the LLM to work with.
185-
3. **Plan approval gates** remain: preliminary plan needs 👍 to proceed to
186-
thorough plan; thorough plan needs 👍 to proceed to implementation.
172+
173+
Only whitelist-visible content reaches the LLM; Claude decides within the work
174+
task whether it has enough to plan, implement, or must ask first.
187175

188176
Whitelisted users: configured via `CLAYDE_WHITELISTED_USERS` in `data/config.env`.
189177

@@ -241,70 +229,44 @@ Key functions:
241229
- `is_comment_visible(comment)` — True if comment author is whitelisted OR has 👍 from whitelisted user.
242230
- `filter_comments(comments)` — returns only visible comments.
243231
- `is_issue_visible(issue)` — True if issue author is whitelisted OR has 👍 from whitelisted user.
232+
- `get_new_visible_comments(comments, last_seen_at)` — visible comments created after `last_seen_at`.
244233
- `has_visible_content(issue, comments)` — True if there is any visible content at all.
245-
- `is_plan_approved(g, owner, repo, number, comment_id)` — True if a whitelisted user reacted +1 to the plan comment.
246-
247-
---
248-
249-
## Plan Task (`tasks/plan.py`)
250-
251-
Two-phase planning with update support:
252-
253-
### Phase 1: Preliminary Plan (`run_preliminary`)
254-
1. Fetch issue metadata and filtered comments
255-
2. `ensure_repo()` to have the code on disk
256-
3. Build prompt with filtered issue body, labels, visible comments, repo path
257-
4. `invoke_claude()` — Claude explores the repo and returns a short overview with questions
258-
5. Post preliminary plan as issue comment
259-
6. Set status → `awaiting_preliminary_approval`
260-
261-
### Phase 2: Thorough Plan (`run_thorough`)
262-
1. Fetch preliminary plan comment and discussion after it
263-
2. Build prompt including preliminary plan + discussion
264-
3. `invoke_claude()` — Claude produces the full detailed plan
265-
4. Post thorough plan as issue comment
266-
5. Set status → `awaiting_plan_approval`
267-
268-
### Plan Updates (`run_update`)
269-
Triggered when new visible comments are detected in `awaiting_preliminary_approval`
270-
or `awaiting_plan_approval` states:
271-
1. Fetch new visible comments since `last_seen_comment_id`
272-
2. Build update prompt with current plan + new comments
273-
3. `invoke_claude()` — Claude produces summary + updated plan
274-
4. **Edit** the existing plan comment AND **post** a new comment with change summary
275-
276-
---
277-
278-
## Implementation Task (`tasks/implement.py`)
279-
280-
1. Fetch plan comment text and filtered discussion comments after the plan
281-
2. `ensure_repo()` to reset to latest default branch
282-
3. Build prompt with issue body, plan, discussion, repo path
283-
4. `invoke_claude()` — Claude creates a branch, implements, commits, and pushes
284-
5. Python code creates PR via PyGitHub or finds an existing one
285-
6. **Assign the issue author as PR reviewer** via `add_pr_reviewer()`
286-
7. Post result comment on issue; set status → `pr_open`
287234

288235
---
289236

290-
## Review Task (`tasks/review.py`)
291-
292-
Handles PR review comments after implementation:
293-
294-
1. Fetch PR reviews and review comments via PyGitHub
295-
2. Filter to new reviews since `last_seen_review_id`, ignoring own reviews
296-
3. If reviews have comments/body: invoke Claude with `address_review.j2` prompt
297-
4. Claude makes changes and pushes to the existing branch
298-
5. Post summary comment on issue; update `last_seen_review_id`; status stays `pr_open`
299-
6. If a review is "APPROVED" with no comments: set status → `done`
237+
## Work Task (`tasks/work.py`)
238+
239+
A single `run(issue_url)` handles every phase. There is no separate
240+
plan/implement/review task — Claude decides what to do from the context it is
241+
given.
242+
243+
1. `fetch_issue()` + `get_default_branch()`; `ensure_repo()` resets the clone
244+
to the latest default branch.
245+
2. Persist issue metadata and `branch_name` to state.
246+
3. Gather context: whitelist-filtered comments, and — if a PR already exists —
247+
its review bodies and inline review comments.
248+
4. Render `work.j2` with the issue body, labels, comments, review text, repo
249+
path, `branch_name`, `pr_url`, and `default_branch`.
250+
5. `invoke_claude()` — Claude explores, then takes whatever action fits: post
251+
a plan/question comment, implement and push, open a PR via `gh pr create`,
252+
or push fixes addressing review comments. It returns a JSON `{summary}`
253+
(`WorkResponse`).
254+
6. Post the `summary` as an issue comment (best-effort: raw output snippet if
255+
JSON parsing fails).
256+
7. Detect a PR via `find_open_pr(branch_name)`. On first detection, **assign
257+
the issue author as reviewer**; persist `pr_url` to state.
258+
259+
Plans and questions are ordinary issue comments — there is no separate
260+
approval gate or 👍 reaction required to advance. Iteration happens through
261+
the normal comment/review activity-detection loop.
300262

301263
---
302264

303265
## Logging
304266

305267
Format: `[YYYY-MM-DD HH:MM:SS] [clayde.<module>] <message>`
306268
File: `/data/logs/agent.log` (appended)
307-
Logger names: `clayde.orchestrator`, `clayde.tasks.plan`, `clayde.tasks.implement`, `clayde.tasks.review`, `clayde.github`, `clayde.claude`
269+
Logger names: `clayde.orchestrator`, `clayde.tasks.work`, `clayde.github`, `clayde.claude`, `clayde.git`, `clayde.state`
308270

309271
---
310272

0 commit comments

Comments
 (0)