This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Artemis is an interactive learning platform for programming exercises, quizzes, modeling tasks, and exams with automatic and manual assessment. It integrates with AI services (Iris for virtual tutoring, Athena for automated assessment, Hyperion for exercise creation).
- Server: Spring Boot 4.1 (Java 25), MySQL, Hibernate, Hazelcast
- Client: Angular 21, TypeScript, SCSS
- Build: Gradle 9.6, pnpm 11 / Node 24 (pnpm version pinned via the
packageManagerfield in package.json; activate withcorepack enable) - Testing: JUnit 6, Vitest, Playwright
./gradlew bootRun # Start dev server (includes Angular build)
./gradlew bootRun -x webapp # Server only (use with pnpm start)
./gradlew -Pprod -Pwar clean bootWar # Production WAR (no SBOM, fast)
./gradlew -Pprod -Pwar -Psbom clean bootWar # Production WAR including server + client SBOMSBOM generation (cyclonedxBom + generateClientSbom) is gated behind the -Psbom Gradle property. CI release-eligible jobs (pushes to develop/main/release/*, version tags, and published releases) set it automatically in .github/workflows/ci-build.yml. Local builds and PR CI ship a WAR without the SBOM — AdminSbomResource returns 404 and the admin UI renders an informational banner in that case.
corepack enable # One-time: activate the pnpm version pinned in package.json
pnpm install --frozen-lockfile # Install dependencies (CI-style, asserts lockfile is authoritative)
pnpm install # Install + allow lockfile updates (for dependency changes)
pnpm start # Angular dev server with HMR (runs prebuild + ng serve)
pnpm run webapp:build # Development build
pnpm run webapp:prod # Production build
pnpm run build # Alternative production build- Client assets:
build/resources/main/static - Production WAR:
build/libs/Artemis-<version>.war
./gradlew spotlessCheck # Check Java formatting
./gradlew spotlessApply # Fix Java formatting
./gradlew checkstyleMain # Java linting
./gradlew modernizer # Check for legacy API usage
pnpm run lint # ESLint
pnpm run lint:fix # Fix ESLint issues
pnpm run stylelint # SCSS linting
pnpm run prettier:check # Check formatting
pnpm run prettier:write # Fix formatting# Server (requires Docker — tests run against PostgreSQL via Testcontainers by default)
./gradlew test -x webapp # All server tests (PostgreSQL)
./gradlew test --tests ExamIntegrationTest -x webapp # Single test class
./gradlew test --tests ExamIntegrationTest.testGetExamScore # Single test method
# Client (Vitest - preferred for new tests)
pnpm run vitest # Watch mode
pnpm run vitest:run # Single run
pnpm run vitest:coverage # With coverage
pnpm run vitest -- path/to/spec.ts # Single Vitest file
# E2E Tests (Playwright) — preferred way to run locally
# The script auto-kills processes on ports 8080/9000/7921, starts Postgres, server, and client.
./run-e2e-tests-local-fast.sh # Run all E2E tests
./run-e2e-tests-local-fast.sh --filter "Quiz" # Run tests matching "Quiz"
./run-e2e-tests-local-fast.sh --filter "ExamAssessment|SystemHealth" # Multiple patterns
./run-e2e-tests-local-fast.sh --stop # Stop all services
# Multi-node E2E (catches Hazelcast cluster / L2 cache coherence regressions)
# Boots the full production-faithful stack: Postgres, JHipster Registry (Eureka),
# ActiveMQ, 3 Artemis nodes, nginx LB, containerised Playwright. Slower than the
# single-node fast script, but the only way to reproduce multi-node bugs locally.
./run-e2e-tests-local-multinode.sh # Full multi-node run (build WAR + image + stack + tests)
./run-e2e-tests-local-multinode.sh --filter "Quiz" # Multi-node, filtered
./run-e2e-tests-local-multinode.sh --skip-build --skip-up # Quick re-run against an already-running stack
./run-e2e-tests-local-multinode.sh --stop # Tear everything down
# Multi-node E2E (fast variant) — same topology, host-launched JVMs instead of Docker images
# Skips the Docker image build that dominates the slow path (~5–8 min). Reuses the WAR built by
# Gradle and runs 3 java -jar processes on the host; Postgres/Eureka/ActiveMQ/nginx still run as
# containers. Use this for server-side iteration on multi-node bugs. Cold ~1–2 min, warm ~30 s.
./run-e2e-tests-local-multinode-fast.sh # Full run (build WAR + infra + 3 host JVMs + tests)
./run-e2e-tests-local-multinode-fast.sh --filter "Quiz" # Filter to a subset of tests
./run-e2e-tests-local-multinode-fast.sh --skip-build --skip-up # Re-run tests against the running stack
./run-e2e-tests-local-multinode-fast.sh --stop # Tear everything downWhich E2E runner should I use?
run-e2e-tests-local-fast.sh— single node, Angular dev server. Best for client (UI) iteration.run-e2e-tests-local-multinode-fast.sh— multi-node, WAR run from host. Best for server iteration that needs the cluster (Hazelcast, ActiveMQ STOMP, LB).run-e2e-tests-local-multinode.sh— full Docker image build, prod-faithful. Use this to reproduce a CI-only failure or before pushing a multi-node-sensitive change.
Organized by feature module:
core/- Configuration, security base, utilities, base entitiesaccount/- User, authority, passkey, account REST, authentication, LDAPexercise/- Base exercise functionalityprogramming/- Programming exercises (lifecycle, grading, repositories)jenkins/- Jenkins CI backend connectorlocalvc/- Embedded git server (HTTP + SSH), repo URI handling, VCS access tokenslocalci/- Local CI orchestration: build job queue, dispatch, result processingquiz/- Quiz exercisesmodeling/- UML diagram exercisestext/- Text exercisesfileupload/- File upload exercisesexam/- Exam modeassessment/- Grading and assessmentcommunication/- Channels, messaging, conversations, FAQs, saved postsnotification/- Course / global / system / push notifications, mail servicelecture/- Lecture managementcalendar/- Calendar events and iCal subscriptionsatlas/- Competency-based learning, learning analyticsiris/- LLM-based virtual tutorathena/- ML-based assessmenthyperion/- LLM-based exercise creation assistantplagiarism/- Plagiarism detection (JPlag)lti/- LTI integrationtutorialgroup/- Tutorial group managementglobalsearch/- Cross-entity search via Weaviatevideosource/- External video source integration (TUM Live)course/- Course management, registration, archive, dashboard, statisticsadmin/- Admin operations: data export, vulnerability scan, cleanup, telemetry, organization management, legal documents
core/- Core services (HTTP, auth, guards)shared/- Shared components, pipes, utilitiesopenapi/- Generated TypeScript client code- Feature modules mirror server structure
- Assets and translations in
content/ - Client tests are co-located with their TypeScript components
src/test/java/- JUnit server testssrc/test/playwright/- E2E tests
src/main/resources/- Spring profiles (config/application-*.yml), Liquibase changelogs, static filesdocumentation/- Project documentationdocker/- Deployment helpers
- Generated at runtime by springdoc:
/v3/api-docsand/swagger-ui
- PascalCase for classes, camelCase for fields/methods
- No wildcard imports (Spotless enforces)
- Package-by-feature organization
- 4-space indentation
- Avoid
@Transactionalscope - Do not inject
EntityManagerorEntityManagerFactorydirectly into services or controllers; all persistence operations must go through Spring Data repositories - Use DTOs (Java records) for REST endpoints
- Prefer constructor injection for Spring beans
- Use Java 25 features (records, sealed classes, pattern matching)
- Do not add
@Cache(Hibernate L2) annotations on entities or associations. Hibernate second-level cache is disabled cluster-wide and an ArchUnit rule (ArchitectureTest.testNoHibernateSecondLevelCacheAnnotation) fails the build if any reappears. Reason:@Modifying @Queryrepository methods bypass L2 invalidation, and the absence of service-level@Transactionalleaves no clean place to coordinate eviction within a REST call — both produced cross-node stale-read bugs in the multi-node cluster (issue #12574, fixed in PR #12578; further cleanup in PR #12579). - For DTO / projection caching, use Spring
@Cacheablewith theHazelcastCacheManager(defined inHazelcastConfiguration.cacheManager). Always pair@Cacheablewith explicit eviction —@CacheEvicton the writer service, or a HibernatePostUpdateEventListener/PostDeleteEventListener. SeeTitleCacheEvictionServicefor the canonical pattern. - The bar for adding a new cache: a measured performance gain that justifies the eviction-correctness work. The default answer is: do not cache.
- Full rationale, history, and patterns:
documentation/docs/developer/guidelines/caching.mdx.
- kebab-case for filenames (
course-detail.component.ts) - PascalCase for classes, camelCase for members
- Single quotes, 4-space indentation
- Standalone components preferred
- Angular 21 signal-based APIs are mandatory for new code:
- Use
input()/input.required()instead of@Input() - Use
output()instead of@Output() - Use
viewChild()/viewChild.required()instead of@ViewChild() - Use
viewChildren()instead of@ViewChildren() - Use
signal(),computed(), andeffect()for reactive state management - Use
inject()for dependency injection instead of constructor injection - Legacy decorators (
@Input,@Output,@ViewChild,@ViewChildren,@ContentChild,@ContentChildren) must not be used in new code - In modules not yet fully migrated, prefer signal-based APIs for new components but maintain consistency within existing components
- An ESLint rule (
enforce-signal-apis-in-migrated-modules) enforces this in fully migrated modules - Prefer
computed()/effect()overngOnChangesfor signal-based components. Note: in Angular 21ngOnChangesdoes fire for signal inputs (it is NOT dead code — that was only true in v17–18), so do not treat it as a bug or auto-convert it. Butcomputed(derived state) /effect(side effects, used sparingly) are the idiomatic, consistent choice.ngOnChangesis still valid when you needSimpleChanges.previousValue/isFirstChange()or logic that must run before child init. A warn-level rule (prefer-signal-reactivity-over-ngonchanges) warns onngOnChangesin clean-baseline Angular client files; known migration-backlog files are temporarily excluded ineslint.config.mjs. The goal is to remove it entirely (migrate tocomputed/effect); rare genuinely-unavoidable cases need a detailed comment and a justified line-level disable.ngOnInit/ngOnDestroyare unaffected by signals. Seedocumentation/docs/developer/guidelines/client-development.mdx.
- Use
- Angular template control flow: use
@if,@for,@switch; never use*ngIf,*ngFor,*ngSwitch - Avoid
null, useundefinedwhere possible - Avoid spread operator for objects
- Prefer 100% type safety
- UI components: Use PrimeNG instead of Bootstrap components
- All new UI elements must be implemented using PrimeNG components
- We are migrating from Bootstrap to Tailwind CSS v4 (utilities) + PrimeNG (components); do not introduce new Bootstrap components, classes, or raw colours
- Existing Bootstrap usage will be migrated incrementally
- Colours use semantic tokens, never primitives or Bootstrap classes:
text-state-danger/text-state-success/text-state-warning/text-state-info(named state tokens; or PrimeNGseverity, ortext-muted-color/bg-surface-*) — never--p-<color>-Nprimitives,text-red-500,text-danger, or the superseded arbitrarytext-(--danger)form. Full standard, decision rules, and the Bootstrap→Tailwind/PrimeNG quick reference:documentation/docs/developer/guidelines/client-development.mdx(### Styling) - Never hand-write PrimeNG component root classes (
class="p-button",class="p-inputtext"): PrimeNG injects component CSS lazily, so a bare element renders non-deterministically. Render the real component (<button pButton>,<input pInputText>,<p-tag>) — enforced bylocalRules/no-primeng-component-classes @ng-bootstrap/ng-bootstrapis deprecated — do not useNgbModal,NgbActiveModal,NgbModalRef,NgbTooltip,NgbDropdown, etc. in new code. Use PrimeNG'sDialogService(primeng/dynamicdialog) for modals,p-tooltipfor tooltips, etc. ng-bootstrap is incompatible with Angular signal inputs (assigning tomodalRef.componentInstance.Xsilently fails whenXisinput()/input.required()). Existing usages are being migrated.
- LF line endings
- Final newlines required
- UTF-8 encoding
- YAML: 2-space indentation
- Server tests require Docker — tests run against PostgreSQL via Testcontainers by default (both locally and in CI).
- Keep tests deterministic; mock external services and WebSockets
- CI enforces coverage thresholds per module
- Use
pnpm run test-difffor incremental client work - Client tests: Vitest
- Use
vi.spyOn(),vi.fn(),vi.clearAllMocks()instead of Jest equivalents - Run Vitest:
pnpm run vitest(watch),pnpm run vitest:run(single run),pnpm run vitest:coverage
- Use
- Name server tests
*Test.java; reuse module base classes when present - When comparing
ZonedDateTimevalues in tests, usetoInstant()for comparisons since PostgreSQL stores timestamps as UTC (timezone offset is not preserved through database round-trips) - E2E tests: Use
./run-e2e-tests-local-fast.sh— this is the intended way to run Playwright E2E tests locally (for both developers and AI agents)- The script automatically kills processes on ports 8080, 9000, and 7921 before starting
- Use
--filter "TestName"to run specific tests; supports regex patterns (e.g.,--filter "Quiz|Exam") - After the first run, reuse running services with
--skip-server --skip-client --skip-db
- Add screenshots for UI changes in PRs
- Verify linting before submitting:
pnpm run lint,./gradlew checkstyleMain -x webapp
- Concise, imperative commit messages scoped where useful (e.g.,
Exam mode: adjust live updates,build: bump version); wrap bodies near 72 chars - PRs: include problem/solution summary, linked issue, commands/tests run, screenshots for UI, and doc updates if relevant
- Target
developbranch; rebase to reduce noise - Run lint and tests before submitting
- Follow
CONTRIBUTING.mdand the guidelines indocumentation/docs/developer/guidelines/ - Use the PR description template in
.github/PULL_REQUEST_TEMPLATE.md