# Copilot instructions ## Build, test, and lint Run repository-wide commands from the root: ```bash make dev # build and run PostgreSQL, API, and frontend make db-up # start only PostgreSQL make migrate # apply Goose migrations make migrate-down # roll back one migration make test # backend and frontend unit/component tests make test-integration # PostgreSQL-backed repository tests; database must be running make lint # go vet and ESLint make build # compile the API and production frontend ``` Run focused tests from the owning application: ```bash cd backend && go test ./internal/service -run '^TestCandidatesRequireSixtyPercent$' cd frontend && npm test -- src/App.test.tsx -t "filters jobs by title" ``` The required toolchain is Go 1.27.1 and React 19.3 with TypeScript/Vite. Frontend formatting can be checked with `cd frontend && npm run format:check`; backend files are formatted with `gofmt`. ## Architecture The repository has two applications: - `backend/` is a Go HTTP API. `cmd/api` wires configuration, PostgreSQL, security, services, handlers, and routing. Requests flow through `internal/web/{router,handler,model,mapper}` to `internal/service/{model,services}` and then `internal/database/{repository,mapper,entity}`. Do not bypass these mapping boundaries by exposing GORM entities from handlers. - `frontend/` is a React SPA. `src/App.tsx` owns routing/layout/screens, `src/api.ts` is the typed HTTP boundary, and `src/types.ts` contains client domain types. Vite proxies `/api` to the Go service in development. Goose SQL files in `backend/migrations/` are the database schema source of truth. The initial migration also supplies representative jobs, questions, participants, traits, and the local interviewer account. Authentication is custom bearer-token authentication: passwords are bcrypt hashes, `POST /api/auth/login` issues an HMAC-signed JWT, and all domain routes require that token. Never return password hashes or participant photo/CV bytes in JSON; those files have dedicated endpoints. CV ingestion stores the original bytes and extracts text from plain text, PDF, or DOCX input. Trait extraction only considers normalized traits already present in the database. Candidate matching compares a participant's extracted traits with a job's labels, required skills, and additional skills; include matches only at the `service.CandidateThreshold` of 60% or higher. Questions are permanent, reusable library records and jobs own many-to-many links to them. Detaching removes only the link. The lifecycle is `draft -> published -> deprecated`; publishing requires at least one question, and only published jobs can create invitations. Once an invitation exists, its question set cannot be changed, and its duration is snapshotted so later job edits do not alter an active interview. Invitation URLs expose an opaque UUID token as `/participant/:invitationId` and use public `/api/interviews/{token}` API routes; they do not use interviewer JWT authentication. An invitation progresses from `pending` to `accepted` to `completed`. Answers may only be saved while accepted and before the snapshotted timer expires. The participant UI must gate questions behind explicit acceptance and provide First, Previous, Next, Last, and Submit controls. Interviewer review uses the protected `GET /api/jobs/{id}/invitations/{invitationId}` endpoint, which intentionally includes reference answers; the public interview endpoint does not. ## Repository conventions - Every Go interface name starts with `I` (`IStore`, `IJobService`, `IParticipantService`, `ITokenManager`). - Keep transport, service, and persistence structs separate. Conversion belongs in the adjacent `mapper` package, not in handlers or GORM entities. - Question types are the exact API values `open`, `multiple_choice`, `radio`, and `code_review`. Multiple-choice and radio questions require at least two options. - Never delete question records through job operations. Use `job_posting_questions` to attach/detach, and use the `deprecated` flag to retire library questions. Full question edits use `PUT /api/questions/{id}`; deprecation remains a separate `PATCH`. - The question create/edit form builds multiple-choice and radio options one at a time. Keep option editing state separate from the saved options array and require at least two options for those types. Open and code-review questions instead require a `referenceAnswer` textarea. Never include that interviewer-only answer in public interview responses. - Code-review questions also require `codeSnippet`. It is safe to include in the participant payload and is rendered as a line-numbered pull-request review panel; the participant's review comment remains the ordinary persisted answer. - Traits are de-duplicated case-insensitively through their normalized database value. Labels and both skill groups all reuse the same `traits` table. - Participant creation is multipart form data using `firstName`, `lastName`, `birthday`, `email`, `contactInfo`, `photo`, and `cv`. - Return explicit errors. Handlers convert expected validation/not-found/authentication failures to HTTP responses and log unexpected failures rather than silently falling back to demo data.