clqms-be/AGENTS.md
root 30c4e47304 chore(repo): normalize EOL and harden contact patch flow
- handle contact PATCH failures by checking model save result and returning HTTP 400 with the model error message
- update ContactDetailModel nested updates to enforce active-detail checks and use model update() with explicit failure propagation
- extend contact patch assertions and align test-create variants expectations to status=success for POST responses
- refresh composer lock metadata/dependency constraints and include generated docs/data/test files updated during normalization
- impact: API contract unchanged except clearer 400 error responses on invalid contact detail updates
2026-04-17 05:38:11 +07:00

9.1 KiB
Executable File
Raw Blame 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.

# 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.
  • Rely on Serena tools for guided edits, searches, and context summaries (use the available symbolic and search tools before running shell commands).