clqms-be/AGENTS.md

153 lines
8.8 KiB
Markdown
Raw Normal View History

# AGENTS.md - Code Guidelines for CLQMS
> **CLQMS (Clinical Laboratory Quality Management System)** headless REST API backend built on CodeIgniter 4 with a focus on laboratory workflows, JWT authentication, and synchronized OpenAPI documentation.
---
## Repository Snapshot
- `app/` holds controllers, models, filters, and traits wired through PSR-4 `App\` namespace.
- `tests/` relies on CodeIgniter's testing helpers plus Faker for deterministic fixtures.
- Shared response helpers and ValueSet lookups live under `app/Libraries` and `app/Traits` and should be reused before introducing new helpers.
- Environment values, secrets, and database credentials live in `.env` but are never committed; treat the file as a reference for defaults.
---
## Build, Lint & Test
All commands run from the repository root.
```bash
# Run the entire PHPUnit suite
./vendor/bin/phpunit
# Target a single test file (fast verification)
./vendor/bin/phpunit tests/feature/Patients/PatientCreateTest.php
# Run one test case by method
./vendor/bin/phpunit --filter testCreatePatientSuccess tests/feature/Patients/PatientCreateTest.php
# Generate scaffolding (model, controller, migration)
php spark make:model <Name>
php spark make:controller <Name>
php spark make:migration <name>
# Database migrations
php spark migrate
php spark migrate:rollback
# After OpenAPI edits
node public/bundle-api-docs.js
```
Use `php spark test --filter <Class>::<method>` when filtering more than one test file is cumbersome.
---
## Agent Rules Scan
- No `.cursor/rules/*` or `.cursorrules` directory detected; continue without Cursor-specific constraints.
- No `.github/copilot-instructions.md` present; Copilot behaviors revert to general GitHub defaults.
---
## Coding Standards
### Language & Formatting
- PHP 8.1+ is the baseline; enable `declare(strict_types=1)` at the top of new files when practical.
- Follow PSR-12 for spacing, line length (~120), and brace placement; prefer 4 spaces and avoid tabs.
- Use short arrays `[]`, and wrap multiline arguments/arrays with one-per-line items.
- Favor expression statements that return early (guard clauses) and keep nested logic shallow.
- Keep methods under ~40 lines when possible; extract private helpers for repeated flows.
### Naming & Types
- Classes, controllers, libraries, and traits: PascalCase (e.g., `PatientImportController`).
- Methods, services, traits: camelCase (`fetchActivePatients`).
- Properties: camelCase for new code; legacy snake_case may persist but avoid new snake_case unless mirroring legacy columns.
- Constants: UPPER_SNAKE_CASE.
- DTOs/array shapes: Use descriptive names (`$patientInput`, `$validatedPayload`).
- Type hints required for method arguments/returns; use union/nullables (e.g., `?string`) instead of doc-only comments.
- Prefer PHPDoc only when type inference fails (complex union or array shapes) but still keep method summaries concise.
### Imports & Structure
- Namespace declarations at the very top followed by grouped `use` statements.
- Import order: Core framework (`CodeIgniter`), then `App\`, then third-party packages (Firebase, Faker, etc.). Keep each group alphabetical.
- No inline `use` statements inside methods.
- Keep `use` statements de-duplicated; rely on IDE or `phpcbf` to reorder.
### Controller Structure
- Controllers orchestrate request validation, delegates to services/models, and return `ResponseTrait` responses; avoid direct DB queries here.
- Inject models/services via constructor when they are reused. When instantiating on the fly, reference FQCN (`new \App\Models\...`).
- Map HTTP verbs to semantic methods (`index`, `show`, `create`, `update`, `delete`). Keep action methods under 30 lines by delegating heavy lifting to models or libraries.
- Always respond through `$this->respond()` or `$this->respondCreated()` so JSON structure stays consistent.
### Response & Error Handling
- All responses follow `{ status, message, data }`. `status` values: `success`, `failed`, or `error`.
- Use `$this->respondCreated()`, `$this->respondNoContent()`, or `$this->respond()` with explicit HTTP codes.
- Wrap JWT/external calls in try/catch. Log unexpected exceptions with `log_message('error', $e->getMessage())` before responding with a sanitized failure.
- For validation failures, return HTTP 400 with detailed message; unauthorized access returns 401. Maintain parity with existing tests.
### Database & Transactions
- Use Query Builder or Model methods; enable `use App\Models\BaseModel` which handles UTC conversions.
- Always call `helper('utc')` when manipulating timestamps.
- Wrap multi-table changes in `$this->db->transStart()` / `$this->db->transComplete()` and check `transStatus()` to abort if false.
- Run `checkDbError()` (existing helper) after saves when manual queries are necessary.
### Service Helpers & Libraries
- Encapsulate complex lookups (ValueSet, encryption) inside `app/Libraries` or Traits.
- Reuse `App\Libraries\Lookups` for consistent label/value translations.
- Keep shared logic (e.g., response formatting, JWT decoding) inside Traits and import them via `use`.
### Testing & Coverage
- Place feature tests under `tests/Feature`, unit tests under `tests/Unit`.
- Test class names should follow `ClassNameTest`; methods follow `test<Action><Scenario><Result>` (e.g., `testCreatePatientValidationFail`).
- Use `FeatureTestTrait` and `CIUnitTestCase` for API tests; prefer `withBodyFormat('json')->post()` flows.
- Assert status codes: 200 for GET/PATCH, 201 for POST, 400 for validation, 401 for auth, 404 for missing resources, 500 for server errors.
- Run targeted tests during development, full suite before merging.
### Documentation & API Sync
- Whenever a controller or route changes, update `public/paths/<resource>.yaml` and matching `public/components/schemas`. Add tags or schema refs in `public/api-docs.yaml`.
- After editing OpenAPI files, regenerate the bundled docs with `node public/bundle-api-docs.js`. Check `public/api-docs.bundled.yaml` into version control.
- Keep the controller-to-YAML mapping table updated to reflect new resources.
### Routing Conventions
- Keep route definitions grouped inside `$routes->group('api/<resource>')` blocks in `app/Config/Routes.php`.
- Prefer nested controllers (e.g., `Patient\PatientController`) for domain partitioning.
- Use RESTful verbs (GET: index/show, POST: create, PATCH: update, DELETE: delete) to keep behavior predictable.
- Document side effects (snapshots, audit logs) directly in the corresponding OpenAPI `paths` file.
### Environment & Secrets
- Use `.env` as the source of truth for database/jwt settings. Do not commit production credentials.
- Sample values are provided in `.env`; copy to `.env.local` or CI secrets with overrides.
- `JWT_SECRET` must be treated as sensitive and rotated via environment updates only.
### Workflows & Misc
- Use `php spark migrate`/`migrate:rollback` for schema changes.
- For seeding or test fixtures, prefer factories (Faker) seeded in `tests/Support` when available.
- Document major changes in `issues.md` or dedicated feature docs under `docs/` before merging.
### Security & Filters
- Apply the `auth` filter to every protected route, and keep `ApiKey` or other custom filters consolidated under `app/Filters`.
- Sanitize user inputs via `filter_var`, `esc()` helpers, or validated entities before they hit the database.
- Always use parameterized queries/Model `save()` methods to prevent SQL injection, especially with legacy PascalCase columns.
- Respond 401 for missing tokens, 403 when permissions fail, and log sanitized details for ops debugging.
### Legacy Field Naming & ValueSets
- Databases use PascalCase columns such as `PatientID`, `NameFirst`, `CreatedAt`. Keep migration checks aware of these names.
- ValueSet lookups centralize label translation: `Lookups::get('gender')`, `Lookups::getLabel('gender', '1')`, `Lookups::transformLabels($payload, ['Sex' => 'gender'])`.
- Prefer `App\Libraries\Lookups` or `app/Traits/ValueSetTrait` to avoid ad-hoc mappings.
### Nested Data Handling
- For entities that carry related collections (`PatIdt`, `PatCom`, `PatAtt`), extract nested arrays before filtering and validating.
- Use transactions whenever multi-table inserts/updates occur so orphan rows are avoided.
- Guard against empty/null arrays by normalizing to `[]` before iterating.
### Observability & Logging
- Use `log_message('info', ...)` for happy-path checkpoints and `'error'` for catch-all failures.
- Avoid leaking sensitive values (tokens, secrets) in logs; log IDs or hash digests instead.
- Keep `writable/logs` clean by rotating or pruning stale log files with automation outside the repo.
---
## Final Notes for Agents
- This repo has no UI layer; focus exclusively on REST interactions.
- Always pull `public/api-docs.bundled.yaml` in after running `node public/bundle-api-docs.js` so downstream services see the latest contract.
- When in doubt, align with existing controller traits and response helpers to avoid duplicating logic.