go-guess/.github/copilot-instructions.md
Daniel Noulet 6153017b62 review
2026-09-19 20:56:27 +02:00

101 lines
5.2 KiB
Markdown

# 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.