summaryrefslogtreecommitdiff
path: root/CLAUDE.md
blob: 16a48b4a09a5dc5c43f226a917401c5c0f47e83f (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
# 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.ClaudeRunner` → `claude -p` subprocess → SQLite + log files

### Packages

| Package | Role |
|---|---|
| `internal/task` | `Task` struct, YAML parsing, state machine, validation |
| `internal/executor` | `Pool` (bounded goroutine pool) + `ClaudeRunner` (subprocess manager) |
| `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. `ClaudeRunner.Run()` invokes `claude -p <instructions> --output-format stream-json`
4. stdout streamed to `~/.claudomator/executions/<exec-id>/stdout.log`; cost parsed from stream-json
5. Execution result written to SQLite; broadcast via WebSocket to connected clients

**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.

## ADRs

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