# 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 php spark make:controller php spark make:migration # Database migrations php spark migrate php spark migrate:rollback # After OpenAPI edits node public/bundle-api-docs.js ``` Use `php spark test --filter ::` 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` (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/.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/')` 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. - Rely on Serena tools for guided edits, searches, and context summaries (use the available symbolic and search tools before running shell commands).