# AGENTS.md ## Purpose Describe how AI coding agents should work in this repository. This file is intentionally small. It acts as the repository-level router and startup guide. Detailed rules live in focused files under `./.ai/skills/` and should be loaded only when relevant to the current task. --- ## Startup Instructions Before working on any task: 1. Read this `AGENTS.md`. 2. Read the main skill index: ```text ./.ai/SKILLS.md ``` 3. Load only the relevant skill files for the current task. 4. Follow project-specific instructions before general best practices. 5. Inspect existing code before introducing new patterns. 6. State important assumptions before making major changes. 7. Prefer small, focused, reviewable changes. --- ## Instruction Priority When instructions conflict, follow this order: 1. User's current request 2. Safety, security, privacy, and data-loss prevention 3. This `AGENTS.md` 4. `./.ai/SKILLS.md` 5. Referenced files in `./.ai/skills/` 6. Existing project conventions 7. General best practices --- ## Project Summary This project is the **KCI Kanban Board** — a print/mail job tracking kanban application built on the **MindVisionCode PHP** framework. It was migrated from an ASP Classic implementation (source: `https://onefortheroadgit.sytes.net/dcovington/KCI-KANBAN`). ### What this app does - Manage multiple kanban **boards** (each board has a slug-based URL) - Each board has configurable **columns** (job stages) and **swim lanes** (job categories/priority rows) - **Cards** represent print jobs: job_number, job_name, customer_name, delivery_date, quantity, notes, full_note (PrintStream raw data) - Cards drag-and-drop between cells (column × lane intersection) via SortableJS - Board settings panel: add/rename/delete/reorder columns and swim lanes - **PrintStream integration flag** on boards (`import_from_printstream`, `printstream_job_name`) — the actual import script is a separate process not yet ported to PHP - Authentication via **Keycloak SSO** (OIDC authorization-code flow) ### Domain tables | Table | Purpose | |-------|---------| | `boards` | One row per kanban board | | `board_columns` | Columns (stages) belonging to a board | | `swim_lanes` | Swim lanes (rows) belonging to a board | | `cards` | Job cards placed in a column × lane cell | ### Auth pattern - `app/Services/AuthService.php` — static helpers: `requireLogin()`, `isLoggedIn()`, `getCurrentUsername()` - Page controllers call `if ($guard = AuthService::requireLogin()) return $guard;` - JSON API controllers call `if (!AuthService::isLoggedIn()) return $this->json([...], 401);` - Keycloak config lives in `config/auth.php`, reads from env vars — see `.env.example` ### Repository instantiation pattern Repositories are instantiated directly inside controllers using the `database()` helper (not DI container): ```php private function boards(): BoardRepository { return new BoardRepository(database()); } ``` ### Route ordering rule `/columns/reorder` and `/swimlanes/reorder` **must be registered before** `/columns/{id}` and `/swimlanes/{id}`. If the literal route comes after the param route, "reorder" is treated as an id. See `routes/web.php` comments. ### JSON body endpoints `ColumnsController::reorder()` and `SwimLanesController::reorder()` receive a JSON array body (not form data). They read it with `json_decode(file_get_contents('php://input'), true)`. Do not try to use `$request->input()` for these. ### Board show view `BoardsController::show()` uses `$this->fragment()` (not `$this->view()`) because the kanban board is a fully self-contained HTML page with its own `/
/` — it does not use the shared `app.php` layout. ### Static assets - `public/css/kanban.css` — kanban grid layout and card styles (copied from ASP repo) - `public/js/kanban-board.js` — grid rendering, drag-drop, search - `public/js/kanban-modal.js` — card create/edit modal - `public/js/kanban-settings.js` — settings panel (add/rename/delete/reorder columns and lanes) The JS posts to `/cards/*`, `/columns/*`, `/swimlanes/*` — the PHP routes must match exactly. ### Composer Run with the PHP binary found at: ``` C:\Users\danielc.NTP\AppData\Local\Microsoft\WinGet\Packages\PHP.PHP.8.5_Microsoft.Winget.Source_8wekyb3d8bbwe\php.exe ``` Composer.phar is at `D:\Development\PHP\PHP-TERRITORY\composer.phar`. Requires `-d extension=php_openssl.dll`. ### Docker build notes The `vendor/` directory is excluded by `.dockerignore`, so `composer install` runs inside the container at build time. The Dockerfile must install `libzip-dev unzip` (apt) and `zip` (PHP ext) **before** the `composer install` step — without them, Composer cannot extract downloaded package archives and exits with code 1. This is already in the Dockerfile. Do not remove those packages if updating the Dockerfile. ### PrintStream background import `scripts/import-printstream.php` runs every 30 minutes via cron inside the Docker container. It: 1. Reads all boards with `import_from_printstream = 1` 2. Parses `printstream_job_name` into filter tokens (newline-separated) 3. Connects to the PrintStream SQL Server (`KCI-PS-2024 / Livedata_dosrun`) via **FreeTDS + pdo_odbc** 4. For each token runs a CTE query against `dbo.SCHEDFIL`, `dbo.ESTIMATE`, `dbo.DEBTOR`, `dbo.NOTES` 5. Inserts new cards (first column, first lane) or refreshes PrintStream fields on existing cards **Schedule:** `docker/crontab` — `*/30 * * * *`. To change the interval, update both the crontab line and `IMPORT_RUN_EVERY_MINUTES` in `.env`. **Log:** `/var/log/kanban-import.log` inside the container (`docker exec