195 lines
8.7 KiB
Markdown
195 lines
8.7 KiB
Markdown
# arr
|
|
|
|
Single-service replacement for Radarr and Sonarr, later Bazarr. Rust workspace,
|
|
API-first, SQLite, no authentication layer.
|
|
|
|
**Read `DESIGN.md` before touching anything.** It is the contract. Issues
|
|
reference its sections rather than restating them. If an issue and the design
|
|
document disagree, the design document wins and the issue is wrong — say so
|
|
rather than implementing the discrepancy.
|
|
|
|
## Repo layout
|
|
|
|
```
|
|
crates/
|
|
├── arr-core/ domain types, policy engine, scoring (no IO, no heavy deps)
|
|
├── arr-parse/ release name parsing (no IO)
|
|
├── arr-meta/ TMDB client
|
|
├── arr-indexer/ Torznab via Prowlarr
|
|
├── arr-dl/ Transmission RPC
|
|
├── arr-probe/ ffprobe wrapper
|
|
├── arr-db/ sqlx + migrations
|
|
├── arr-api/ axum + OpenAPI
|
|
├── arr-compat/ Radarr/Sonarr v3 shim for Jellyseerr
|
|
├── arr-daemon/ reconcile loop, wires everything
|
|
└── arr-e2e/ cross-process integration tests
|
|
web/ Vite + TypeScript SPA, embedded via include_dir
|
|
```
|
|
|
|
**The invariant that matters:** `arr-core` and `arr-parse` must never depend on
|
|
axum, sqlx or reqwest. They hold the logic that gets tested constantly and they
|
|
must stay fast to compile. Everything expensive is downstream of them. A pull
|
|
request that adds a heavy dependency to either is wrong.
|
|
|
|
## Conventions
|
|
|
|
- Dependency versions are pinned once in the root `[workspace.dependencies]`.
|
|
Member crates reference them by name, never by version.
|
|
- Lints are declared once in the root `[workspace.lints]`. Member crates carry
|
|
only `[lints] workspace = true`.
|
|
- Commits follow Conventional Commits, subject ≤ 50 characters, body only when
|
|
the "why" is not obvious from the diff.
|
|
- No `unwrap()` in non-test code. The lint is on.
|
|
- SQL goes through `sqlx` compile-time-checked queries.
|
|
|
|
## Design and frontend work
|
|
|
|
**Any issue labelled `area/web`, and any work that decides how something looks
|
|
or behaves on screen, goes through the `impeccable` skill.** That includes the
|
|
design system itself, component work, layout, visual hierarchy, empty and error
|
|
states, and any change to the UI described in `DESIGN.md` §9.2 and §9.3. Invoke
|
|
it before writing markup, not as a review pass afterwards.
|
|
|
|
`.impeccable/design.json` is the token system — colours, typography, spacing.
|
|
It is authored once by that skill and then treated as the source of truth;
|
|
components consume tokens rather than literal values. `.impeccable/live/config.json`
|
|
points the skill's live browser iteration at `web/index.html`.
|
|
|
|
`~/tea/arcada` has the same setup and is worth reading for the shape of a
|
|
finished `design.json`.
|
|
|
|
This matters more here than in a typical CRUD app because the manual-search
|
|
view (§9.3) is the whole reason for the project's UI existing — Radarr's is
|
|
unusable specifically because of a layout decision. Getting it right is a design
|
|
problem, not a markup problem.
|
|
|
|
## Working an issue
|
|
|
|
1. Read `DESIGN.md`, then the issue, then the sections the issue cites.
|
|
2. Branch from `main`. One issue per branch.
|
|
3. Implement the issue as scoped. Do not widen it. If you find adjacent work,
|
|
open a new issue and keep going.
|
|
4. `just ci` must pass locally before pushing. That runs the same gate as CI.
|
|
5. Open a pull request referencing the issue number.
|
|
|
|
If an issue turns out to be blocked by something not yet built, say so on the
|
|
issue and stop. Do not implement the dependency inline — it has its own issue,
|
|
or it needs one.
|
|
|
|
## Label taxonomy
|
|
|
|
Every issue carries exactly one `phase/`, one `area/`, one `difficulty/`, and
|
|
one `type/`. Dependencies are expressed in the issue body as `Depends on: #N`.
|
|
|
|
### `phase/` — build order, from `DESIGN.md` §13
|
|
|
|
| Label | Meaning |
|
|
|---|---|
|
|
| `phase/1-skeleton` | workspace, CI, config, database, empty API and SPA |
|
|
| `phase/2-logic` | parsing and the policy engine, pure, no network |
|
|
| `phase/3-sourcing` | TMDB and Prowlarr, read-only, grabs nothing |
|
|
| `phase/4-movies` | movies end to end, first real cutover test |
|
|
| `phase/5-ui` | search, buckets, library views, queues |
|
|
| `phase/6-tv` | seasons, episodes, tracking, derived status |
|
|
| `phase/7-people` | owner tags and notifications |
|
|
| `phase/8-compat` | Jellyseerr shim |
|
|
|
|
Phases are ordered but not strictly serial — issues within a phase often
|
|
parallelise, and some later-phase issues unblock early. `Depends on:` is
|
|
authoritative, the phase label is a hint.
|
|
|
|
### `area/` — which crate
|
|
|
|
`area/core`, `area/parse`, `area/meta`, `area/indexer`, `area/dl`,
|
|
`area/probe`, `area/db`, `area/api`, `area/compat`, `area/daemon`, `area/web`,
|
|
`area/ci`, `area/infra`
|
|
|
|
### `difficulty/` — drives model selection
|
|
|
|
| Label | Shape of the work |
|
|
|---|---|
|
|
| `difficulty/trivial` | one file, mechanical, no design decisions |
|
|
| `difficulty/easy` | bounded, obvious approach, few files |
|
|
| `difficulty/moderate` | multiple files, some judgement, a real interface to design |
|
|
| `difficulty/hard` | subtle correctness, cross-cutting, or the design document itself is thin here |
|
|
|
|
### `type/`
|
|
|
|
`type/feature`, `type/chore`, `type/test`, `type/bug`
|
|
|
|
## Driving this autonomously
|
|
|
|
A driver session should be able to work from `DESIGN.md` plus the issue list
|
|
alone. The loop:
|
|
|
|
1. List open issues. Drop any whose `Depends on:` references an open issue.
|
|
2. Of what remains, take the lowest phase number first, then whatever
|
|
parallelises within it.
|
|
3. Spawn one session per issue, selecting the model from `difficulty/`.
|
|
4. Wait for CI green on the pull request before marking the issue done and
|
|
recomputing the ready set.
|
|
|
|
### Model selection
|
|
|
|
Sessions are spawned with `aoe`. Each difficulty tier has **two models**, and
|
|
issues at that tier are distributed between them — not run twice. Splitting
|
|
across two providers keeps a single provider's rate limits or an outage from
|
|
stalling the whole queue, and keeps one model's blind spots from shaping the
|
|
entire codebase.
|
|
|
|
| Difficulty | Models | Spawn |
|
|
|---|---|---|
|
|
| `difficulty/hard` | Fable 5 | `aoe add <path> --tool claude --extra-args "--model claude-fable-5" -l` |
|
|
| | Sol | `aoe add <path> --tool codex --extra-args "-m gpt-5.6-sol" -l` |
|
|
| `difficulty/moderate` | Opus 5 | `aoe add <path> --tool claude --extra-args "--model claude-opus-5" -l` |
|
|
| | Sol, low effort | `aoe add <path> --tool codex --extra-args "-m gpt-5.6-sol -c model_reasoning_effort=low" -l` |
|
|
| `difficulty/easy` | Sonnet 5 | `aoe add <path> --tool claude --extra-args "--model claude-sonnet-5" -l` |
|
|
| | Terra | `aoe add <path> --tool codex --extra-args "-m gpt-5.6-terra" -l` |
|
|
| `difficulty/trivial` | Terra | `aoe add <path> --tool codex --extra-args "-m gpt-5.6-terra" -l` |
|
|
|
|
Alternate within a tier rather than draining one model first, so a bad run is
|
|
visible early instead of after ten issues.
|
|
|
|
Use `-w <branch> -b` to put each session in its own git worktree, which is what
|
|
makes parallel issues safe.
|
|
|
|
Caveats worth resolving before a long unattended run:
|
|
|
|
- `gpt-5.6-terra` is confirmed — it is the default in `~/.codex/config.toml`.
|
|
`gpt-5.6-sol` is inferred from the sibling naming and unverified.
|
|
- `-c model_reasoning_effort=low` is the documented codex config-override form
|
|
but has not been exercised here.
|
|
|
|
**`area/web` issues run on Fable 5 regardless of their difficulty label**, since
|
|
they go through the `impeccable` skill and the UI is the reason this project
|
|
has a frontend at all (see the design section above).
|
|
|
|
Escalate one tier if a session fails CI twice on the same issue — and escalate
|
|
to the *other* model at that tier first, before going up. Never de-escalate
|
|
mid-issue.
|
|
|
|
### What a driver must not do
|
|
|
|
- Do not run more than one session per crate at a time. Cargo workspace
|
|
builds contend, and two sessions editing the same crate produce conflicts
|
|
that cost more than the parallelism saved.
|
|
- Do not start `phase/4-movies` work until `phase/2-logic` is complete. The
|
|
policy engine is the thing everything else calls.
|
|
- Do not merge a pull request that skips tests to get CI green. Failing tests
|
|
are the signal the design document is wrong somewhere; surface it.
|
|
|
|
## Environment
|
|
|
|
- **Prowlarr** — `prowlarr` container on the Dokploy host, port 9696. Owns
|
|
tracker auth, FlareSolverr and the Cardigann definitions. Not replaced.
|
|
- **Transmission** — native in LXC 130 at `10.6.10.45:9091`, RPC
|
|
unauthenticated. Download dir `/mnt/media/transmission/complete`.
|
|
- **Jellyfin** — native in LXC at `10.6.10.18:8096`. Library roots under
|
|
`/mnt/media-v2`.
|
|
- **Media** — ZFS, single dataset, bind-mounted as `/mnt/media`. Downloads and
|
|
library share it, so hardlinks work.
|
|
- **ntfy** — running on the Dokploy host, one topic per person.
|
|
|
|
Development happens on the same machine that runs the stack, so all of the
|
|
above are directly reachable. Never point tests at a live tracker.
|