Skip to content

Commit 3efaaa2

Browse files
committed
chore: add CLAUDE.md and rules for Claude Code guidance
1 parent 6579f5e commit 3efaaa2

5 files changed

Lines changed: 218 additions & 0 deletions

File tree

.claude/CLAUDE.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
# Octi Server
6+
7+
Sync server for the [Octi](https://github.com/d4rken-org/octi) Android app. Devices in the same account push/pull module data through this server, with real-time WebSocket notifications.
8+
9+
- **Package**: `eu.darken.octi.kserver`
10+
- **Architecture**: Ktor 3 + Dagger 2 DI + file-based JSON persistence (no database)
11+
- **Key tech**: Kotlin coroutines, kotlinx-serialization, Netty, KSP
12+
13+
## Rules
14+
15+
- [Architecture](rules/architecture.md) — Domain hierarchy, routing, persistence, sync flow, auth
16+
- [Build Commands](rules/build-commands.md) — Gradle commands, running locally, Docker, CI
17+
- [Testing](rules/testing.md) — TestRunner, integration tests, helpers
18+
- [Commit Guidelines](rules/commit-guidelines.md) — Commit message format and examples

.claude/rules/architecture.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Architecture
2+
3+
## Domain Hierarchy
4+
5+
Entities nest: **Account → Device → Module**. This maps directly to both the code packages and the on-disk storage layout.
6+
7+
```
8+
dataPath/accounts/{accountId}/
9+
├── account.json
10+
├── shares/{shareId}.json
11+
└── devices/{deviceId}/
12+
├── device.json
13+
└── modules/{sha1(moduleId)}/
14+
├── blob (binary payload)
15+
└── meta.json (Module.Info)
16+
```
17+
18+
## Startup Flow
19+
20+
```
21+
App.main(args) → parse CLI config → DaggerAppComponent.build() → App.launch() → Server.start()
22+
```
23+
24+
`Server.start()` installs Ktor middleware (logging, WebSockets, content negotiation, body limits, rate limiting, status pages), then registers all routes.
25+
26+
## Routing
27+
28+
Each domain has a `*Route` class (`@Singleton`, injected by Dagger) with a `setup(Routing)` method called from `Server.kt`. All endpoints live under `/v1/`.
29+
30+
| Route | Path | Purpose |
31+
|-------|------|---------|
32+
| AccountRoute | `/v1/account` | Create/delete accounts |
33+
| ShareRoute | `/v1/account/share` | Generate/consume share codes |
34+
| DeviceRoute | `/v1/devices` | List/delete/reset devices |
35+
| ModuleRoute | `/v1/module/{moduleId}` | Read/write/delete module data |
36+
| WsRoute | `/v1/ws` | WebSocket sync notifications |
37+
| StatusRoute | `/v1/status` | Health check |
38+
| MyIpRoute | `/v1/myip` | Client IP echo |
39+
40+
## Persistence
41+
42+
**No database.** All state is JSON files on disk + in-memory `ConcurrentHashMap` caches.
43+
44+
- Repos (`AccountRepo`, `DeviceRepo`, `ModuleRepo`, `ShareRepo`) load everything into memory at startup via `runBlocking` in `init {}`.
45+
- File writes use `kotlinx.serialization`.
46+
- `Device` operations are protected by a per-device `Mutex`.
47+
- Module IDs are SHA-1 hashed for safe directory names.
48+
49+
## Sync Flow
50+
51+
1. Device A writes module → `POST /v1/module/{moduleId}`
52+
2. `ModuleRepo` stores data, calls `SyncNotifier.enqueue()`
53+
3. `SyncNotifier` debounces 500ms, then broadcasts `ModuleChanged` event
54+
4. `ConnectionRegistry` delivers event to all WebSocket sessions in the account except the originator
55+
5. Device B receives event, fetches updated data via `GET /v1/module/{moduleId}`
56+
57+
## Authentication
58+
59+
- `X-Device-ID` header: device UUID
60+
- `Authorization: Basic base64(accountId:devicePassword)` header
61+
- Device password: 50 random bytes, generated at registration, constant-time comparison
62+
- Helper: `HttpExtensions.authenticateDevice()` — parses headers, looks up device, verifies credentials, updates `lastSeen`
63+
64+
## Background Jobs
65+
66+
All run in `AppScope` (application-wide `SupervisorJob + Dispatchers.Default`):
67+
68+
- **Account GC**: removes accounts with no devices after 10 min
69+
- **Device expiration**: removes devices not seen in 90 days
70+
- **Module expiration**: removes modules not accessed in 90 days
71+
- **Share expiration**: cleans up expired share codes (60 min TTL)
72+
73+
## WebSocket Connection Limits
74+
75+
Managed by `ConnectionRegistry`:
76+
- Per account: 64
77+
- Per IP: 32
78+
- Global: 10,000
79+
- Frame rate: 120 frames/min per connection
80+
- Oldest session evicted when per-account limit exceeded

.claude/rules/build-commands.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Build Commands
2+
3+
## Building
4+
5+
```bash
6+
# Build distribution
7+
./gradlew clean installDist
8+
9+
# Assemble only (CI uses this)
10+
./gradlew assemble
11+
```
12+
13+
Output: `./build/install/octi-server/`
14+
15+
## Testing
16+
17+
```bash
18+
# Run all tests
19+
./gradlew test
20+
21+
# Full checks (tests + verification)
22+
./gradlew check
23+
24+
# Run a single test class
25+
./gradlew test --tests "eu.darken.octi.kserver.module.ModuleFlowTest"
26+
27+
# Run a single test method
28+
./gradlew test --tests "eu.darken.octi.kserver.module.ModuleFlowTest.writing a module"
29+
```
30+
31+
## Running Locally
32+
33+
```bash
34+
./build/install/octi-server/bin/octi-server --datapath=./octi-data
35+
```
36+
37+
CLI arguments:
38+
- `--datapath=<path>` (required) — data storage directory
39+
- `--port=<num>` — server port (default: 8080)
40+
- `--debug` — verbose logging
41+
- `--disable-rate-limits` — disable rate limiting
42+
43+
## Docker
44+
45+
```bash
46+
docker run -v octi-data:/etc/octi-server -p 8080:8080 ghcr.io/d4rken-org/octi-server
47+
```
48+
49+
Environment variables: `OCTI_PORT`, `OCTI_DEBUG`, `OCTI_DATA_DIR`
50+
51+
## CI
52+
53+
- **code-checks.yml**: runs `assemble` + `check` on push to main and PRs
54+
- **release-tag.yml**: builds `installDist`, zips, creates GitHub release on `v*` tags
55+
- JDK 17 (adopt), Gradle 8.13
56+
57+
## Context Management
58+
59+
When running gradle builds or tests, use the Task tool with a sub-agent to keep verbose output isolated from the main conversation context. Run gradle directly only when the user explicitly requests full output.

.claude/rules/commit-guidelines.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Commit Message Guidelines
2+
3+
## Format
4+
5+
- Imperative mood, present tense ("Add feature" not "Added feature")
6+
- First line: 50-60 characters max
7+
- No module prefix — flat commit messages
8+
- Optionally add a blank line and detailed description body
9+
10+
## Examples from History
11+
12+
```
13+
Restrict GITHUB_TOKEN permissions to minimum required
14+
Rename project from octi-sync-server-kotlin to octi-server
15+
Remove idle timeout cleanup, rely on Ktor ping/pong for WS liveness
16+
Add WebSocket-based real-time sync notifications
17+
Add share code consumption and account linking
18+
```
19+
20+
## Prefixes
21+
22+
- `fix:` / `chore:` / `feat:` style prefixes are used (lowercase, with colon)
23+
- **Release commits**: `Release: {version}`
24+
- **Dependency upgrades**: `Upgrade {dependency} from {old} to {new}`

.claude/rules/testing.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Testing
2+
3+
## Stack
4+
5+
JUnit 5 + Kotest matchers + MockK + Ktor test host
6+
7+
## Test Infrastructure
8+
9+
### `TestRunner` (base class)
10+
- Starts a real server on a random port with a temp data directory
11+
- Provides an HTTP client pre-configured with JSON content negotiation
12+
- Tears down server and deletes temp data after each test
13+
14+
### `runTest2 { }` lambda
15+
All integration tests use this pattern — it sets up the environment and provides a receiver with helper functions.
16+
17+
### `TestRunnerExtensions.kt` helpers
18+
Pre-built flows for common operations:
19+
- `createDevice()` / `createDeviceRaw()` — register a device and get credentials
20+
- `createShareCode()` — generate a share code for an account
21+
- `addDeviceId()` / `addAuth()` / `addCredentials()` — attach auth headers
22+
- `readModule()` / `writeModule()` / `deleteModule()` — module CRUD
23+
- `listDevices()` / `deleteDevice()` — device management
24+
25+
### Data classes
26+
- `Credentials` — wraps device ID + auth pair
27+
- `Auth` — account ID + password
28+
29+
## Test Organization
30+
31+
- `*FlowTest` — integration tests with full HTTP round-trips (most tests are this type)
32+
- `*RepoTest` — unit tests for persistence layer
33+
- `*Test` — unit tests for individual components (RateLimiter, ConnectionRegistry, etc.)
34+
35+
## Assertions
36+
37+
Uses Kotest matchers: `shouldBe`, `shouldNotBe`, `shouldBeNull`, etc.

0 commit comments

Comments
 (0)