Add AGENTS.md, CLAUDE.md, and OpenSkill AGENTS.md
CLAUDE.md points at AGENTS.md so any agent can use the same
project context. OpenSkill gets its own AGENTS.md documenting
the standalone rating library.

Assisted-by: Claude Opus 4.6 via pi
change xkuvzprxzuxlznsxktkyxytovxqtxott
commit 3f5a489d5c0dcfc7129dc8e4ce9430b602838d91
author Alpha Chen <alpha@kejadlen.dev>
date
parent ttpqlmxq
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..2f2bae8
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,62 @@
+# Agents
+
+## Project overview
+
+Rank King is a webapp for ranking items along multiple axes through pairwise comparisons, powered by OpenSkill (Plackett-Luce). Users create lists of items, define axes to rank along, then do head-to-head matchups. Ratings are derived from results.
+
+## Tech stack
+
+- Ruby 4, Roda (routing), Sequel (ORM), SQLite
+- Phlex for views (pure Ruby HTML)
+- Puma web server, Rack
+- Minitest for tests
+
+## Repository layout
+
+```
+config.ru          — Rack entrypoint
+Gemfile            — dependencies
+Rakefile           — rake tasks (test, dev)
+db/migrate/        — Sequel migrations
+lib/rank_king/     — app code
+  config.rb        — Config data class, reads from ENV
+  db.rb            — Sequel connection + migration runner
+  models.rb        — Sequel models (List, Item, Axis, Result)
+  web.rb           — Roda routes
+  views/           — Phlex view components
+lib/open_skill/    — OpenSkill rating library (pure Ruby)
+test/              — Minitest tests
+public/            — static assets
+.github/workflows/ — CI (GitHub Actions)
+```
+
+## Domain model
+
+- **List** has many Items and Axes
+- **Item** belongs to a List
+- **Axis** belongs to a List, has many Results
+- **Result** belongs to an Axis, a winner (Item), and a loser (Item)
+
+## Commands
+
+| Task | Command |
+|------|---------|
+| Run tests | `bundle exec rake test` |
+| Run dev server | `bundle exec rake dev` (uses `fd \| entr -r rackup`) |
+| Start server | `bundle exec rackup` |
+
+## Environment variables
+
+- `DATABASE_URL` — SQLite path (default: `:memory:`)
+- `SESSION_SECRET` — session signing key (auto-generated if unset)
+
+Tests set `DATABASE_URL=:memory:` in `test/test_helper.rb`.
+
+## Conventions
+
+- `$LOAD_PATH` is set to `lib/` in both `config.ru` and `Rakefile`
+- Sequel migrations live in `db/migrate/` with numeric prefixes
+- Models use `Sequel::Model` with `timestamps` plugin
+- Views are Phlex components under `lib/rank_king/views/`
+- CSRF protection is on for all POST routes
+- Config is a frozen `Data.define` value object
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..c43203a
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,3 @@
+# Claude
+
+See [AGENTS.md](AGENTS.md) for project context, commands, and conventions.
diff --git a/lib/open_skill/AGENTS.md b/lib/open_skill/AGENTS.md
new file mode 100644
index 0000000..a90b5f0
--- /dev/null
+++ b/lib/open_skill/AGENTS.md
@@ -0,0 +1,58 @@
+# OpenSkill library
+
+Ruby port of [openskill.js](https://github.com/philihp/openskill.js) — a Bayesian skill rating system using the Plackett-Luce model.
+
+## Purpose
+
+This is a standalone, pure-Ruby rating library with no external dependencies. It is used by the Rank King app but has no coupling to it.
+
+## Files
+
+| File | Role |
+|------|------|
+| `rating.rb` | `Rating` value object (`Data.define(:mu, :sigma)`) |
+| `game.rb` | `Game` — structures placements, ties, and teams; `TeamRating` aggregation |
+| `model.rb` | `Model` — top-level API: `rating`, `rate(game)`, `ordinal(rating)` |
+| `plackett_luce.rb` | `PlackettLuce` — the rating update algorithm |
+| `statistics.rb` | `Statistics` — normal distribution helpers (`phi_minor`, `phi_major`, `v`, `w`, `vt`, `wt`) |
+| `util.rb` | `Util` — shared math helpers (`util_c`, `util_sum_q`, `util_a`, `gamma`, `score`, `ladder_pairs`) |
+
+## Key concepts
+
+- **mu** (μ) — estimated skill mean (default 25)
+- **sigma** (σ) — uncertainty / standard deviation (default μ/z ≈ 8.333)
+- **beta** (β) — performance variability per match (default σ/2)
+- **ordinal** — display rating: `target + alpha * (mu - z * sigma)`
+- **tau** (τ) — optional additive dynamics factor that inflates sigma before rating, keeping ratings pliable over time
+- **limit_sigma** — when true with tau, caps sigma so ordinal never drops after a win
+
+## Usage
+
+```ruby
+model = OpenSkill::Model.new
+a = model.rating  # Rating(mu: 25, sigma: 8.333...)
+b = model.rating
+
+# a beats b
+new_a, new_b = model.rate(OpenSkill::Game.new([[a]], [[b]]))
+
+# Ties: second and third place tied
+model.rate(OpenSkill::Game.new([[first]], [[tied_a], [tied_b]], [[last]]))
+
+# Teams
+model.rate(OpenSkill::Game.new([[p1, p2]], [[p3, p4]]))
+
+model.ordinal(new_a.first)  # => scalar rating for display
+```
+
+## Tests
+
+Tests live at `test/test_open_skill.rb` (project root). They are ported from the upstream JS test suite and verify statistical helpers, model defaults, ordinal calculation, and Plackett-Luce rating updates including ties, teams, tau, and series.
+
+Run with: `bundle exec rake test`
+
+## Conventions
+
+- Value objects use `Data.define` (frozen, immutable)
+- No mutation — `rate` returns new Rating arrays
+- Floating-point assertions use `assert_in_delta` with tolerances matching the JS reference implementation's precision