Live App: https://simc-rotation.app
AI-powered World of Warcraft rotation guides generated directly from SimulationCraft Action Priority Lists (APLs). Each guide is built by feeding the raw .simc APL file into Claude, producing a structured, human-readable rotation guide that stays automatically synchronized with the SimC midnight branch.
SimC APLs are the authoritative source for optimal rotation logic, but they are written in a domain-specific scripting language that is difficult to read. This app translates those APLs into clear, prioritized rotation guides using Claude.
Every spec across all 13 classes is tracked. When SimC's APL changes (detected via commit SHA), the guide is automatically regenerated. Historical versions are preserved so you can see how a rotation evolved over patches.
- 40 WoW specs across all 13 classes
- AI-generated guides from live SimC APLs using
claude-sonnet-4-6 - Auto-sync - daily cron detects APL changes via GitHub commit SHA and regenerates only what changed
- Guide history - every generated version is archived with its APL commit SHA, generation date, and model used
- Changelog tracking - each regenerated guide is compared against the previous version by Claude to summarize what changed
- Ask AI - Q&A chatbot lets you ask questions about any spec's rotation (requires QA API key)
- Complexity rankings - specs ranked by how many entries their single-target and AoE priority lists contain
- Dark mode - full dark/light theme toggle with persistent preference
- Responsive UI with class-colored sidebar, role badges, and priority list rendering
- Node.js 20+
- npm 10+
- Anthropic API key - console.anthropic.com. Only needed to generate guides or use Ask AI. The default local dev path seeds placeholder sample guides without one.
- (Optional) GitHub personal access token for higher API rate limits
Copy one of the example files to .env in the project root. .env.example.local is the local development template, .env.example documents every supported variable, and .env.example.prod covers the split Railway/Vercel deployment.
cp .env.example.local .envDo not copy
.env.exampleverbatim for local work: it ships production values (CORS_ORIGIN=https://simc-rotation.app,DB_PATH=/app/data/db.sqlite) that will block requests fromhttp://localhost:5173.
| Variable | Required | Default | Description |
|---|---|---|---|
ANTHROPIC_API_KEY |
For generation and Q&A | -- | Anthropic API key for Claude |
ADMIN_SECRET |
Production | Auto-generated in dev | Bearer token for admin endpoints |
NODE_ENV |
No | Unset (production in the backend image) |
production makes ADMIN_SECRET mandatory (startup fails without it) and suppresses the dev QA key |
PORT |
No | 3001 |
Backend HTTP port |
DB_PATH |
No | packages/backend/data/db.sqlite |
Path to SQLite database file |
ANTHROPIC_MODEL |
No | claude-sonnet-4-6 |
Claude model to use |
PROMPT_VERSION |
No | 1.0.0 |
Logged alongside generated guides |
GITHUB_TOKEN |
No | -- | GitHub PAT (avoids 60 req/hr rate limit) |
CRON_SCHEDULE |
No | 0 3 * * * |
Cron schedule (default: 3 AM UTC daily) |
CORS_ORIGIN |
No | http://localhost:5173 |
Allowed CORS origin |
VITE_API_BASE_URL |
Frontend | Unset (requests go to relative /api) |
Backend origin, appended with /api. Set this for split deployments such as Vercel. |
Additional optional configuration (see .env.example for full list):
| Variable | Default | Description |
|---|---|---|
RATE_LIMIT_GENERAL |
120 |
Requests per minute for public API |
RATE_LIMIT_ADMIN |
10 |
Requests per minute for admin endpoints |
RATE_LIMIT_QA |
5 |
Requests per minute for Q&A endpoints |
QA_MAX_LENGTH |
1000 |
Max character length for Q&A questions |
CHANGELOG_CACHE_TTL_MS |
3600000 |
Changelog cache TTL (default 1 hour) |
SIMC_REPO |
simulationcraft/simc |
SimulationCraft GitHub repository |
SIMC_BRANCH |
midnight |
SimC branch to track |
SIMC_APL_PATH |
ActionPriorityLists/default |
APL directory within the SimC repo |
PROJECT_REPO |
Brandon82/SimCRotationGuides |
This project's GitHub repo (for changelog) |
# 1. Install all workspace dependencies
npm install
# 2. Start the backend (http://localhost:3001)
npm run dev:backend
# 3. Start the frontend (http://localhost:5173)
npm run dev:frontendOn first start the backend creates the SQLite file and, if the guides table is empty, seeds placeholder sample guides for all 40 specs. That content comes from role-based templates rather than real APL analysis, so no Anthropic API key is required for this path. Unless NODE_ENV=production, the seed also creates a QA API key labelled dev and prints it to the console for testing Ask AI (skipped if an active dev key already exists). To re-run the sample seed manually, use npm run seed:sample; it skips any spec that already has a current guide.
Full seed (optional): To generate fresh guides from the live SimC APLs using your own API key, run
npm run seed --workspace packages/backendinstead. Estimated cost is $1-5 depending on APL length and model pricing.
docker-compose up- Backend:
http://localhost:3001(image runs withNODE_ENV=productionandDB_PATH=/app/data/db.sqlite) - Frontend:
http://localhost:5173(nginx serves the built SPA on container port 80) - Database persisted in the named volume
db_data, mounted at/app/data
Compose passes ANTHROPIC_API_KEY, ADMIN_SECRET (falls back to dev-secret), GITHUB_TOKEN, ANTHROPIC_MODEL, and PROMPT_VERSION through from your root .env. VITE_API_BASE_URL is a frontend build arg (default http://localhost:3001), so pointing the UI at a different backend requires rebuilding the frontend image.
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Health check |
GET |
/api/specs |
All classes and specs with hasGuide flag |
GET |
/api/guides |
All guide entries, current and historical (metadata only) |
GET |
/api/guides/:specName |
Current guide for a spec |
GET |
/api/guides/:specName/history |
Historical guide list |
GET |
/api/guides/:specName/history/:id |
A specific historical guide |
GET |
/api/rankings |
Current guides ranked by how many entries their single_target and aoe priority lists contain (two separate rankings) |
GET |
/api/changelog |
Paginated commit history for this repo (?page defaults to 1, ?per_page defaults to 20 and is capped at 100) |
| Method | Path | Description |
|---|---|---|
GET |
/api/qa/validate |
Validate the X-QA-Key header. Always returns 200 with { "valid": true } or { "valid": false }, even when the header is absent |
POST |
/api/qa/ask |
Ask a question about a spec's rotation. Requires a valid X-QA-Key header (401 otherwise) |
POST /api/qa/ask body:
| Method | Path | Description |
|---|---|---|
POST |
/api/admin/refresh |
Trigger guide generation |
DELETE |
/api/admin/guides/history |
Delete old (non-current) guide versions |
POST |
/api/admin/backfill-changelog |
Generate changelogs for past guides |
DELETE |
/api/admin/changelogs |
Clear all changelogs |
POST |
/api/admin/qa-keys |
Create a QA API key |
GET |
/api/admin/qa-keys |
List all QA API keys |
DELETE |
/api/admin/qa-keys/:id |
Deactivate a QA API key |
POST /api/admin/refresh body:
{ "spec": "warrior_arms" } // Single spec
{ "class": "warrior" } // All specs in a class (takes precedence over "spec")
{ "spec": ["warrior_arms", "warrior_fury"] } // Multiple specs
{ "spec": "all" } // All 40 specs
{ "spec": "warrior_arms", "force": true } // Skip SHA check; combines with any form aboveDELETE /api/admin/guides/history body:
{} // All specs
{ "spec": "warrior_arms" } // One spec onlyPOST /api/admin/backfill-changelog body:
{ "spec": "warrior_arms" } // Single spec, current guide only ("mode" defaults to "current")
{ "spec": "warrior_arms", "mode": "all" } // Single spec, all history
{ "spec": "all" } // All specs (runs in background; "mode" applies here too)spec is required; a request without it returns 400.
POST /api/admin/qa-keys body:
{ "label": "Brandon" } // Returns { id, apiKey, label }Defaults (configurable via env vars):
| Tier | Limit | Endpoints |
|---|---|---|
| General | 120 req/min | specs, guides, rankings, changelog |
| Admin | 10 req/min | all /api/admin/* routes |
| Q&A | 5 req/min | all /api/qa/* routes, including /validate |
Limits are per IP over a 60 second window. /api/health sits outside all three tiers and is not rate limited.
| Path | Page |
|---|---|
/ |
Home - class selector and spec grid |
/guide/:specName |
Spec guide with changelog and version history |
/rankings |
Specs ranked by priority list length (ST and AoE) |
/history |
Filterable table of all generated guides |
/ask-ai |
Q&A chatbot for asking about spec rotations |
/changelog |
Project commit history pulled from GitHub |
* |
Not Found page for any unmatched path |
All spec definitions live in packages/backend/src/data/specs.ts. Each entry is a class with a name, label, Blizzard class color, and a specs array. Each spec in that array has its own name (the API and DB key, e.g. warrior_arms), label, and role (dps, healer, or tank). An optional per-spec aplName overrides the SimC filename, which otherwise defaults to the spec's name plus .simc, for cases where SimC doesn't follow that convention (e.g. death_knight_blood maps to deathknight_blood).
After adding a new spec, trigger its first guide generation via the admin API.
LLM prompts live in packages/backend/src/prompts/:
| File | Purpose |
|---|---|
guidePrompt.ts |
Main APL-to-guide generation. Instructs Claude to translate SimC conditions into plain English and output structured JSON with sections: overview, talent_notes, precombat, single_target, aoe, items_and_racials |
qaPrompt.ts |
Q&A answering. Provides the guide content and raw APL as context for answering user questions about rotations. Output is short conversational markdown, explicitly not JSON |
changelogPrompt.ts |
Changelog generation. Compares old and new guide versions and outputs a bare JSON array of 3-8 bullet-point strings describing what changed |
The PROMPT_VERSION env var is stored alongside each guide for traceability when the prompt is updated.
Root npm scripts:
| Script | Description |
|---|---|
npm run dev:backend |
Backend dev server (tsx watch) |
npm run dev:frontend |
Frontend dev server (Vite) |
npm run build:backend |
Compile the backend with tsc |
npm run build:frontend |
Type-check and Vite-build the frontend |
npm run build |
Both builds, backend first |
npm run lint |
ESLint over the frontend workspace |
npm run seed:sample |
Re-run the sample data seed |
Backend-only scripts, run with --workspace packages/backend: start (serve the compiled build), seed (full generation from live APLs), generate (regenerate a single spec), create-qa-key (issue a QA key against the local database).
Helper scripts live in the scripts/ directory:
| Script | Description |
|---|---|
dev.sh |
Starts both backend and frontend dev servers in parallel (kills both on Ctrl+C) |
dev-backend.sh |
Starts the backend dev server only |
dev-frontend.sh |
Starts the frontend dev server only |
admin_api_helper.sh |
Interactive admin CLI for managing the deployed backend (see below) |
scripts/admin_api_helper.sh is an interactive Bash script for managing the deployed backend without crafting raw curl commands. It prompts for your Railway URL and admin secret, then presents a menu, runs the one action you pick, and exits:
| Action | Description |
|---|---|
| Refresh guides | Regenerate one spec, multiple specs, an entire class, or all 40 specs (with optional force flag to skip SHA check) |
| Delete old guides | Purge non-current historical guide versions for one or all specs |
| Backfill changelogs | Generate changelogs for existing guides that are missing them (one spec or all; current guide only or the full history) |
| Delete all changelogs | Clear every changelog entry (asks for confirmation first) |
| Create a QA key | Issue a new API key for the Ask AI feature |
| List QA keys | Show every QA key, active and revoked, including its ID, label, full key value, and last-used date |
| Revoke a QA key | Deactivate a specific QA key by ID |
bash scripts/admin_api_helper.sh
{ "specName": "warrior_arms", "question": "When should I use Mortal Strike?" } // Both fields are required. "question" is capped at QA_MAX_LENGTH (1000) characters.