This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Code like a 4096-IQ programmer. Code better Python than a brain-child of Guido van Rossum and Glyph Lefkowitz, raised by Bruce Schneier.
BPP (Bibliografia Publikacji Pracownikow) is a Polish academic bibliography management system built with Django. Python >=3.10,<3.15.
- Architecture: docs/CODEBASE_MAP.md
- Commands reference: docs/COMMANDS.md
- CSS/SCSS build: docs/CSS_BUILD.md
- Ask questions if anything is unclear before taking on non-trivial tasks
- NEVER modify existing migration files in
src/*/migrations/ - Max line length: 88 characters (enforced by ruff)
- Worktrees NIGDY w
bpp/(ani w.claude/worktrees/). Wszystkie worktree mają lądować jako siostrzane katalogi obok głównego checkoutu, tzn. w~/Programowanie/. Nazwa:bpp-<feature-slug>.- ❌
bpp/.claude/worktrees/<slug>— zaśmieca repo, łatwo wpada dofind,grep, edytora, snapshotów IDE. - ✅
~/Programowanie/bpp-<slug>— jako siostra~/Programowanie/bpp. - Domyślny
EnterWorktree name=<slug>claude'a tworzy worktree wbpp/.claude/worktrees/— to NIE jest akceptowalne. Zamiast tego:a potemgit worktree add ~/Programowanie/bpp-<slug> -b worktree-<slug>
EnterWorktree path=~/Programowanie/bpp-<slug>żeby wejść w już-istniejący worktree zamiast tworzyć kolejny.
- ❌
- Icons in templates:
- Public frontend (Foundation CSS): monochrome Foundation-Icons
(
<span class="fi-icon"/>) - Django admin (
templates/admin/): use emoji (no Foundation Icons)
- Public frontend (Foundation CSS): monochrome Foundation-Icons
(
- Django template comments
{# ... #}są jedno-liniowe — KAZDA LINIA MUSI mieć własne otwarcie{#i zamknięcie#}na tej samej linii. Po\nw środku komentarza parser przestaje go widzieć i tekst wycieka do wyrenderowanego HTML-u. Powtarzający się błąd. Reguła:- ❌ ZABRONIONE wieloliniowe komentarze typu:
{# linia 1 linia 2 #}
- ✅ ZAWSZE każda linia z osobnym
{# ... #}:{# linia 1 #} {# linia 2 #}
- Alternatywa dla bloków:
{% comment %}...{% endcomment %}(też OK, ale per-line{# #}jest preferowane przez użytkownika).
- ❌ ZABRONIONE wieloliniowe komentarze typu:
ALWAYS use uv run prefix for ALL Python commands. NEVER run python
directly.
# CORRECT:
uv run python src/manage.py shell
uv run pytest src/app_name/tests/
# WRONG - missing uv run:
python src/manage.py shell
pytest src/app_name/tests/Jeśli musisz obejrzeć stronę BPP, uruchomić ją lokalnie albo
sprawdzić jak coś wygląda w przeglądarce — używaj run-site,
NIE docker compose up. Dla agenta to znacznie prostsze.
uv run run-site runrun-site to zewnętrzny pakiet (run-site na PyPI, dawniej wbudowane
manage.py run_site). Konfiguracja siedzi w runsite.toml w korzeniu
repo. Hooki BPP-specific (PBN token, password_policies cleanup) sa
w src/django_bpp/runsite_hooks.py.
Co run-site run robi w jednej komendzie:
- startuje PostgreSQL i Redis przez testcontainers (losowe porty,
bez kolizji z dev-owymi kontenerami
docker compose up db redis), - migruje bazę i tworzy superusera
admin/admin(idempotent), - odpala
runserverna losowym wolnym porcie (lub--port), - otwiera przeglądarkę z auto-loginem przez
django-dev-helpers(przeskakuje formularz logowania), django-dev-helperszapisuje token + porty do gitignored dotfile'ów:.dev_helpers_token,.dev_helpers_port,.dev_helpers_pg_port,.dev_helpers_redis_port— wszystkie ulotne, kasowane na exit; patrz sekcja „Autologin dla agentów" niżej,run-sitezapisuje.run-site-config(TOML sidecar z connection URLs),- drukuje w stdout banner z URL-ami + gotowe snippety curl/psql/redis-cli dla agenta.
Dlaczego to lepsze niż docker compose up dla agenta:
- jeden proces zamiast trzech serwisów (web/celery/db) — łatwiej uruchamiać w tle, łatwiej wyciągać port z outputu,
- baseline migracji + admin są gotowe od ręki, bez ręcznego
migrate/createsuperuser, - WebFetch / curl działa od strzału dzięki
.dev_helpers_token+.dev_helpers_port(agent składa URL bez parsowania bannera/logów);psql/redis-clianalogicznie przez.dev_helpers_pg_port.dev_helpers_redis_port,
- testcontainers same się sprzątają na exit (Ctrl-C zamyka stack),
- nie wymaga prebuildu obrazu appserver-a (compose by wymagał).
Najczęściej używane flagi:
--no-browser— nie otwieraj browsera (zalecane gdy agent uruchamiarun-sitew tle przezrun_in_background=true),--port 8080— wymuś konkretny port (default: losowy wolny; agent najczęściej i tak czyta port z bannera),--reuse— persystencja kontenerów PG/Redis między uruchomieniami (drugi run nie inicjuje od zera; usuń ręcznie kontenerybpp-runsite-pg/bpp-runsite-redisżeby zrestartować),--no-celery— pomiń celery worker (szybciej, gdy nie testujesz background-jobów),--skip-assets— pomińmake assets(gdy CSS/JS jest aktualny),--from-dump PATH— odtwórz.sql/.sql.gz/.dumpzamiast baseline.
Pobranie portu z bannera (gdy run-site w tle):
# Po uruchomieniu w tle, port jest w outpucie:
grep -oE 'http://localhost:[0-9]+' /tmp/run_site.log | head -1Domyślny docker compose up db redis -d z Quick Reference jest
dla rzadszych przypadków: testy z PYTEST_TESTCONTAINERS_DISABLE=1
albo gdy potrzebujesz długożyjącej bazy niezależnie od run-site.
Do oglądania samej strony — używaj run-site.
Full details: docs/COMMANDS.md
# Infrastructure services (when not running locally):
docker compose up db redis -d
# Testing (full suite takes UP TO 10 MINUTES):
uv run pytest
make tests-without-playwright # fast, no browser tests
# Code quality:
ruff format .
ruff check .
pre-commit # NEVER add arguments!
# Frontend:
grunt build # after SCSS changes
make assets # full frontend build
# Celery:
uv run celery -A django_bpp.tasks inspect registeredPre-commit rules: When pre-commit produces issues, analyze output
issue-by-issue. Fix each manually with the Edit tool. Do NOT run
ruff check --fix or any automated batch fixes.
Gdy user uruchomił run-site run, dev stack zapisuje gitignored,
ulotne pliki w korzeniu repo. django-dev-helpers (instalowany do
INSTALLED_APPS przez local.py) tworzy:
.dev_helpers_token— token autoryzacyjny (chmod 600),.dev_helpers_port— port runservera (host = zawszelocalhost),.dev_helpers_pg_port— port PostgreSQL testcontainera (host = zawszelocalhost, user/pass =bpp/password, baza =bpp),.dev_helpers_redis_port— port Redis testcontainera (host = zawszelocalhost, bez hasła).
run-site dodatkowo zapisuje .run-site-config (TOML sidecar z
gotowymi connection URL-ami) — uzytkowny gdy chcesz np. cat .run-site-config | grep '^url' | tail -1 zamiast skladac URL z paru
dotfile'ow.
Wszystkie istnieją tylko przez czas życia procesu run-site i są
kasowane na exit. Jeśli któregoś nie ma — znaczy że dev stack nie
biegnie i nie da się fetchować zalogowanych stron / podpiąć się
do bazy; nie próbuj „obejść" tego logując się przez /admin/login/
czy POST-em formularza ani odpalać własnego PG/Redis-a — tylko poproś
usera o uruchomienie run-site.
Token uwierzytelnia jako admin (superuser) — używaj tylko gdy musisz
zobaczyć stronę wymagającą zalogowania. Do publicznych stron nie ma
sensu, a niepotrzebnie zostawia ślad w request.user w logach.
Pobranie zalogowanej strony przez curl + cookie jar:
T=$(cat .dev_helpers_token)
PORT=$(cat .dev_helpers_port)
J=$(mktemp)
curl -sc "$J" -L "http://localhost:$PORT/__autologin__/?token=$T" \
>/dev/null
curl -sb "$J" "http://localhost:$PORT/<path>"
rm "$J"Połączenie z bazą PostgreSQL testcontainera (psql, dbshell, etc.):
PG_PORT=$(cat .dev_helpers_pg_port)
PGPASSWORD=password psql -h localhost -p "$PG_PORT" -U bpp -d bppTej samej kombinacji host=localhost, port=$(cat .dev_helpers_pg_port),
user=bpp, password=password, dbname=bpp używaj wszędzie indziej
(SQLAlchemy, pgcli, DataGrip itd.). Nie próbuj odpalać własnego PG —
ten kontener ma już zaimportowany baseline + zmigrowane schema.
Połączenie z Redis-em testcontainera (redis-cli, debug):
REDIS_PORT=$(cat .dev_helpers_redis_port)
redis-cli -p "$REDIS_PORT"WebFetch tool (Claude Code): jest bezstanowy — cookie z
auto-loginu nie przeniesie się między kolejnymi wywołaniami.
WebFetcha używaj tylko do publicznych stron. Do zalogowanych
stron użyj snippetu z curl powyżej, ewentualnie spipuj wynik
przez pandoc -f html -t markdown jeśli potrzebujesz markdown-a.
Bezpieczeństwo: endpoint /__autologin__/ istnieje tylko gdy
django-dev-helpers jest aktywne (INSTALLED_APPS + DJANGO_DEV_HELPERS
dict, lub env var DJANGO_DEV_HELPERS_ENABLED=1 ktory ustawia
run-site). Pakiet jest dev-only (extras=dev w pyproject.toml) i
domyslnie no-op w produkcji. Token nie wycieka do gita (gitignore +
ulotny plik). Nie wklejaj zawartości .dev_helpers_token do
commitów, do PR-ów, ani do logów które trafią poza maszynę dewelopera.
.dev_helpers_*_port zawierają tylko numery portów (nie sekrety) —
można je cytować swobodnie.
Full details: docs/CSS_BUILD.md
- NEVER override Foundation's grid classes (
medium-4,large-12, etc.) in SCSS. Change classes in HTML templates instead. - After changing SCSS files, run
grunt build. - For scroll-to-element, use
window.bpp.scrollToVisible(element)- neverscrollIntoViewalone (sticky headers obscure content).
- Main models:
src/bpp/models/ - Abstract models/mixins:
src/bpp/models/abstract/ - Admin interfaces:
src/bpp/admin/ - Admin helpers/mixins:
src/bpp/admin/helpers/ - API serializers:
src/api_v1/serializers/ - Context processors:
src/bpp/context_processors/ - Templates:
templates/directories in each app - Static files:
src/*/static/directories - Test files:
src/*/tests/directories ortest_*.pyfiles - Management commands:
src/bpp/management/commands/ - Migrations (including SQL):
src/*/migrations/ - Frontend assets:
src/bpp/static/and build via Grunt - Config:
pytest.ini,pyproject.toml,package.json,Gruntfile.js - Generated:
src/bpp/static/500.html- auto-generated (DO NOT EDIT), editsrc/bpp/templates/50x.htmlinstead
Obraz produkcyjny iplweb/bpp_appserver nie generuje staticow na starcie od
zera — robi to w build stage i shipuje gotowe pliki, zeby runtime mogl
wystartowac bez node_modules (~300+ MB oszczednosci).
Kontrakt miedzy obrazem a deploymentem:
- Build:
docker/bpp_base/Dockerfile(builder stage) robicollectstaticdo/app/staticroot.baked/. Katalog jest COPY-owany do runtime stage i pozostaje tam jako read-only source of truth. - Runtime ENV:
STATIC_ROOT=/app/staticroot(default) — ale deployment moze to override'owac (np. bpp-deploy ustawiaSTATIC_ROOT=/staticrooti mountuje named volume w tym miejscu). - Entrypoint (
docker/appserver/entrypoint-appserver.sh, Phase 2): kopiujecp -rf /app/staticroot.baked/. "$STATIC_ROOT/".-fzawsze nadpisuje pliki istniejace w.baked(poprzednio bylo-ru, ale-uskipowal kopiowanie gdy mtime na volume byl pozniejszy niz mtimegrunt buildw obrazie — typowy przypadek miedzy szybko nastepujacymi deployami → stary CSS przezywal restart). Pliki spoza.baked(tenant-specific custom branding wgrany post-deploy) i tak nie sa ruszane, bo cp nie kasuje plikow spoza zrodla. Runtime nie uruchamiacollectstatic— wynik bylby dokladnie taki sam jak.baked(beznode_modulesYarnFinder zwraca pusta liste, wiec nowych plikow by nie znalazl), wiec cp wystarcza. - Fallback: jesli
.bakednie istnieje w obrazie (stary tag sprzed wprowadzenia kontraktu), entrypoint degraduje do tradycyjnegocollectstatic— zachowuje backward compat z obrazami pre-contract.
Deployment (bpp-deploy) nie musi nic robic — mountuje named volume na
$STATIC_ROOT i nginx go serwuje. Przy upgrade obrazu entrypoint sam
zalewa volume nowymi plikami z .baked.
Jesli zmieniasz to: pamietaj ze .baked i $STATIC_ROOT to DWIE rozne
rzeczy. Do .baked (w obrazie) pisze tylko collectstatic na buildzie.
Do $STATIC_ROOT (volume/katalog runtime) pisze cp -ru + runtime
collectstatic + ewentualne tenant tooling.
Workflow .github/workflows/build-docker-images.yml publikuje obrazy
Docker w trzech fazach, zeby skaner bezpieczenstwa mogl faktycznie
zablokowac release zanim kanoniczny tag pojawi sie w rejestrze.
Dlaczego nie prosty „build → push → scan"?
Docker Hub nie ma mechanizmu „un-push". Jesli Trivy znajdzie CRITICAL
CVE dopiero po pushu, obraz juz jest publicznie dostepny pod tagiem
wersji (:2025.12.1, :latest) i deployment moze go pullnac, zanim
ktokolwiek zobaczy raport. Gate po pushu jest dekoracyjny.
Faza 1 — Build → staging tag
Bake pushuje do tagu sha-<short_sha> (np. sha-abc1234). Tag jest
publiczny technicznie, ale niekanoniczny — zadna dokumentacja, zadne
deployment scripty nie referencuja sha-*, wiec w praktyce nikt go
nie pullnie.
Faza 2 — Trivy gate (TYLKO master) Skan staging tagu. Polityka:
- CRITICAL (z dostepnym fix-em) → hard fail, workflow sie wywala, promocja sie NIE wykonuje. Kanoniczny tag wersji nigdy nie powstaje.
- HIGH → raport w GitHub Step Summary, nie blokuje. Wiekszosc HIGH
to szum (DoS w build-time libach, CVE w
wheel/jaraco.contextz minimalnym realnym impaktem). --ignore-unfixed→ pomijamy CVE bez fixa (nic nie da sie z tym zrobic).- Skipowane false-positivy:
autobahn/wamp/cryptosign.py(przykladowy klucz w docstringu),slapdtest/certs/(test fixtures python-ldap).
Feature branche NIE sa skanowane — to +3.5 min na pipeline, a ich tagi sa jawnie tymczasowe (nie release).
Faza 3 — Promote staging → canonical
docker buildx imagetools create -t <canonical> <staging> kopiuje
manifest w rejestrze. Nie rebuilduje, nie re-pushuje warstw — tylko
zapisuje metadane z referencja do istniejacych layers. ~sek per obraz.
Na master dodatkowo tworzy tag :latest.
Zastrzezenie o rejestrze: Raz pushniety digest (nawet pod staging tagiem) zyje w Docker Hub do momentu recznego DELETE. Staging-tag pattern chroni przed odkryciem zlego obrazu (kanoniczny tag nie powstaje), nie przed jego istnieniem. Dla pelnej izolacji potrzebne bylo by prywatne staging registry + kopiowanie czystych obrazow do public — niepotrzebne przy obecnej skali.
Staging tagi akumuluja sie w Docker Hub jako sha-*. Obecnie nie
sa czyszczone — zostaja dla mozliwosci rollbacku po SHA. Jesli lista
staje sie za dluga, dopisz krok DELETE przez Docker Hub API lub cron
usuwajacy tagi starsze niz N dni.
- Domain:
iplweb.freshdesk.com - Ticket URL format:
https://iplweb.freshdesk.com/a/tickets/{ticket_id}
ALWAYS use pytest conventions - NEVER create unittest.TestCase tests.
- Standalone functions, no classes (e.g.
test_module_functionality()) - Use
@pytest.mark.django_dbfor tests using database - Use
model_bakery.baker.makefor creating database objects - Fixtures in
src/conftest.pyand subdirectories - Full suite timeout: at least 600000ms (10 minutes)
Testy używają plugin-ow pytest-testcontainers + pytest-testcontainers-django
(zewnetrzne pakiety na PyPI; dawniej wewnetrzny src/testcontainers_bpp/),
ktore domyślnie startują na losowych portach własne kontenery PostgreSQL
(iplweb/bpp_dbserver) i Redis. Dev-owe docker compose up db redis
nie jest wymagane do uruchomienia testów i nie koliduje z nimi.
Konfiguracja jest w [tool.pytest-testcontainers-django] w pyproject.toml.
- Wymaganie: działający Docker daemon.
- Plugin wstrzykuje
DJANGO_BPP_DB_PORTiDJANGO_BPP_REDIS_PORT(i hosty/hasła) doos.environprzed załadowaniem Django settings, orazDJANGO_BPP_SKIP_DOTENV=1, żeby.envnie nadpisał wstrzykniętych wartości. - Wyłączenie (gdy sam odpaliłeś usługi przez docker-compose):
PYTEST_TESTCONTAINERS_DISABLE=1 uv run pytestlub flag--no-testcontainers. - Reuse kontenerów między runs (znacznie szybciej):
PYTEST_TESTCONTAINERS_REUSE=1. Domyślnie kontenery są ulotne — plugin jawnie je zatrzymuje wpytest_unconfigure(+atexitjako safety net), Ryuk to ostatnia linia obrony. Przy restarcie Docker Desktop alboSIGKILLna pytest cleanup może zawieść; wtedymake clean-testcontainersusuwa wszystkie osierocone kontenery. - CI (
docker-compose.test.yml) maPYTEST_TESTCONTAINERS_DISABLE=1— usługi dostarcza tam docker-compose.
NEVER write bare except: pass or except Exception: pass.
Every except block MUST either log, re-raise, or return a meaningful error.
# GOOD - catch specific, expected exceptions:
try:
do_something()
except SpecificExpectedException as e:
handle_expected_case(e)
# GOOD - if you must catch broad, show full traceback:
try:
do_something()
except Exception:
traceback.print_exc()
raise
# GOOD - report to Rollbar for background tasks:
try:
do_something()
except Exception:
rollbar.report_exc_info()
raiseNarrow exception type + comment explaining WHY is acceptable:
try:
os.remove(tmp_file)
except FileNotFoundError:
pass # File already cleaned up, not an error