# AGENTS.md - AI Agent Guidelines for [PROJECT NAME] ## AI Agent Guidelines 1. **Readability**: Write code that is easy to read and understand. 2. **Maintainability**: Write code that is easy to maintain and update. 3. **Performance**: Write code that is fast and efficient. 4. **Security**: Write code that is secure and protected against attacks. 5. **Testing**: Write code that is tested and verified. ## Technology Stack | Layer | Technology | |-------|------------| | Backend | CodeIgniter 4 (PHP 8.1+) | | Frontend | Alpine.js + TailwindCSS | | Database | MySQL/MariaDB | ## Key Files & Locations ### Backend ``` app/Controllers/ # API & page controllers app/Models/ # Eloquent-style models app/Database/Migrations/ # Schema definitions app/Config/Routes.php # All routes defined here app/Helpers/ # Helper functions ``` ### Frontend ``` app/Views/ # PHP views with Alpine.js app/Views/layout/ # Base templates public/ # Static assets (css, js) ``` ## Coding Conventions ### PHP / CodeIgniter 4 1. **Controllers** extend `BaseController` and use `ResponseTrait` 2. **Models** extend `App\Models\BaseModel` (custom base with auto camel/snake conversion) 3. **Soft deletes** are enabled on all tables (`deleted_at`) 4. **Timestamps** are automatic (`created_at`, `updated_at`) 5. **Validation** happens in controllers, not models 6. **JSON API responses** follow this structure: ```php return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $rows ], 200); ``` 7. **Use camelCase for input/output**, snake_case for database: - Controllers convert camel→snake before insert/update - Models convert snake→camel after fetch - Use helper functions: `camel_to_snake()`, `camel_to_snake_array()`, `snake_to_camel()` ### Database 1. **Primary keys**: `{table_singular}_id` (e.g., `item_id`, `pat_id`) 2. **Foreign keys**: Match the referenced primary key name 3. **Naming**: All lowercase, underscores 4. **Soft deletes**: All tables have `deleted_at` DATETIME column 5. **Master data tables**: Prefix with `master_` (e.g., `master_items`) 6. **Timestamps**: `created_at`, `updated_at` DATETIME columns 7. **Unique constraints**: Add on code fields (e.g., `item_code`) ### Frontend / Alpine.js 1. **x-data** on container elements 2. **Fetch API** for AJAX calls (no jQuery) 3. **DaisyUI components** for UI elements 4. **camelCase** for JavaScript, **snake_case** for PHP/DB 5. **Modals** with x-show and x-transition 6. **ES6 modules** importing Alpine from `app.js` ### File Naming | Component | Pattern | Example | |-----------|---------|---------| | Controller | `PascalCase + Controller` | `ItemsController` | | Model | `PascalCase + Model` | `ItemsModel` | | Migration | `YYYY-MM-DD-XXXXXX_Description.php` | `2026-01-15-000001_Items.php` | | View | `module/action.php` | `items/index.php` | | Helper | `snake_case + _helper.php` | `stringcase_helper.php` | | Filter | `PascalCase + Filter.php` | `JwtAuthFilter.php` | ### Directory Structure ``` app/ ├── Config/ │ ├── Routes.php │ └── Filters.php ├── Controllers/ │ ├── BaseController.php │ ├── ItemsController.php │ └── Master/ │ └── ItemsController.php ├── Database/ │ └── Migrations/ ├── Filters/ │ └── JwtAuthFilter.php ├── Helpers/ │ ├── stringcase_helper.php │ └── utc_helper.php ├── Models/ │ ├── BaseModel.php │ ├── ItemsModel.php │ └── Master/ │ └── ItemsModel.php └── Views/ ├── layout/ │ ├── main_layout.php │ └── form_layout.php ├── items/ │ ├── items_index.php │ └── dialog_items_form.php └── login.php ``` ## Common Tasks ### Adding a New Master Data Entity 1. Create migration in `app/Database/Migrations/` 2. Create model in `app/Models/[Module]/` 3. Create controller in `app/Controllers/[Module]/` 4. Add routes in `app/Config/Routes.php` 5. Create view in `app/Views/[module]/` ### Adding a New API Endpoint ```php // In Routes.php $routes->get('api/resource', 'ResourceController::index'); $routes->get('api/resource/(:num)', 'ResourceController::show/$1'); $routes->post('api/resource', 'ResourceController::create'); $routes->patch('api/resource/(:num)', 'ResourceController::update/$1'); ``` ### Controller Template ```php model = new ItemsModel(); $this->rules = [ 'itemCode' => 'required|min_length[1]', 'itemName' => 'required', ]; } public function index() { $keyword = $this->request->getGet('keyword'); try { $rows = $this->model->getItems($keyword); return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $rows ], 200); } catch (\Exception $e) { return $this->failServerError('Exception: ' . $e->getMessage()); } } public function show($id = null) { try { $rows = $this->model->where('item_id', $id)->findAll(); if (empty($rows)) { return $this->respond([ 'status' => 'success', 'message' => 'data not found.' ], 200); } return $this->respond([ 'status' => 'success', 'message' => 'fetch success', 'data' => $rows ], 200); } catch (\Exception $e) { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } public function create() { $input = $this->request->getJSON(true); $input = camel_to_snake_array($input); if (!$this->validate($this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); } try { $id = $this->model->insert($input, true); return $this->respondCreated([ 'status' => 'success', 'message' => $id ]); } catch (\Exception $e) { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } public function update($id = null) { $input = $this->request->getJSON(true); $input = camel_to_snake_array($input); if (!$this->validate($this->rules)) { return $this->failValidationErrors($this->validator->getErrors()); } try { $this->model->update($id, $input); return $this->respondCreated([ 'status' => 'success', 'message' => 'update success', 'data' => $id ]); } catch (\Exception $e) { return $this->failServerError('Something went wrong: ' . $e->getMessage()); } } } ``` ### Model Template ```php groupStart() ->like('item_code', $keyword) ->orLike('item_name', $keyword) ->groupEnd() ->findAll(); } return $this->findAll(); } } ``` ### Migration Template ```php forge->addField([ 'item_id' => [ 'type' => 'int', 'unsigned' => true, 'auto_increment' => true ], 'item_code' => [ 'type' => 'VARCHAR', 'constraint' => 50 ], 'item_name' => [ 'type' => 'VARCHAR', 'constraint' => 150 ], 'created_at' => [ 'type' => 'DATETIME', 'null' => true ], 'updated_at' => [ 'type' => 'DATETIME', 'null' => true ], 'deleted_at' => [ 'type' => 'DATETIME', 'null' => true ] ]); $this->forge->addKey('item_id', true); $this->forge->addUniqueKey('item_code'); $this->forge->createTable('module_items'); } public function down() { $this->forge->dropTable('module_items', true); } } ``` ### Routes Template ```php post('/login', 'AuthController::login'); $routes->get('/logout', 'AuthController::logout'); $routes->group('', ['filter' => 'jwt-auth'], function ($routes) { $routes->get('/', 'PagesController::dashboard'); $routes->get('/module', 'PagesController::module'); $routes->get('/master/items', 'PagesController::masterItems'); }); $routes->group('api', function ($routes) { $routes->get('module/items', 'Module\ItemsController::index'); $routes->get('module/items/(:num)', 'Module\ItemsController::show/$1'); $routes->post('module/items', 'Module\ItemsController::create'); $routes->patch('module/items/(:num)', 'Module\ItemsController::update/$1'); }); ``` ### View Template (Index with Alpine.js) ```php extend("layout/main_layout"); ?> section("content"); ?>

Items

Manage your items

include('module/items/dialog_items_form'); ?>
endSection(); ?> section("script"); ?> endSection(); ?> ``` ### Running Migrations ```bash php spark migrate # Run all pending php spark migrate:rollback # Rollback last batch php spark migrate:refresh # Rollback all + re-run ``` ## Testing ```bash # Run all tests ./vendor/bin/phpunit # Run specific test file ./vendor/bin/phpunit tests/unit/SomeTest.php ``` ## Things to Avoid 1. **Don't use jQuery** - Use Alpine.js or vanilla JS 2. **Don't over-engineer** - This is a "no-nonsense" project 3. **Don't skip soft deletes** - Always use `deleted_at` 4. **Don't hardcode** - Use `.env` for configuration 5. **Don't mix concerns** - Controllers handle HTTP, Models handle data 6. **Don't forget camel/snake conversion** - Frontend uses camelCase, DB uses snake_case ## Questions to Ask Before Making Changes 1. Does this follow the existing patterns? 2. Is there a simpler way to do this? 3. Did I add the route? 4. Did I handle errors gracefully? 5. Does the API response match the standard format? 6. Did I convert camelCase to snake_case before DB operations? 7. Did I convert snake_case to camelCase after fetching? ## Post-Change Requirements **After every significant code change, update `README.md`:** | Change Type | README Section to Update | |-------------|--------------------------| | New Controller | Project Structure, API Endpoints | | New Model | Project Structure | | New Migration/Table | Database Schema | | New View | Project Structure | | New Route | API Endpoints | | New Command | Development Commands | | Config Change | Setup | Keep the README accurate and up-to-date with the actual codebase.