This file provides guidance to agent CLIs — Claude Code (through the
@AGENTS.md import in CLAUDE.md) and OpenCode — when working with code in
this repository. It holds only what the whole repo shares; each subproject and
each documented module has its own AGENTS.md.
Four-Points is a full-stack hotel Property Management System (PMS). Next.js 16 (App Router) frontend + Express 5 backend. TypeScript throughout. The system manages hotel operations including logbooks, parking, scheduling, maintenance, cashier, backoffice, and more.
frontend/ and backend/ are two independent pnpm projects, not a
workspace. There is no root package.json and no pnpm-workspace.yaml: each
side has its own package.json, pnpm-lock.yaml, pnpm-workspace.yaml and
.nvmrc. Always cd into one of them before running pnpm, and never assume a
dependency present on one side exists on the other — that is exactly how the two
Zod majors coexist. They do not even share the Node major: frontend/ runs 24,
backend/ 22. The root has no .nvmrc: a session started here activates each
side's Node before running anything there (Rule 9 under ## Exceptions in each
subproject's AGENTS.md).
Three levels. Read downwards before touching code — root, then subproject, then module — and follow the links across when a task crosses from one side to the other.
- Root
AGENTS.md(this file) — shared by both subprojects: Context7 policy, Markdown formatting, commits and hooks, the security level, the contract between frontend and backend, and the module map. backend/AGENTS.mdandfrontend/AGENTS.md— the harness rules (layer 1), then the subproject's stack, installed versions, commands, architecture, security areas and the list of its module docs.- Module
AGENTS.md— one per documented module, inside the subproject it belongs to. Each one links up to its subproject and across to its counterpart on the other side, when there is one.
Every AGENTS.md has a one-line CLAUDE.md next to it (@AGENTS.md), so
Claude Code loads it (ADR-042).
What gets loaded:
- Claude Code — this file at start; a subproject or module
AGENTS.mdas soon as it reads a file in that directory. - OpenCode — at start, every
AGENTS.mdfrom the directory where it was started up to the repo root (started inbackend/:backend/AGENTS.mdand this file); a module one as soon as it reads a file below it. Checked in its source at the installed version, 1.18.12. - Neither loads the other side on its own. A backend module doc does not
bring in
frontend/AGENTS.md, nor the reverse: when a task crosses sides, follow the links.
Public, human-facing docs per module live in docs/<module>/ (English). State
and decisions live in docs/SESSION.md and docs/DECISIONS.md, shared by both
subprojects.
This repository assumes the Context7 MCP is registered at the user level in
the client (not a project dependency and not in any package.json). The policy
is the same whichever client runs it.
Check that it is available before relying on it:
opencode mcp list # should show context7 → ConnectedIf it does not appear, add it to ~/.config/opencode/opencode.json (user
config, not project):
The API key is read from the CONTEXT7_API_KEY environment variable — never
literal in the file. If the MCP is not available on the current machine, say
so explicitly in the response and proceed with your own knowledge; do not
pretend to have consulted documentation.
Reactive and scoped activation: only when writing or modifying code that
touches a library listed in the installed versions of backend/AGENTS.md or
frontend/AGENTS.md. Never as a sweep ("review the whole project against
current docs") — that generates unrequested cascading refactors.
Flow: resolve-library-id → query-docs with the ID, the installed version
(the lockfile, not the ^ range) and the specific topic. If that exact version
is not indexed, use the closest minor below and state which one you consulted.
Zod is in two different majors — v3 in frontend/, v4 in backend/. Never
copy a schema between the two without consulting docs for both: in v4, custom
error messages, z.ZodError, and several z.string() helpers changed. Always
indicate the subproject when querying.
- Context7 is for verification, not migration. Do not upgrade versions or "modernize" existing working code without explicit request.
- A module's
AGENTS.mdmay pin a version for operational reasons (Render memory, host compatibility…). That note overrides the version lists and official docs — these are infrastructure constraints Context7 does not know about.pdf-parseinbackend/services/fnb/is the living example. - If you detect a deprecated API in existing code, mention it in the response but do not change it.
- If current docs recommend a different pattern than the repo uses, follow the repo convention and mention the discrepancy.
- Does not apply to business logic, internal refactors, or project-specific debugging — only to library APIs.
- Start backend:
cd backend && pnpm dev:local - Start frontend:
cd frontend && pnpm dev - Access app at
http://localhost:3000 - API available at
http://localhost:4000
A fresh local install (tables, first admin, departments, mock data) is
cd backend && pnpm setup:local. Each subproject's full command list is in its
own AGENTS.md.
What both halves must agree on. The details of each side live in its own
AGENTS.md.
- Auth is custom JWT, not NextAuth. The backend sets two HttpOnly cookies,
access_token(15 min) andrefresh_token(7 d),SameSite=Lax, both signed withSECRET_JWT_KEY; in production they are scoped to.four-points.stackbp.es. The frontend sendscredentials: 'include'on every call and, on a 401, retries throughPOST /api/auth/refresh-token. There is noexpress-session, nosessionstable and no dev-mode bypass. - The frontend calls the backend directly —
NEXT_PUBLIC_API_URLfrom the browser,NEXT_SERVER_API_URLfrom the server — with no Next.js API routes in between. A new frontend origin has to be added to the CORS allowlist inbackend/config/cors.ts(or passed asFRONTEND_URL): CORS is also the CSRF defence, and any other origin gets a 403. - Roles —
admin,recepcionista,mantenimiento,group-admin— are enforced in the backend. The frontend only hides what a role cannot use. - Demo: one admin account with
users.is_demo = 1, limited bybackend/middlewares/demoRestriction.ts. It exists only withDEMO_MODE=truein the backend;NEXT_PUBLIC_DEMO_MODE=trueshows its button on the login page (ADR-038). - Shapes are copied by hand, never shared: Zod schemas (v3 / v4, see above),
the TS types mirrored in
frontend/app/lib/<module>/types.ts, and the checklist JSON duplicated inbackend/content/andfrontend/content/. Changing one side means changing the other. - The UI is in Spanish, and so are some code comments.
Prettier with the root config (.prettierrc): aligned GitHub-flavored tables,
proseWrap: always (auto re-flowing prose to 80 columns) and
embeddedLanguageFormatting: off (no reformatting code inside ```
blocks — respect the original style).
The same Markdown rule applies everywhere. Prettier uses the nearest config,
so backend/.prettierrc and frontend/.prettierrc repeat it in a *.md
override (80 columns, proseWrap: always, embeddedLanguageFormatting: off);
their code settings are unchanged. frontend/.prettierignore leaves out the
checklist guides and references in frontend/content/checklist/, which the app
renders, until they are edited on purpose.
Lines over 80 columns are only acceptable inside code blocks and where the line is a single unbreakable piece (a long inline code span or link). Prose and tables are not an exception: see the next rule.
Tables only for short cells. Prettier aligns tables but cannot break a cell,
so a long sentence widens the whole table and the editor misaligns it. If any
row exceeds 80 columns (printWidth) or a cell has more than one sentence, use
a list (- **key**: description) instead of a table. Applies to any .md in
the repo (including docs/) when creating or editing a table; old ones are
fixed when touched.
CI checks every tracked .md with the frontend's Prettier. Both projects
pin the same version: a different one wraps some lines differently, so upgrade
them together. Run the same check from the root before committing, and format
only the files you changed:
git ls-files -z '*.md' | xargs -0 ./frontend/node_modules/.bin/prettier --check --ignore-path .prettierignore --ignore-path frontend/.prettierignore
./frontend/node_modules/.bin/prettier --write <changed files>Rules of mandatory compliance. They are also enforced by hooks in
.githooks/ (see below): if a message violates them, the commit is rejected —
this is not a recommendation.
- Language: English. Subject and body, and also PR bodies (
gh pr create). Proper names, technical identifiers and paths can remain in their original language. - Format: Conventional Commits —
feat:,fix:,docs:,chore:,refactor:,test:,perf:,build:,ci:,style:,revert:. Optional scope:feat(scheduling/solver): … - Subject in imperative mood, ≤ 72 characters, no trailing period.
- Body: explain the why, not the what (the diff already tells that).
Bullets when there are 3+ independent points. No vague messages (
fix bug,update code,wip). - No AI trace. The history must read as human work. Never
Co-Authored-By:from an assistant, never footers like🤖 Generated with ..., nor "AI-assisted", "generated by", nor bot emojis. Applies to subject, body, footers and PR comments. If your default template adds them, remove them before committing. - Footers: only real metadata (
Refs #123,Closes #45). - Never
pushwithout explicit permission. Creating commits locally is fine if the user asked to implement or fix something. Publishing them requires a "haz push" / "sube esto" from the owner in the same session. Local commits accumulate until then.--forceagainstmainrequires a direct order.
The hooks live in .githooks/ and are versioned, but Git does not activate
them automatically: core.hooksPath is local config, so you must run it once on
each machine where you clone the repo.
git config core.hooksPath .githookscommit-msg: Rejects the commit if the message contains AI attribution (Co-Authored-By: Claude,Generated with,🤖,AI-assisted…)pre-push: Blocks push in non-interactive sessions (agents, scripts) and rejects any non-fast-forward push tomain/master
Notes:
- They are local hooks: they consume nothing from GitHub (not Actions) and are not enforced server-side.
- Automated push already authorized by the user:
FP_PUSH_OK=1 git push. That variable does not bypass the force-push guard onmain. --no-verifyskips the hooks. Using it is a conscious owner decision, never an agent's.
L3 — estimated on 2026-09-28: there is login and user accounts, personal
data (users, guest blacklist with photos, cashier) and own backend; the L3 level
is declared by the owner as the target (docs/DECISIONS.md ADR-017), not the
current state. Re-estimate when: Aiven starts storing real guest or staff
data.
The same level line is repeated in backend/AGENTS.md and frontend/AGENTS.md;
a re-estimate changes all three.
Areas: each subproject lists its own in its ## Security section, with
paths relative to it — backend/AGENTS.md and
frontend/AGENTS.md. A review that spans both
reads both lists; one that spans neither (root config, .githooks/, .github/)
deduces its areas from the code.
Each entry points to the module's AGENTS.md. Some modules have one doc per
side; the smaller ones keep a single doc covering both, where most of their
logic lives.
- Scheduling (CP-SAT solver, grid UI) — backend TS:
backend/services/scheduling/· Python solver:backend/scheduling-solver/· UI:frontend/app/components/scheduling/ - Parking (bookings lifecycle, responsive reference) — backend:
backend/services/parking/· UI:frontend/app/dashboard/parking/ - Cashier (shifts, denominations, vouchers, PDF export) — backend:
backend/services/cashier/· UI:frontend/app/components/cashier/ - Group Tracking (bookings, payments, 4-status tracks) — backend:
backend/services/group/· UI:frontend/app/components/groups/ - Maintenance (7-state workflow, Cloudinary images) — both sides:
frontend/app/components/maintenance/ - Checklist (daily ops checklists, JSON sync rule) — both sides:
backend/services/checklist/ - Logbook (incident log, read/unread, audit trail) — both sides:
backend/services/logbook/. The frontend route is plural (app/dashboard/logbooks/), the backend folders singular - F&B / Restaurant (Opera PDF parser, daily revenue) — both sides:
backend/services/fnb/ - Backoffice (suppliers, invoices, assets, batch-pay) — both sides:
backend/services/backoffice/ - Blacklist (banned guests, Cloudinary photos, audit trail) — both sides:
backend/services/blacklist/ - DB / Migrations (policy, schema, idempotent scripts) —
backend/db-mysql/ - Messaging — no module doc yet: its paths are in
backend/AGENTS.mdandfrontend/AGENTS.md
When working inside a module and encountering a concept, endpoint, or behaviour that belongs to another module:
- Don't guess from the 1-line entry in the Module Map above — that entry only tells you where the doc is, not what's in it.
- Read the target module's
AGENTS.md(path shown in the map) before making any decision that touches that module. - Then return to the original task with full context.
The Module Map is a discovery map, not a summary. If something in the codebase
seems undocumented, check whether a sibling module's AGENTS.md covers it
before assuming it isn't documented.
When a new module grows complex enough to warrant its own doc:
- Create
AGENTS.mdin the most specific relevant directory (backend/services/<module>/orfrontend/app/components/<module>/). If noservices/dir exists, create it as a documentation anchor. Add a one-lineCLAUDE.mdnext to it with@AGENTS.md. - Write it in idiomatic technical English. Make it self-contained — a reader in that directory should not need the root to understand the module.
- Open it with the up link the other module docs carry (subproject
AGENTS.mdand this file), and link its counterpart on the other side, both ways. - Add it to the Module Map above and to the module list of its subproject's
AGENTS.md(both, when it covers both sides). - Commit as
docs(agents): split <module> module context into module file.
- Don't modify code without permission: always ask before making code changes.
- Database changes: coordinate SQL migrations between local and Aiven
databases — see
backend/AGENTS.md§ Database.
{ "$schema": "https://opencode.ai/config.json", "mcp": { "context7": { "type": "remote", "url": "https://mcp.context7.com/mcp", "headers": { "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" } } } }