chore: unify agent context into AGENTS.md (#320) (#322)

* chore: add staging smoke test script

Adds scripts/smoke_test_staging.py to verify API health and rate limiting configuration in the staging environment.

* chore: unify agent context into AGENTS.md

Consolidates CLAUDE.md and GEMINI.md into a single AGENTS.md file. Adds symlinks for compatibility.

authored by zzstoatzz.io and committed by GitHub 88c72080 f606fc11

Changed files
+70 -60
.github
backend
alembic
scripts
src
tests
.dockerignore backend/.dockerignore
.env.example backend/.env.example
+3 -3
.github/path-filters.yml
··· 1 1 migrations: 2 - - "alembic/versions/**" 3 - - "alembic/env.py" 4 - - "alembic.ini" 2 + - "backend/alembic/versions/**" 3 + - "backend/alembic/env.py" 4 + - "backend/alembic.ini"
+1 -1
.github/workflows/deploy-prod.yml
··· 25 25 if [ "${{ steps.changes.outputs.migrations }}" == "true" ]; then 26 26 echo "🔄 migrations detected - will run via release_command before deployment" 27 27 fi 28 - flyctl deploy --config fly.toml --remote-only -a relay-api 28 + flyctl deploy --config backend/fly.toml --remote-only -a relay-api 29 29 env: 30 30 FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_PROD }}
+8 -8
.github/workflows/deploy-staging.yml
··· 5 5 branches: 6 6 - main 7 7 paths: 8 - - "src/**" 9 - - "pyproject.toml" 10 - - "uv.lock" 11 - - "Dockerfile" 12 - - "fly.staging.toml" 13 - - "alembic/**" 14 - - "alembic.ini" 8 + - "backend/src/**" 9 + - "backend/pyproject.toml" 10 + - "backend/uv.lock" 11 + - "backend/Dockerfile" 12 + - "backend/fly.staging.toml" 13 + - "backend/alembic/**" 14 + - "backend/alembic.ini" 15 15 - ".github/workflows/deploy-staging.yml" 16 16 workflow_dispatch: 17 17 ··· 36 36 if [ "${{ steps.changes.outputs.migrations }}" == "true" ]; then 37 37 echo "🔄 migrations detected - will run via release_command before deployment" 38 38 fi 39 - flyctl deploy --config fly.staging.toml --remote-only -a relay-api-staging 39 + flyctl deploy --config backend/fly.staging.toml --remote-only -a relay-api-staging 40 40 env: 41 41 FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_STAGING }}
+3 -2
.github/workflows/run-pre-commit.yml
··· 20 20 uses: astral-sh/setup-uv@v7 21 21 with: 22 22 enable-cache: true 23 - cache-dependency-glob: "uv.lock" 23 + cache-dependency-glob: "backend/uv.lock" 24 24 25 25 - name: install bun 26 26 uses: oven-sh/setup-bun@v2 ··· 34 34 ${{ runner.os }}-bun- 35 35 36 36 - name: install dependencies 37 - run: uv sync 37 + run: cd backend && uv sync 38 38 39 39 - name: install frontend dependencies 40 40 run: cd frontend && bun install ··· 44 44 45 45 - name: check lockfile is up to date 46 46 run: | 47 + cd backend 47 48 if ! uv lock --check; then 48 49 echo "❌ lockfile is out of date!" 49 50 echo "to update the lockfile, run 'uv lock'."
+7 -7
.github/workflows/test-backend.yml
··· 3 3 on: 4 4 pull_request: 5 5 paths: 6 - - "src/backend/**" 7 - - "tests/**" 8 - - "pyproject.toml" 9 - - "uv.lock" 6 + - "backend/src/backend/**" 7 + - "backend/tests/**" 8 + - "backend/pyproject.toml" 9 + - "backend/uv.lock" 10 10 - ".github/workflows/test-backend.yml" 11 11 12 12 permissions: ··· 44 44 uses: astral-sh/setup-uv@v7 45 45 with: 46 46 enable-cache: true 47 - cache-dependency-glob: "uv.lock" 47 + cache-dependency-glob: "backend/uv.lock" 48 48 49 49 - name: install dependencies 50 - run: uv sync --locked 50 + run: cd backend && uv sync --locked 51 51 52 52 - name: run tests 53 53 env: 54 54 DATABASE_URL: postgresql+asyncpg://relay_test:relay_test@localhost:5432/relay_test 55 - run: uv run pytest tests/ 55 + run: cd backend && uv run pytest tests/ 56 56 57 57 - name: prune uv cache 58 58 if: always()
+2 -1
.pre-commit-config.yaml
··· 5 5 rev: v0.24.1 6 6 hooks: 7 7 - id: validate-pyproject 8 + files: ^backend/pyproject\.toml$ 8 9 9 10 - repo: https://github.com/pre-commit/mirrors-prettier 10 11 rev: v3.1.0 ··· 23 24 hooks: 24 25 - id: type-check 25 26 name: type check 26 - entry: uv run ty check 27 + entry: bash -c 'cd backend && uv run ty check' 27 28 language: system 28 29 types: [python] 29 30 pass_filenames: false
Dockerfile backend/Dockerfile
alembic.ini backend/alembic.ini
alembic/README backend/alembic/README
alembic/env.py backend/alembic/env.py
alembic/script.py.mako backend/alembic/script.py.mako
alembic/versions/2025_11_01_13_34_23_aabe82c8f0fb_add_features_column_to_tracks.py backend/alembic/versions/2025_11_01_13_34_23_aabe82c8f0fb_add_features_column_to_tracks.py
alembic/versions/2025_11_01_14_18_45_547a171dfd11_drop_old_artist_columns.py backend/alembic/versions/2025_11_01_14_18_45_547a171dfd11_drop_old_artist_columns.py
alembic/versions/2025_11_02_12_21_41_06daebe03213_add_user_preferences_table.py backend/alembic/versions/2025_11_02_12_21_41_06daebe03213_add_user_preferences_table.py
alembic/versions/2025_11_02_12_22_19_9e8c7aa5b945_add_user_preferences_table_fixed.py backend/alembic/versions/2025_11_02_12_22_19_9e8c7aa5b945_add_user_preferences_table_fixed.py
alembic/versions/2025_11_02_14_04_59_31e69ba0c570_add_timezone_support_to_datetime_columns.py backend/alembic/versions/2025_11_02_14_04_59_31e69ba0c570_add_timezone_support_to_datetime_columns.py
alembic/versions/2025_11_02_20_53_02_846cc6b867b8_add_oauth_states_table.py backend/alembic/versions/2025_11_02_20_53_02_846cc6b867b8_add_oauth_states_table.py
alembic/versions/2025_11_03_10_15_32_ec40ac6453bc_add_exchange_tokens_table_for_secure_.py backend/alembic/versions/2025_11_03_10_15_32_ec40ac6453bc_add_exchange_tokens_table_for_secure_.py
alembic/versions/2025_11_03_18_13_14_5684967eb462_add_queue_state_table.py backend/alembic/versions/2025_11_03_18_13_14_5684967eb462_add_queue_state_table.py
alembic/versions/2025_11_04_00_27_13_008ffaa79bea_merge_queue_and_auto_advance_migrations.py backend/alembic/versions/2025_11_04_00_27_13_008ffaa79bea_merge_queue_and_auto_advance_migrations.py
alembic/versions/2025_11_04_04_10_00_bcb5c0fd5d43_add_auto_advance_preference.py backend/alembic/versions/2025_11_04_04_10_00_bcb5c0fd5d43_add_auto_advance_preference.py
alembic/versions/2025_11_09_02_23_52_ba46ea4ba64e_remove_unique_constraint_from_tracks_.py backend/alembic/versions/2025_11_09_02_23_52_ba46ea4ba64e_remove_unique_constraint_from_tracks_.py
alembic/versions/2025_11_09_14_34_08_36868f2c20e5_add_image_id_to_tracks.py backend/alembic/versions/2025_11_09_14_34_08_36868f2c20e5_add_image_id_to_tracks.py
alembic/versions/2025_11_10_17_10_08_2d9a62e170d6_add_pds_url_to_artists_table_for_caching.py backend/alembic/versions/2025_11_10_17_10_08_2d9a62e170d6_add_pds_url_to_artists_table_for_caching.py
alembic/versions/2025_11_11_12_11_10_32c17ef04e98_add_track_likes_table_for_indexing_.py backend/alembic/versions/2025_11_11_12_11_10_32c17ef04e98_add_track_likes_table_for_indexing_.py
alembic/versions/2025_11_12_01_54_02_14fff0296365_add_image_url_to_tracks.py backend/alembic/versions/2025_11_12_01_54_02_14fff0296365_add_image_url_to_tracks.py
alembic/versions/2025_11_12_02_06_23_2d6d201752ef_add_performance_indexes_on_track_.py backend/alembic/versions/2025_11_12_02_06_23_2d6d201752ef_add_performance_indexes_on_track_.py
alembic/versions/2025_11_12_14_34_51_07c4ad820a27_add_notification_sent_to_tracks.py backend/alembic/versions/2025_11_12_14_34_51_07c4ad820a27_add_notification_sent_to_tracks.py
alembic/versions/2025_11_13_100209_30344721c491_add_album_slug_column_to_tracks.py backend/alembic/versions/2025_11_13_100209_30344721c491_add_album_slug_column_to_tracks.py
alembic/versions/2025_11_13_210000_add_albums_table.py backend/alembic/versions/2025_11_13_210000_add_albums_table.py
+1
backend/README.md
··· 1 + ../README.md
+34
backend/justfile
··· 1 + # backend/justfile 2 + set shell := ["bash", "-eu", "-o", "pipefail", "-c"] 3 + default := "run-backend" 4 + 5 + # run backend server (hot reloads) 6 + run-backend: 7 + uv run uvicorn backend.main:app --reload --host 0.0.0.0 --port ${PORT:-8001} 8 + 9 + # run tests with docker-compose 10 + test *ARGS='tests/': 11 + docker compose -f tests/docker-compose.yml up -d 12 + uv run pytest {{ ARGS }} 13 + docker compose -f tests/docker-compose.yml down 14 + 15 + # run type checking and linting 16 + lint: 17 + uv run ty check 18 + uv run ruff check . 19 + 20 + # create a new database migration 21 + migrate MESSAGE: 22 + uv run alembic revision --autogenerate -m "{{ MESSAGE }}" 23 + 24 + # upgrade database to latest migration 25 + migrate-up: 26 + uv run alembic upgrade head 27 + 28 + # show current migration status 29 + migrate-status: 30 + uv run alembic current 31 + 32 + # create a github release (triggers production deployment) 33 + release: 34 + ./scripts/release
fly.staging.toml backend/fly.staging.toml
fly.toml backend/fly.toml
+5 -30
justfile
··· 1 1 # plyr.fm dev workflows 2 2 mod frontend 3 3 mod transcoder 4 + mod backend # Backend commands are now managed in backend/justfile 4 5 5 6 6 7 # show available commands ··· 13 14 ln -s AGENTS.md CLAUDE.md 14 15 ln -s AGENTS.md GEMINI.md 15 16 16 - # run backend server (hot reloads) 17 - run-backend: 18 - uv run uvicorn backend.main:app --reload --host 0.0.0.0 --port ${PORT:-8001} 19 - 20 - # run tests with docker-compose 21 - test *ARGS='tests/': 22 - docker compose -f tests/docker-compose.yml up -d 23 - uv run pytest {{ ARGS }} 24 - docker compose -f tests/docker-compose.yml down 25 - 26 - # run type checking 27 - lint: 28 - uv run ty check 29 - 30 - # create a new database migration 31 - migrate MESSAGE: 32 - uv run alembic revision --autogenerate -m "{{ MESSAGE }}" 33 - 34 - # upgrade database to latest migration 35 - migrate-up: 36 - uv run alembic upgrade head 37 - 38 - # show current migration status 39 - migrate-status: 40 - uv run alembic current 17 + # Setup sub-modules if they have setup recipes 18 + # just frontend setup # Uncomment if frontend/justfile gets a setup 19 + # just backend setup # Uncomment if backend/justfile gets a setup 41 20 42 21 43 22 # show commits since last release 44 23 changelog: 45 24 @git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:'%C(yellow)%h%Creset %C(blue)%ad%Creset %C(green)%s%Creset %C(dim)- %an%Creset' --date=relative 46 25 47 - # create a github release (triggers production deployment) 48 - release: 49 - ./scripts/release 50 - 51 26 # deploy frontend only (promote remote main to production-fe branch) 52 27 release-frontend-only: 53 28 git fetch origin main 54 - git push origin origin/main:production-fe 29 + git push origin origin/main:production-fe
+4 -2
pyproject.toml backend/pyproject.toml
··· 24 24 "cachetools>=6.2.1", 25 25 "pytest-asyncio>=0.25.3", 26 26 "aioboto3>=15.5.0", 27 - "slowapi>=0.1.9", 27 + "slowapi @ git+https://github.com/zzstoatzz/slowapi.git@fix-deprecation", 28 28 ] 29 29 30 30 requires-python = ">=3.11" ··· 83 83 python_files = ["test_*.py", "*_test.py"] 84 84 python_classes = ["Test*"] 85 85 python_functions = ["test_*"] 86 - filterwarnings = ["ignore::pydantic.warnings.UnsupportedFieldAttributeWarning"] 86 + filterwarnings = [ 87 + "ignore::pydantic.warnings.UnsupportedFieldAttributeWarning", 88 + ] 87 89 88 90 [tool.ruff.lint] 89 91 fixable = ["ALL"]
scripts/backfill_atproto_records.py backend/scripts/backfill_atproto_records.py
scripts/backfill_image_urls.py backend/scripts/backfill_image_urls.py
scripts/copy_r2_buckets.py backend/scripts/copy_r2_buckets.py
scripts/delete_track.py backend/scripts/delete_track.py
scripts/generate_audio_sample.py backend/scripts/generate_audio_sample.py
scripts/migrate_atproto_namespace.py backend/scripts/migrate_atproto_namespace.py
scripts/migrate_images_to_new_buckets.py backend/scripts/migrate_images_to_new_buckets.py
scripts/migrate_r2_bucket.py backend/scripts/migrate_r2_bucket.py
scripts/release backend/scripts/release
scripts/run_migration.py backend/scripts/run_migration.py
scripts/simulate_broken_tracks.py backend/scripts/simulate_broken_tracks.py
scripts/smoke_test_staging.py backend/scripts/smoke_test_staging.py
scripts/test-transcoder.sh backend/scripts/test-transcoder.sh
src/backend/__init__.py backend/src/backend/__init__.py
src/backend/_internal/CLAUDE.md backend/src/backend/_internal/CLAUDE.md
src/backend/_internal/__init__.py backend/src/backend/_internal/__init__.py
src/backend/_internal/atproto/__init__.py backend/src/backend/_internal/atproto/__init__.py
src/backend/_internal/atproto/handles.py backend/src/backend/_internal/atproto/handles.py
src/backend/_internal/atproto/profile.py backend/src/backend/_internal/atproto/profile.py
src/backend/_internal/atproto/records.py backend/src/backend/_internal/atproto/records.py
src/backend/_internal/atproto/tid.py backend/src/backend/_internal/atproto/tid.py
src/backend/_internal/audio.py backend/src/backend/_internal/audio.py
src/backend/_internal/auth.py backend/src/backend/_internal/auth.py
src/backend/_internal/exports.py backend/src/backend/_internal/exports.py
src/backend/_internal/image.py backend/src/backend/_internal/image.py
src/backend/_internal/notifications.py backend/src/backend/_internal/notifications.py
src/backend/_internal/oauth_stores/__init__.py backend/src/backend/_internal/oauth_stores/__init__.py
src/backend/_internal/oauth_stores/postgres.py backend/src/backend/_internal/oauth_stores/postgres.py
src/backend/_internal/queue.py backend/src/backend/_internal/queue.py
src/backend/_internal/uploads.py backend/src/backend/_internal/uploads.py
src/backend/api/CLAUDE.md backend/src/backend/api/CLAUDE.md
src/backend/api/__init__.py backend/src/backend/api/__init__.py
src/backend/api/albums.py backend/src/backend/api/albums.py
src/backend/api/artists.py backend/src/backend/api/artists.py
src/backend/api/audio.py backend/src/backend/api/audio.py
src/backend/api/auth.py backend/src/backend/api/auth.py
src/backend/api/exports.py backend/src/backend/api/exports.py
src/backend/api/migration.py backend/src/backend/api/migration.py
src/backend/api/preferences.py backend/src/backend/api/preferences.py
src/backend/api/queue.py backend/src/backend/api/queue.py
src/backend/api/search.py backend/src/backend/api/search.py
src/backend/api/tracks/__init__.py backend/src/backend/api/tracks/__init__.py
src/backend/api/tracks/constants.py backend/src/backend/api/tracks/constants.py
src/backend/api/tracks/likes.py backend/src/backend/api/tracks/likes.py
src/backend/api/tracks/listing.py backend/src/backend/api/tracks/listing.py
src/backend/api/tracks/metadata_service.py backend/src/backend/api/tracks/metadata_service.py
src/backend/api/tracks/mutations.py backend/src/backend/api/tracks/mutations.py
src/backend/api/tracks/playback.py backend/src/backend/api/tracks/playback.py
src/backend/api/tracks/router.py backend/src/backend/api/tracks/router.py
src/backend/api/tracks/services.py backend/src/backend/api/tracks/services.py
src/backend/api/tracks/uploads.py backend/src/backend/api/tracks/uploads.py
src/backend/config.py backend/src/backend/config.py
src/backend/main.py backend/src/backend/main.py
src/backend/models/__init__.py backend/src/backend/models/__init__.py
src/backend/models/album.py backend/src/backend/models/album.py
src/backend/models/artist.py backend/src/backend/models/artist.py
src/backend/models/database.py backend/src/backend/models/database.py
src/backend/models/exchange_token.py backend/src/backend/models/exchange_token.py
src/backend/models/oauth_state.py backend/src/backend/models/oauth_state.py
src/backend/models/preferences.py backend/src/backend/models/preferences.py
src/backend/models/queue.py backend/src/backend/models/queue.py
src/backend/models/session.py backend/src/backend/models/session.py
src/backend/models/track.py backend/src/backend/models/track.py
src/backend/models/track_like.py backend/src/backend/models/track_like.py
src/backend/py.typed backend/src/backend/py.typed
src/backend/schemas.py backend/src/backend/schemas.py
src/backend/storage/__init__.py backend/src/backend/storage/__init__.py
src/backend/storage/r2.py backend/src/backend/storage/r2.py
src/backend/utilities/__init__.py backend/src/backend/utilities/__init__.py
src/backend/utilities/aggregations.py backend/src/backend/utilities/aggregations.py
src/backend/utilities/database.py backend/src/backend/utilities/database.py
src/backend/utilities/hashing.py backend/src/backend/utilities/hashing.py
src/backend/utilities/rate_limit.py backend/src/backend/utilities/rate_limit.py
src/backend/utilities/slugs.py backend/src/backend/utilities/slugs.py
tests/CLAUDE.md backend/tests/CLAUDE.md
tests/__init__.py backend/tests/__init__.py
tests/api/__init__.py backend/tests/api/__init__.py
tests/api/test_albums.py backend/tests/api/test_albums.py
tests/api/test_analytics.py backend/tests/api/test_analytics.py
tests/api/test_audio.py backend/tests/api/test_audio.py
tests/api/test_queue.py backend/tests/api/test_queue.py
tests/api/test_track_deletion.py backend/tests/api/test_track_deletion.py
tests/api/test_track_likes.py backend/tests/api/test_track_likes.py
tests/api/test_upload_size_limits.py backend/tests/api/test_upload_size_limits.py
tests/conftest.py backend/tests/conftest.py
tests/docker-compose.yml backend/tests/docker-compose.yml
tests/settings/__init__.py backend/tests/settings/__init__.py
tests/settings/test_notifications.py backend/tests/settings/test_notifications.py
tests/test_audio_formats.py backend/tests/test_audio_formats.py
tests/test_auth.py backend/tests/test_auth.py
tests/test_database.py backend/tests/test_database.py
tests/test_image_formats.py backend/tests/test_image_formats.py
tests/test_origin_validation.py backend/tests/test_origin_validation.py
tests/test_queue.py backend/tests/test_queue.py
tests/test_security_headers.py backend/tests/test_security_headers.py
tests/test_settings.py backend/tests/test_settings.py
tests/test_token_refresh.py backend/tests/test_token_refresh.py
tests/utilities/__init__.py backend/tests/utilities/__init__.py
tests/utilities/test_aggregations.py backend/tests/utilities/test_aggregations.py
tests/utilities/test_hashing.py backend/tests/utilities/test_hashing.py
tests/utilities/test_slugs.py backend/tests/utilities/test_slugs.py
+2 -6
uv.lock backend/uv.lock
··· 364 364 { name = "python-dotenv", specifier = ">=1.1.0" }, 365 365 { name = "python-jose", extras = ["cryptography"], specifier = ">=3.3.0" }, 366 366 { name = "python-multipart", specifier = ">=0.0.20" }, 367 - { name = "slowapi", specifier = ">=0.1.9" }, 367 + { name = "slowapi", git = "https://github.com/zzstoatzz/slowapi.git?rev=fix-deprecation" }, 368 368 { name = "sqlalchemy", specifier = ">=2.0.36" }, 369 369 { name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0" }, 370 370 ] ··· 2461 2461 [[package]] 2462 2462 name = "slowapi" 2463 2463 version = "0.1.9" 2464 - source = { registry = "https://pypi.org/simple" } 2464 + source = { git = "https://github.com/zzstoatzz/slowapi.git?rev=fix-deprecation#4a9e9a228ec3dbcad28e8fa8b6112da7052ee2d9" } 2465 2465 dependencies = [ 2466 2466 { name = "limits" }, 2467 - ] 2468 - sdist = { url = "https://files.pythonhosted.org/packages/a0/99/adfc7f94ca024736f061257d39118e1542bade7a52e86415a4c4ae92d8ff/slowapi-0.1.9.tar.gz", hash = "sha256:639192d0f1ca01b1c6d95bf6c71d794c3a9ee189855337b4821f7f457dddad77", size = 14028, upload-time = "2024-02-05T12:11:52.13Z" } 2469 - wheels = [ 2470 - { url = "https://files.pythonhosted.org/packages/2b/bb/f71c4b7d7e7eb3fc1e8c0458a8979b912f40b58002b9fbf37729b8cb464b/slowapi-0.1.9-py3-none-any.whl", hash = "sha256:cfad116cfb84ad9d763ee155c1e5c5cbf00b0d47399a769b227865f5df576e36", size = 14670, upload-time = "2024-02-05T12:11:50.898Z" }, 2471 2467 ] 2472 2468 2473 2469 [[package]]