summaryrefslogtreecommitdiff
path: root/CLAUDE.md
blob: 7ef8d63182f8d05d79f089312dc0da5537f7085e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Also check `~/.claude/CLAUDE.md` for user-level development standards (TDD workflow, git practices, session state management, etc.) that apply globally across all projects.

## Canonical Repository

**The canonical source of truth is `/workspace/claudomator`.** All development must happen here.
Do not work in any other directory unless explicitly instructed. Do not explore `/site/doot.terst.org/` for source files.

## Build & Test Commands

```bash
# Build
go build ./...

# Run all tests
go test ./...

# Run a single package's tests
go test ./internal/executor/...

# Run a single test by name
go test ./internal/api/ -run TestServer_CreateTask_MissingName

# Run with race detector (important for executor/pool tests)
go test -race ./...

# Build the binary
go build -o claudomator ./cmd/claudomator/
```

> **Note:** `go-sqlite3` uses CGo. A C compiler (`gcc`) must be present for builds and tests.

## Running the Server

```bash
# Initialize data directory
./claudomator init

# Start API server (default :8484)
./claudomator serve

# Run a task file directly (bypasses server)
./claudomator run ./test/fixtures/tasks/simple-task.yaml

# List tasks via CLI
./claudomator list
```

Config defaults to `~/.claudomator/config.toml`. Data is stored in `~/.claudomator/` (SQLite DB + execution logs).

## Architecture

**Pipeline:** CLI/API → `executor.Pool` → `executor.ContainerRunner` → Docker container → SQLite + log files

### Packages

| Package | Role |
|---|---|
| `internal/task` | `Task` struct, YAML parsing, state machine, validation |
| `internal/executor` | `Pool` (bounded goroutine pool) + `ContainerRunner` (Docker-based executor) |
| `internal/storage` | SQLite wrapper; stores tasks and execution records |
| `internal/api` | HTTP server (REST + WebSocket via `internal/api.Hub`) |
| `internal/reporter` | Formats and emits execution results |
| `internal/config` | TOML config + data dir layout |
| `internal/cli` | Cobra CLI commands (`run`, `serve`, `list`, `status`, `init`) |

### Key Data Flows

**Task execution:**
1. Task created via `POST /api/tasks` or YAML file (`task.ParseFile`)
2. `POST /api/tasks/{id}/run` → `executor.Pool.Submit()` → goroutine in pool
3. `ContainerRunner.Run()` clones `repository_url`, runs `docker run claudomator-agent:latest`
4. Agent runs `claude -p` inside the container; stdout streamed to `executions/<exec-id>/stdout.log`
5. On success, runner pushes commits back to the remote; execution result written to SQLite + WebSocket broadcast

**State machine** (`task.ValidTransition`):
`PENDING` → `QUEUED` → `RUNNING` → `COMPLETED | FAILED | TIMED_OUT | CANCELLED | BUDGET_EXCEEDED`
Failed tasks can retry: `FAILED` → `QUEUED`

**WebSocket:** `Hub` fans out task completion events to all connected clients. `Server.StartHub()` must be called after creating the server.

### Task YAML Format

```yaml
name: "My Task"
claude:
  model: "sonnet"
  instructions: |
    Do something useful.
  working_dir: "/path/to/project"
  max_budget_usd: 1.00
  permission_mode: "default"
  allowed_tools: ["Bash", "Read"]
timeout: "15m"
priority: "normal"  # high | normal | low
tags: ["ci"]
```

Batch files wrap multiple tasks under a `tasks:` key.

### Storage Schema

Two tables: `tasks` (with `config_json`, `retry_json`, `tags_json`, `depends_on_json` as JSON blobs) and `executions` (with paths to log files). Schema is auto-migrated on `storage.Open()`.

## Features

### Changestats

After each task execution, Claudomator extracts git diff statistics from the execution's stdout log. If the log contains a git `diff --stat` summary line (e.g. `5 files changed, 127 insertions(+), 43 deletions(-)`), the stats are parsed and stored in the `executions.changestats_json` column via `storage.DB.UpdateExecutionChangestats`.

**Extraction points:**
- `internal/executor.Pool.handleRunResult` — calls `task.ParseChangestatFromFile(exec.StdoutPath)` after every execution; stores via `Store.UpdateExecutionChangestats`.
- `internal/api.Server.processResult` — also extracts changestats when the API server processes a result (same file, idempotent second write).

**Parser location:** `internal/task/changestats.go` — exported functions `ParseChangestatFromOutput` and `ParseChangestatFromFile` usable by any package without creating circular imports.

**Frontend display:** `web/app.js` renders a `.changestats-badge` on COMPLETED/READY task cards and in execution history rows.

## GitHub Webhook Integration

Claudomator can automatically create tasks when CI builds fail on GitHub.

### Endpoint

`POST /api/webhooks/github`

Accepts `check_run` and `workflow_run` events from GitHub. Returns `{"task_id": "..."}` (200) when a task is created, or 204 when the event is ignored.

### Config (`~/.claudomator/config.toml`)

```toml
# Optional: HMAC-SHA256 secret set in the GitHub webhook settings.
# If omitted, signature validation is skipped.
webhook_secret = "your-github-webhook-secret"

# Projects for matching incoming webhook repository names to local directories.
[[projects]]
name = "myrepo"
dir  = "/workspace/myrepo"

[[projects]]
name = "other-service"
dir  = "/workspace/other-service"
```

### Matching logic

The handler matches the webhook's `repository.name` against each project's `name` and the basename of its `dir` (case-insensitive substring). If no match is found and only one project is configured, that project is used as a fallback.

### GitHub webhook setup

In your GitHub repository → Settings → Webhooks → Add webhook:
- **Payload URL:** `https://<your-claudomator-host>/api/webhooks/github`
- **Content type:** `application/json`
- **Secret:** value of `webhook_secret` in config (or leave blank if not configured)
- **Events:** select *Workflow runs* and *Check runs*

### Task creation

A task is created for:
- `check_run` events with `action: completed` and `conclusion: failure`
- `workflow_run` events with `action: completed` and `conclusion: failure` or `timed_out`

Tasks are tagged `["ci", "auto"]`, capped at $3 USD, and use tools: Read, Edit, Bash, Glob, Grep.

## System Maintenance (Cron)

The following crontab entries are required for system operation and must be maintained for the root user:

```crontab
# Sync Claude and Gemini credentials every 10 minutes
*/10 * * * * /workspace/claudomator/scripts/sync-credentials

# Start the next queued task every 20 minutes
*/20 * * * * /workspace/claudomator/scripts/start-next-task >> /var/log/claudomator-cron.log 2>&1
```

> **Note:** These requirements are critical for agent authentication and automated task progression.

## Agent Tooling (`ct` CLI)

Agents running inside containers have access to `ct`, a pre-built CLI for interacting with the Claudomator API. It is installed at `/usr/local/bin/ct` in the container image. **Use `ct` to create and manage subtasks — do not attempt raw `curl` API calls.**

### Environment (injected automatically)

| Variable | Purpose |
|---|---|
| `CLAUDOMATOR_API_URL` | Base URL of the Claudomator API (e.g. `http://host.docker.internal:8484`) |
| `CLAUDOMATOR_TASK_ID` | ID of the currently-running task; used as the default `parent_task_id` for new subtasks |

### Commands

```bash
# Create a subtask and immediately queue it (returns task ID)
ct task submit --name "Fix tests" --instructions "Run tests and fix any failures." [--model sonnet] [--budget 3.0]

# Create, queue, and wait for completion (exits 0=COMPLETED, 1=FAILED, 2=BLOCKED)
ct task submit --name "Fix tests" --instructions "..." --wait

# Read instructions from a file instead of inline
ct task submit --name "Fix tests" --file /workspace/subtask-instructions.txt --wait

# Lower-level: create only (returns task ID), then run separately
TASK_ID=$(ct task create --name "..." --instructions "...")
ct task run "$TASK_ID"
ct task wait "$TASK_ID" --timeout 600

# Check status of any task
ct task status <task-id>

# List recent tasks
ct task list
```

### Notes

- Default model is `sonnet`; default budget is `$3.00 USD`. Override with `--model` / `--budget`.
- `ct task wait` polls every 5 seconds and exits with the task's terminal state on stdout.
- Subtasks inherit the current task as their parent automatically (via `$CLAUDOMATOR_TASK_ID`).
- Override parent with `--parent <task-id>` if needed.

## ADRs

See `docs/adr/001-language-and-architecture.md` for the Go + SQLite + WebSocket rationale.