diff --git a/.kilo/EVOLUTION_LOG.md b/.kilo/EVOLUTION_LOG.md
index 8e2386f..7425e4d 100644
--- a/.kilo/EVOLUTION_LOG.md
+++ b/.kilo/EVOLUTION_LOG.md
@@ -249,3 +249,80 @@ Rules in `.kilo/rules/` are loaded into ALL agents' context. Heavyweight rules w
- [x] Compressed files reference correct skills directories
- [x] No content loss — all detail moved to `.kilo/skills/` or `.kilo/shared/`
- [ ] Pipeline validation pending
+
+---
+
+## Entry: 2026-04-18T23:08:00+01:00
+
+### Type
+Capability Expansion + Architecture Improvements — 7 evolutionary tasks
+
+### Gap Analysis
+1. No PHP web development support (Laravel, Symfony, WordPress)
+2. Agents hang on large tasks — need atomic decomposition
+3. Giant monolithic files instead of modular architecture
+4. Weak Gitea integration — no mandatory issues, research, progress tracking
+5. BUG: Issues created in APAW instead of target project (hardcoded repo)
+6. No execution logging — impossible to monitor agent performance
+7. Excessive token consumption — vague task assignments, scope creep
+
+### Implementation
+
+#### New Agent
+| Agent | Model | Purpose |
+|-------|-------|---------|
+| `php-developer` | qwen3-coder:480b | PHP/Laravel/Symfony/WordPress web apps |
+
+#### New Skills (6 PHP + 1 Logging)
+| Skill | Lines | Purpose |
+|-------|-------|---------|
+| `php-laravel-patterns` | 403 | Routing, Eloquent, Services, Repositories, Auth, Queues |
+| `php-symfony-patterns` | 233 | Controllers, Doctrine, Messenger, Voters |
+| `php-wordpress-patterns` | 276 | Plugins, CPT, REST API, Security |
+| `php-security` | 147 | OWASP Top 10, CSRF, XSS, SQL injection |
+| `php-testing` | 242 | PHPUnit, Pest, Dusk browser tests |
+| `php-modular-architecture` | 242 | Module separation, interfaces, events |
+| `agent-logging` | 160 | Execution logging to agent-executions.jsonl |
+
+#### New Commands
+| Command | Purpose |
+|---------|---------|
+| `/laravel` | Full-stack Laravel web application pipeline |
+| `/wordpress` | WordPress site/plugin development pipeline |
+
+#### New Rules (4)
+| Rule | Purpose |
+|------|---------|
+| `atomic-tasks.md` | 1 action = 1 task, task sizing, decomposition protocol |
+| `modular-code.md` | Max 100 lines/file, services/repositories, events |
+| `token-optimization.md` | Token budgets, no scope creep, routing matrix |
+| `gitea-centric-workflow.md` | Mandatory issues, research, progress tracking |
+
+#### Critical Bug Fix: Target Project Resolution
+- Removed ALL hardcoded `UniqueSoft/APAW` from API calls
+- Added `get_target_repo()` auto-detection via `git remote`
+- Updated: `gitea-api.md`, `gitea-commenting/SKILL.md`, `gitea-workflow/SKILL.md`, `gitea/SKILL.md`
+- Fallback: `GITEA_TARGET_REPO` env var → `UniqueSoft/APAW` only when in APAW directory
+
+#### New Monitoring
+- `.kilo/logs/agent-executions.jsonl` — execution log
+- `scripts/agent-stats.ts` — statistics aggregator
+
+### Verification
+- [x] PHP developer agent created with valid YAML frontmatter
+- [x] Orchestrator permissions updated for php-developer
+- [x] Capability index updated with php routing
+- [x] All hardcoded APAW refs replaced with auto-detection
+- [x] Execution logging initialized
+- [x] Agent stats script functional
+- [x] YAML validated (capability-index.yaml)
+- [x] README updated to current state
+- [x] STRUCTURE updated to current state
+
+### Metrics
+- New agents: 1 (php-developer, total now 29)
+- New skills: 7 (6 PHP + 1 logging)
+- New commands: 2 (laravel, wordpress)
+- New rules: 4 (atomic-tasks, modular-code, token-optimization, gitea-centric)
+- Hardcoded APAW refs fixed: 15+ across 5 files
+- Documentation pages updated: 3 (README, STRUCTURE, EVOLUTION_LOG)
diff --git a/.kilo/agents/orchestrator.md b/.kilo/agents/orchestrator.md
index c601cea..cc04da8 100755
--- a/.kilo/agents/orchestrator.md
+++ b/.kilo/agents/orchestrator.md
@@ -41,6 +41,7 @@ permission:
"reflector": allow
"memory-manager": allow
"agent-architect": allow
+ "php-developer": allow
---
# Orchestrator
@@ -70,6 +71,7 @@ Task dispatcher and state machine manager. Route by issue status; enforce workfl
| the-fixer | Review fail: fix issues |
| frontend-developer | UI implementation needed |
| backend-developer | Node.js/Express/API work |
+| php-developer | PHP/Laravel/Symfony/WordPress web apps |
| go-developer | Go backend services |
| flutter-developer | Flutter mobile apps |
| performance-engineer | Review pass: check performance |
diff --git a/.kilo/agents/php-developer.md b/.kilo/agents/php-developer.md
new file mode 100644
index 0000000..15bd9cc
--- /dev/null
+++ b/.kilo/agents/php-developer.md
@@ -0,0 +1,65 @@
+---
+description: PHP backend specialist for Laravel, Symfony, WordPress, and full-stack web applications
+mode: subagent
+model: ollama-cloud/qwen3-coder:480b
+variant: thinking
+color: "#8B5CF6"
+permission:
+ read: allow
+ edit: allow
+ write: allow
+ bash: allow
+ glob: allow
+ grep: allow
+ task:
+ "*": deny
+ "code-skeptic": allow
+ "security-auditor": allow
+ "orchestrator": allow
+---
+
+# PHP Developer
+
+## Role
+PHP backend specialist: Laravel/Symfony APIs, WordPress plugins, database integration, authentication, modular architecture.
+
+## Behavior
+- Security first: validate input, sanitize output, parameterized queries, CSRF protection
+- RESTful design: proper HTTP methods, status codes, error handling
+- Modular architecture: separate controllers, services, repositories, models
+- Use dependency injection and service containers
+- Follow PSR-12 coding standards
+- Never mix business logic in controllers — use service classes
+- Write tests with PHPUnit/Pest before implementation (TDD)
+
+## Delegates
+| Agent | When |
+|-------|------|
+| code-skeptic | After implementation |
+| security-auditor | For security review |
+
+## Output
+
+
+
+
+
+
+
+## Skills
+| Skill | When |
+|-------|------|
+| php-laravel-patterns | Laravel routing, Eloquent, middleware, queues |
+| php-symfony-patterns | Symfony controllers, services, Doctrine |
+| php-wordpress-patterns | WordPress plugins, themes, REST API, hooks |
+| php-security | OWASP, CSRF, XSS, SQL injection, auth |
+| php-testing | PHPUnit, Pest, Dusk, mocking |
+| php-modular-architecture | Modules, packages, service separation |
+
+## Handoff
+1. Run `composer install` && `vendor/bin/phpunit`
+2. Run `phpcs --standard=PSR12 src/`
+3. Verify no security vulnerabilities: `composer audit`
+4. Delegate: code-skeptic
+
+
\ No newline at end of file
diff --git a/.kilo/capability-index.yaml b/.kilo/capability-index.yaml
index 3432858..7dee94a 100644
--- a/.kilo/capability-index.yaml
+++ b/.kilo/capability-index.yaml
@@ -49,6 +49,41 @@ agents:
- visual-tester
- orchestrator
+ php-developer:
+ capabilities:
+ - php_web_development
+ - laravel_development
+ - symfony_development
+ - wordpress_development
+ - php_api_development
+ - php_database_design
+ - php_authentication
+ - php_modular_architecture
+ - php_testing
+ - php_security
+ receives:
+ - api_specifications
+ - database_requirements
+ - ui_requirements
+ produces:
+ - laravel_routes
+ - php_models
+ - php_services
+ - php_controllers
+ - php_migrations
+ - php_tests
+ - wordpress_plugins
+ forbidden:
+ - frontend_code
+ - non_php_backend
+ model: ollama-cloud/qwen3-coder:480b
+ variant: thinking
+ mode: subagent
+ delegates_to:
+ - code-skeptic
+ - security-auditor
+ - orchestrator
+
backend-developer:
capabilities:
- api_development
@@ -423,6 +458,7 @@ agents:
- the-fixer
- frontend-developer
- backend-developer
+ - php-developer
- go-developer
- flutter-developer
- performance-engineer
@@ -700,6 +736,11 @@ agents:
clickhouse_integration: go-developer
# Mobile development
flutter_development: flutter-developer
+ # PHP Development
+ php_web_development: php-developer
+ laravel_development: php-developer
+ symfony_development: php-developer
+ wordpress_development: php-developer
# DevOps
docker_configuration: devops-engineer
kubernetes_setup: devops-engineer
diff --git a/.kilo/commands/laravel.md b/.kilo/commands/laravel.md
new file mode 100644
index 0000000..6e86c72
--- /dev/null
+++ b/.kilo/commands/laravel.md
@@ -0,0 +1,225 @@
+---
+description: Full-stack Laravel web application pipeline — from requirements to deployment
+mode: laravel
+model: ollama-cloud/qwen3-coder:480b
+variant: thinking
+color: "#8B5CF6"
+permission:
+ read: allow
+ edit: allow
+ write: allow
+ bash: allow
+ glob: allow
+ grep: allow
+ task:
+ "*": deny
+ "php-developer": allow
+ "system-analyst": allow
+ "lead-developer": allow
+ "sdet-engineer": allow
+ "code-skeptic": allow
+ "the-fixer": allow
+ "frontend-developer": allow
+ "devops-engineer": allow
+ "release-manager": allow
+ "security-auditor": allow
+ "browser-automation": allow
+ "orchestrator": allow
+---
+
+# Laravel Web Application Pipeline
+
+Create a full-stack Laravel web application with modular architecture, authentication, database, API, and Docker deployment. Follows atomic task decomposition — each step is ONE atomic task.
+
+## Parameters
+
+- `project_name`: Application name (required)
+- `stack`: Laravel version - '10', '11' (default: '11')
+- `frontend`: Frontend - 'blade', 'inertia', 'api-only' (default: 'blade')
+- `database`: Database - 'mysql', 'pgsql', 'sqlite' (default: 'mysql')
+- `docker`: Create Docker deployment (default: true)
+- `issue`: Gitea issue number for tracking (required)
+
+## Overview
+
+```
+Requirements → Architecture → Models → API → Frontend → Auth → Tests → Docker → Docs
+```
+
+## Step 1: Requirements (Atomic: 1 task)
+
+**Agent**: `@requirement-refiner`
+
+- Create Gitea issue in TARGET PROJECT (not APAW)
+- Define user stories with acceptance criteria as checkboxes
+- Identify stakeholders and roles
+- Document non-functional requirements
+
+## Step 2: Architecture (Atomic: 1 task)
+
+**Agent**: `@system-analyst`
+
+- Design database schema
+- Define API endpoints (REST)
+- Choose Laravel modules
+- Document architecture decisions as Gitea comment
+- Create modular structure plan:
+
+```
+app/Modules/
+├── User/ # Authentication, profiles
+├── {Feature}/ # Main feature module
+└── Shared/ # Cross-module utilities
+```
+
+## Step 3: Project Setup (Atomic: 1 task)
+
+**Agent**: `@php-developer`
+
+```bash
+composer create-project laravel/laravel {project_name}
+cd {project_name}
+composer require laravel/sanctum # API auth
+```
+
+## Step 4: Database Migrations (Atomic: per model)
+
+**Agent**: `@php-developer` (one invocation per model)
+
+Each model is its own atomic task:
+- Create migration file
+- Create Eloquent model with scopes and relationships
+- Create factory for testing
+- Run `php artisan migrate`
+
+**Example atomic task**: "Create Product model with migration at `app/Modules/Product/Models/Product.php` with fields: name, slug, price, category_id, is_active, timestamps. Create migration at `database/migrations/2026_04_18_create_products_table.php`."
+
+## Step 5: Repositories (Atomic: per repository)
+
+**Agent**: `@php-developer` (one invocation per repository)
+
+- Create repository interface
+- Create repository implementation
+- Register in service container
+
+## Step 6: Services (Atomic: per service)
+
+**Agent**: `@php-developer` (one invocation per service, max 3 methods)
+
+- Create service class with business logic
+- Inject dependencies via constructor
+- Dispatch events for side effects
+
+## Step 7: Controllers (Atomic: per controller)
+
+**Agent**: `@php-developer` (one invocation per controller)
+
+- Thin controller, delegates to service
+- Form Request for validation
+- API Resource for response transformation
+
+## Step 8: Routes (Atomic: 1 task)
+
+**Agent**: `@php-developer`
+
+- Define API routes in `routes/api.php`
+- Apply middleware groups
+- Version API: `Route::prefix('v1')`
+
+## Step 9: Authentication (Atomic: 1 task)
+
+**Agent**: `@php-developer`
+
+- Laravel Sanctum setup
+- Login/Register/Logout endpoints
+- Password reset
+- Email verification
+
+## Step 10: Frontend (Atomic: per view/component)
+
+**Agent**: `@frontend-developer` (one invocation per component)
+
+- Blade templates OR Inertia.js components
+- Responsive layout
+- Form validation feedback
+
+## Step 11: Tests (Atomic: per test file)
+
+**Agent**: `@sdet-engineer` (one invocation per test suite)
+
+- PHPUnit/Pest feature tests for each endpoint
+- Unit tests for services
+- Browser tests for critical flows
+
+## Step 12: Code Review
+
+**Agent**: `@code-skeptic`
+
+- Review all changes
+- Check security, performance, maintainability
+- Verify modular architecture rules
+
+## Step 13: Security Audit
+
+**Agent**: `@security-auditor`
+
+- OWASP Top 10 check
+- `composer audit` for CVEs
+- CSRF, XSS, SQL injection review
+- Authentication review
+
+## Step 14: Docker
+
+**Agent**: `@devops-engineer`
+
+- Create `Dockerfile` (multi-stage)
+- Create `docker-compose.yml` (app, db, nginx)
+- Health checks and environment configuration
+
+## Step 15: Release
+
+**Agent**: `@release-manager`
+
+- Final test run
+- Lint: `phpcs --standard=PSR12`
+- Coverage report
+- **Only commit if user explicitly requests**
+
+## Atomic Task Rules
+
+### Each task invocation follows this pattern:
+
+1. Post starting comment to Gitea issue (in TARGET project!)
+2. Execute ONE atomic task
+3. Run verification (tests, lint)
+4. Log execution to `.kilo/logs/agent-executions.jsonl`
+5. Post completion comment to Gitea issue
+6. Update progress checkboxes
+
+### Task Sizing:
+
+| Task | Agent | Max Tokens |
+|------|-------|-----------|
+| Create model + migration | php-developer | 5,000 |
+| Create repository | php-developer | 5,000 |
+| Create service (3 methods max) | php-developer | 8,000 |
+| Create controller + routes | php-developer | 5,000 |
+| Create auth endpoints | php-developer | 8,000 |
+| Create Vue/Blade component | frontend-developer | 8,000 |
+| Write test suite | sdet-engineer | 8,000 |
+| Review all code | code-skeptic | 8,000 |
+| Security audit | security-auditor | 10,000 |
+| Docker setup | devops-engineer | 5,000 |
+
+## Quality Gates
+
+| Gate | Criteria |
+|------|----------|
+| Architecture | Modular structure defined |
+| Migrations | `php artisan migrate` succeeds |
+| Models | Factory and scopes work |
+| API | All endpoints return correct responses |
+| Auth | Login/register/logout work |
+| Tests | Coverage >= 80% |
+| Security | No vulnerabilities, `composer audit` clean |
+| Docker | Containers build and run |
\ No newline at end of file
diff --git a/.kilo/commands/pipeline.md b/.kilo/commands/pipeline.md
index a437c55..1e8f7c0 100644
--- a/.kilo/commands/pipeline.md
+++ b/.kilo/commands/pipeline.md
@@ -60,15 +60,22 @@ Based on the issue status label, invoke the appropriate agent using Task tool:
## Step 5: Log Progress to Gitea
-After each agent completes, post comment:
+After each agent completes, post comment to the TARGET project issue (NOT APAW):
+
```bash
-gh issue comment {issue_number} --body "## ✅ {agent_name} completed
+# Auto-detect target project
+TARGET_REPO=$(git remote get-url origin | sed -E 's|.*[:/]([^/]+/[^/]+?)(\.git)?$|\1|')
-**Score**: {score}/10
-**Duration**: {duration}
-**Next**: {next_agent}
+# Post comment using target project
+curl -X POST -H "Authorization: token ${GITEA_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d "{\"body\":\"## ✅ ${agent_name} completed\\n\\n**Score**: ${score}/10\\n**Duration**: ${duration}\\n**Tokens**: ~${tokens_used}\\n**Next**: ${next_agent}\\n\\n${agent_notes}\"}" \
+ "https://git.softuniq.eu/api/v1/repos/${TARGET_REPO}/issues/${issue_number}/comments"
+```
-{agent_notes}"
+Also log execution to `.kilo/logs/agent-executions.jsonl`:
+```bash
+echo "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"agent\":\"${agent_name}\",\"issue\":${issue_number},\"project\":\"${TARGET_REPO}\",\"task\":\"${task}\",\"subtask_type\":\"${subtask_type}\",\"duration_ms\":${duration_ms},\"tokens_used\":${tokens_used},\"status\":\"${status}\",\"files\":[${files}],\"score\":${score},\"next_agent\":\"${next_agent}\"}" >> .kilo/logs/agent-executions.jsonl
```
## Step 6: Update Status Label
diff --git a/.kilo/commands/wordpress.md b/.kilo/commands/wordpress.md
new file mode 100644
index 0000000..c7b9ac0
--- /dev/null
+++ b/.kilo/commands/wordpress.md
@@ -0,0 +1,131 @@
+---
+description: WordPress site or plugin development pipeline with modern patterns
+mode: wordpress
+model: ollama-cloud/qwen3-coder:480b
+variant: thinking
+color: "#21759B"
+permission:
+ read: allow
+ edit: allow
+ write: allow
+ bash: allow
+ glob: allow
+ grep: allow
+ task:
+ "*": deny
+ "php-developer": allow
+ "system-analyst": allow
+ "lead-developer": allow
+ "sdet-engineer": allow
+ "code-skeptic": allow
+ "the-fixer": allow
+ "frontend-developer": allow
+ "devops-engineer": allow
+ "release-manager": allow
+ "security-auditor": allow
+ "orchestrator": allow
+---
+
+# WordPress Development Pipeline
+
+Create a WordPress site, theme, or plugin following modern PHP patterns with namespacing, strict types, and modular architecture.
+
+## Parameters
+
+- `project_name`: Plugin or theme name (required)
+- `type`: 'plugin', 'theme', 'site' (default: 'plugin')
+- `wp_version`: WordPress version (default: '6.5')
+- `docker`: Create Docker deployment (default: true)
+- `issue`: Gitea issue number for tracking (required)
+
+## Overview
+
+```
+Requirements → Architecture → Setup → Custom Types → REST API → Frontend → Tests → Docker
+```
+
+## Atomic Task Decomposition
+
+Each step is exactly ONE atomic task per agent invocation.
+
+### Step 1: Requirements (1 task)
+
+**Agent**: `@requirement-refiner`
+- Create issue in TARGET PROJECT (not APAW)
+- Define user stories and acceptance criteria
+
+### Step 2: Architecture (1 task)
+
+**Agent**: `@system-analyst`
+- Define data model
+- Design REST API endpoints
+- Plan custom post types and taxonomies
+
+### Step 3: Plugin/Theme Setup (1 task)
+
+**Agent**: `@php-developer`
+
+For plugin:
+```
+{project_name}/
+├── {project_name}.php # Main plugin file
+├── composer.json
+├── includes/
+│ ├── Admin/
+│ ├── Frontend/
+│ ├── REST/
+│ ├── PostTypes/
+│ ├── Taxonomies/
+│ └── Utils/
+├── assets/
+└── languages/
+```
+
+### Step 4: Custom Post Types (1 task per CPT)
+
+**Agent**: `@php-developer` (ONE invocation per CPT)
+
+- Register custom post type with labels and supports
+- Register custom meta fields with `show_in_rest`
+- Create CPT factory for testing
+
+### Step 5: REST API Controllers (1 task per resource)
+
+**Agent**: `@php-developer` (ONE invocation per controller)
+
+- Extend `WP_REST_Controller`
+- Implement CRUD operations
+- Add permission callbacks
+- Input sanitization and validation
+
+### Step 6: Frontend (1 task per component)
+
+**Agent**: `@frontend-developer`
+
+- Gutenberg blocks or Vue.js components
+- Admin pages with React/Vue
+- Frontend templates
+
+### Step 7: Tests (1 task per test file)
+
+**Agent**: `@sdet-engineer`
+
+- PHPUnit tests for services
+- WP_REST_Server integration tests
+- E2E tests for critical flows
+
+### Step 8: Review → Security → Docker → Release
+
+Same pattern as Laravel pipeline.
+
+## Quality Gates
+
+| Gate | Criteria |
+|------|----------|
+| Setup | Plugin activates without errors |
+| CPTs | `show_in_rest` works, API returns data |
+| API | All endpoints return correct responses |
+| Auth | Permission checks work |
+| Security | Nonce verification, input sanitization |
+| Tests | PHPUnit passes |
+| Docker | Containers build and run |
\ No newline at end of file
diff --git a/.kilo/logs/agent-executions.jsonl b/.kilo/logs/agent-executions.jsonl
new file mode 100644
index 0000000..f9f17cd
--- /dev/null
+++ b/.kilo/logs/agent-executions.jsonl
@@ -0,0 +1 @@
+{"ts":"2026-04-18T14:00:00Z","agent":"system","issue":0,"project":"UniqueSoft/APAW","task":"Initialize agent execution logging","subtask_type":"config_change","duration_ms":0,"tokens_used":0,"status":"success","files":[],"score":10,"next_agent":null}
\ No newline at end of file
diff --git a/.kilo/rules/atomic-tasks.md b/.kilo/rules/atomic-tasks.md
new file mode 100644
index 0000000..d14aaf9
--- /dev/null
+++ b/.kilo/rules/atomic-tasks.md
@@ -0,0 +1,102 @@
+# Atomic Task Decomposition Rules
+
+CRITICAL: Agents must execute ONE small task per invocation. Never assign broad multi-step tasks.
+
+## Problem
+
+Agents frequently hang or produce incomplete results when given large, complex tasks. Token budgets are exhausted before completion.
+
+## Solution: Atomic Task Principle
+
+**1 agent invocation = 1 atomic task = 1 clear outcome = 1 verification**
+
+## Atomic Task Definition
+
+An atomic task meets ALL criteria:
+- Completable in under 5 minutes
+- Has a single clear deliverable
+- Can be verified independently (test, lint, build)
+- Produces at most 3-5 files
+- No more than 100 lines changed per file
+
+## Decomposition Rules
+
+### Before Delegating
+
+1. **Decompose first**: Break any task into 3-5 atomic subtasks
+2. **Order by dependency**: Subtasks that depend on others come later
+3. **Each subtask gets its own agent invocation**
+
+### Task Sizing Guide
+
+| Task Type | Max Scope | Max Files | Max Lines |
+|-----------|-----------|-----------|-----------|
+| Model/Entity creation | 1 model + 1 migration | 2 | 80 |
+| API endpoint | 1 endpoint + 1 test | 2 | 100 |
+| Service method | 1 method + 1 test | 2 | 60 |
+| UI Component | 1 component + 1 test | 2 | 80 |
+| Bug fix | 1 fix + 1 test | 2 | 50 |
+| Config change | 1 config file | 1 | 30 |
+
+### Violation Examples (DON'T)
+
+```
+❌ "Implement the entire e-commerce backend"
+❌ "Create all models, controllers, and services for the product module"
+❌ "Build the admin panel with all CRUD operations"
+❌ "Fix all failing tests"
+```
+
+### Correct Examples (DO)
+
+```
+✅ "Create Product model with migration and factory"
+✅ "Add POST /api/products endpoint with validation and test"
+✅ "Build ProductCard.vue component with props and unit test"
+✅ "Fix TypeError in OrderService::calculateTotal - add null check"
+```
+
+## Orchestrator Decomposition Protocol
+
+When orchestrator receives a task:
+
+1. **Count atomic subtasks**: How many minimal units?
+2. **If > 5 subtasks**: Create sub-milestone issues in Gitea for tracking
+3. **Delegate one subtask at a time** via Task tool
+4. **Wait for completion** before delegating next
+5. **Verify output** after each subtask
+6. **Update Gitea issue** with progress after each subtask
+
+## Agent Self-Regulation
+
+Each agent must:
+
+1. **Check task size**: If too broad, split it and report back
+2. **Focus on one deliverable**: Don't expand scope
+3. **Complete before extending**: Finish the assigned task, don't add extras
+4. **Report precisely**: "Done: X" not "I also did Y and Z"
+
+## Token Budget per Atomic Task
+
+| Task Complexity | Token Budget | Time Budget |
+|----------------|-------------|-------------|
+| Simple (config, fix) | 5,000 | 2 min |
+| Medium (endpoint, component) | 10,000 | 5 min |
+| Complex (multi-service flow) | 20,000 | 10 min |
+
+If approaching budget, STOP and report progress. Delegate continuation to next invocation.
+
+## Pipeline Step Granularity
+
+Pipeline steps must be fine-grained:
+
+```
+❌ Step 3: "Implement Backend" (too broad)
+✅ Step 3a: "Create Product model + migration"
+✅ Step 3b: "Add GET /api/products endpoint"
+✅ Step 3c: "Add POST /api/products endpoint"
+✅ Step 3d: "Create ProductService with list() and create()"
+✅ Step 3e: "Add ProductRepository with filtering"
+```
+
+Each sub-step is its own agent invocation with its own Gitea comment.
\ No newline at end of file
diff --git a/.kilo/rules/gitea-centric-workflow.md b/.kilo/rules/gitea-centric-workflow.md
new file mode 100644
index 0000000..4bc8974
--- /dev/null
+++ b/.kilo/rules/gitea-centric-workflow.md
@@ -0,0 +1,206 @@
+# Gitea-Centric Workflow Rules
+
+Gitea is the brain and center of all work. Every task, every decision, every progress update must flow through Gitea.
+
+## Core Rules
+
+### 1. ALWAYS Create Issues Before Work
+
+Before any implementation work begins:
+
+1. **Create a Gitea issue** in the TARGET project repository (NOT in APAW)
+2. Issue must include acceptance criteria as checkboxes
+3. Issue must have appropriate labels (`status: new`, workflow type)
+4. Post the issue number for all agents to reference
+
+### 2. ALWAYS Plan Before Implementing
+
+1. Post research findings as comments on the issue
+2. Include links to references, documentation, similar solutions
+3. Get confirmation before proceeding to implementation
+4. Document architecture decisions in issue comments
+
+### 3. ALWAYS Track Progress via Checkboxes
+
+Update the issue body checkboxes as work progresses:
+
+```markdown
+## Progress
+
+- [x] Requirements gathered
+- [x] Architecture designed
+- [ ] Database migration created
+- [ ] API endpoints implemented
+- [ ] Tests written
+- [ ] Code reviewed
+```
+
+### 4. ALWAYS Post Screenshots on Test Results
+
+When running tests (E2E, visual, browser):
+- Upload screenshots of pass/fail states to Gitea
+- Include URLs tested
+- Include console/network errors if any
+- Reference screenshots in issue comments
+
+### 5. ALWAYS Leave Research Links
+
+When investigating solutions:
+- Post relevant documentation links in issue comments
+- Reference Stack Overflow, official docs, package docs
+- Note pros/cons of considered approaches
+- Include code snippets found during research
+
+## Target Project Resolution
+
+**CRITICAL**: Issues must be created in the project being worked on, NOT in APAW.
+
+### How to Determine Target Project
+
+1. Check `git remote -v` in the working directory
+2. Parse the owner/repo from the remote URL
+3. Use that repo for ALL Gitea operations
+
+```python
+import re
+
+def get_target_repo():
+ """Detect target project from git remote"""
+ import subprocess
+ result = subprocess.run(
+ ['git', 'remote', 'get-url', 'origin'],
+ capture_output=True, text=True
+ )
+ remote_url = result.stdout.strip()
+
+ # HTTPS: https://git.softuniq.eu/Owner/Repo.git
+ # SSH: git@git.softuniq.eu:Owner/Repo.git
+
+ match = re.search(r'[:/]([^/]+/[^/]+?)(?:\.git)?$', remote_url)
+ if match:
+ return match.group(1)
+
+ # FALLBACK: default to APAW only if we're IN the APAW directory
+ return "UniqueSoft/APAW"
+```
+
+### Usage in All Gitea API Calls
+
+```python
+# NEVER hardcode the repo
+# ❌ BAD
+url = f"https://git.softuniq.eu/api/v1/repos/UniqueSoft/APAW/issues"
+
+# ✅ GOOD
+target_repo = get_target_repo()
+url = f"https://git.softuniq.eu/api/v1/repos/{target_repo}/issues"
+```
+
+### Environment Variable Override
+
+```bash
+# Set target project explicitly if needed
+export GITEA_TARGET_REPO="UniqueSoft/my-project"
+```
+
+## Comment Protocol
+
+### Before Starting Work
+
+```markdown
+## 🔄 {agent-name} starting
+
+**Task**: {what will be done}
+**Issue**: #{issue_number}
+**Atomic subtask**: {specific subtask description}
+**Estimated complexity**: {simple/medium/complex}
+```
+
+### After Research
+
+```markdown
+## 🔍 {agent-name} research complete
+
+### Findings
+- {finding 1}
+- {finding 2}
+
+### References
+- [Doc Link 1](url)
+- [Doc Link 2](url)
+
+### Architecture Decision
+{decision with rationale}
+
+### Next Steps
+1. {step 1}
+2. {step 2}
+```
+
+### During Testing (with screenshots)
+
+```markdown
+## 🧪 {agent-name} test results
+
+### Screenshot
+
+
+### URL Tested
+- `{url}`
+
+### Console Errors
+{any console errors}
+
+### Network Errors
+{any network errors}
+
+### Verdict
+✅ PASS / ❌ FAIL
+```
+
+### On Completion
+
+```markdown
+## ✅ {agent-name} completed
+
+**Task**: {what was done}
+**Files**: {list of files changed}
+**Duration**: {time spent}
+**Score**: {self-assessment 1-10}
+
+### Changes Made
+- {change 1}
+- {change 2}
+
+**Next**: {next_agent_name}
+```
+
+### On Blocking Issue
+
+```markdown
+## 🚫 {agent-name} blocked
+
+**Blocker**: {what's blocking}
+**Options**: {1, 2, 3}
+
+Waiting for decision.
+```
+
+## Git History as Knowledge Base
+
+Every file's git history is accessible and valuable:
+
+1. **Before modifying any file**: Check `git log -- {filepath}` for context
+2. **Before creating a feature**: Search `git log --all --grep="{keywords}"`
+3. **Before fixing a bug**: Check if it was fixed before: `git log --all -S "{pattern}"`
+4. **Reference commits**: Include commit hashes in issue comments
+
+## Verification Checklist
+
+- [ ] Issue created in TARGET project (not APAW unless APAW is the target)
+- [ ] Acceptance criteria defined as checkboxes
+- [ ] Research posted with links before implementation
+- [ ] Progress checkboxes updated after each subtask
+- [ ] Screenshots uploaded for test results
+- [ ] All comments reference the correct issue number
+- [ ] Git history checked before making changes
\ No newline at end of file
diff --git a/.kilo/rules/modular-code.md b/.kilo/rules/modular-code.md
new file mode 100644
index 0000000..82e6134
--- /dev/null
+++ b/.kilo/rules/modular-code.md
@@ -0,0 +1,200 @@
+# Modular Code Rules
+
+CRITICAL: Never write giant monolithic files. Split code into modules, libraries, and microservice-ready components.
+
+## Problem
+
+Agents write enormous single files that are hard to review, test, debug, and maintain. No clear boundaries between features.
+
+## Core Principles
+
+1. **Maximum file size**: 100 lines per file (excluding tests and migrations)
+2. **Maximum function/method size**: 30 lines
+3. **Maximum class size**: 5 public methods
+4. **One responsibility per file**: A file does ONE thing
+
+## Module Structure (Mandatory)
+
+Every feature must be organized as an independent module:
+
+```
+{feature}/
+├── Controllers/ # HTTP request handling (thin)
+├── Services/ # Business logic (fat)
+├── Repositories/ # Data access (abstracted)
+├── Models/ # Data definitions
+├── Routes/ # Route definitions
+├── Events/ # Events this module emits
+├── Listeners/ # Events this module handles
+├── Jobs/ # Async work this module performs
+├── Requests/ # Input validation (not in controller)
+├── Resources/ # Output transformation (not raw model)
+├── Exceptions/ # Module-specific exceptions
+├── Tests/ # Module-specific tests
+└── ModuleServiceProvider.php # Module registration
+```
+
+## Service Layer Rules
+
+```php
+// ❌ BAD: Business logic in controller
+class ProductController
+{
+ public function store(Request $request)
+ {
+ $product = Product::create($request->all());
+ Cache::forget('products');
+ event(new ProductCreated($product));
+ Mail::to($product->vendor)->send(new NewProduct($product));
+ return response()->json($product);
+ }
+}
+
+// ✅ GOOD: Business logic in service
+class ProductController
+{
+ public function __construct(private ProductService $service) {}
+
+ public function store(ProductStoreRequest $request): JsonResponse
+ {
+ $product = $this->service->create($request->validated());
+ return response()->json(new ProductResource($product), 201);
+ }
+}
+
+class ProductService
+{
+ public function create(array $data): Product
+ {
+ $product = $this->repository->create($data);
+ $this->clearCache();
+ ProductCreated::dispatch($product);
+ $this->notifyVendor($product);
+ return $product;
+ }
+}
+```
+
+## Repository Pattern (Mandatory for Data Access)
+
+```php
+// ❌ BAD: Query in controller or service
+$products = Product::where('active', true)->paginate(20);
+
+// ✅ GOOD: Query in repository
+interface ProductRepositoryInterface
+{
+ public function listActive(int $perPage = 20): LengthAwarePaginator;
+}
+
+class ProductRepository implements ProductRepositoryInterface
+{
+ public function __construct(private Product $model) {}
+
+ public function listActive(int $perPage = 20): LengthAwarePaginator
+ {
+ return $this->model->query()
+ ->where('is_active', true)
+ ->orderBy('created_at', 'desc')
+ ->paginate($perPage);
+ }
+}
+```
+
+## Cross-Module Communication
+
+Modules MUST NOT import models or repositories from other modules.
+
+```
+❌ Product module imports Order model directly
+❌ Order module calls ProductRepository directly
+
+✅ Product module dispatches ProductCreated event
+✅ Order module listens to ProductCreated event
+✅ Module boundaries enforced via interfaces
+```
+
+## Microservice Readiness
+
+Every module must be extractable as an independent service:
+
+1. **Own database migrations**: Module manages its own tables
+2. **Own routes**: Module registers its own routes
+3. **Own config**: Module has its own configuration
+4. **Own tests**: Module tests run independently
+5. **Interface contracts**: Module exposes interfaces, not implementations
+
+## File Splitting Rules
+
+When a file exceeds 100 lines:
+
+```
+Original: ProductController.php (250 lines)
+ ↓ Split into:
+ProductController.php # index, show (thin delegates)
+ProductStoreController.php # store endpoint (thin delegates)
+ProductUpdateController.php # update endpoint (thin delegates)
+ProductService.php # business logic (called by all)
+```
+
+When a service exceeds 5 methods:
+
+```
+Original: ProductService.php (8 methods)
+ ↓ Split into:
+ProductCrudService.php # create, update, delete
+ProductSearchService.php # list, search, filter
+ProductPricingService.php # calculatePrice, applyDiscount
+```
+
+## Language-Specific Module Patterns
+
+### Node.js
+```
+src/modules/product/
+├── routes.js
+├── controller.js
+├── service.js
+├── repository.js
+├── model.js
+├── validators.js
+└── __tests__/
+```
+
+### Go
+```
+internal/product/
+├── handler.go
+├── service.go
+├── repository.go
+├── model.go
+└── handler_test.go
+```
+
+### Flutter/Dart
+```
+lib/features/product/
+├── data/
+│ ├── repositories/
+│ └── models/
+├── domain/
+│ ├── entities/
+│ └── usecases/
+└── presentation/
+ ├── pages/
+ ├── widgets/
+ └── providers/
+```
+
+## Checklist
+
+- [ ] Every file under 100 lines
+- [ ] Every function under 30 lines
+- [ ] Every class under 5 public methods
+- [ ] Features organized as modules
+- [ ] Service layer contains business logic
+- [ ] Repository layer abstracts data access
+- [ ] Controllers are thin (5-10 lines per method)
+- [ ] Cross-module communication via events
+- [ ] Each module testable independently
+- [ ] Each module extractable to microservice
\ No newline at end of file
diff --git a/.kilo/rules/token-optimization.md b/.kilo/rules/token-optimization.md
new file mode 100644
index 0000000..6235aea
--- /dev/null
+++ b/.kilo/rules/token-optimization.md
@@ -0,0 +1,163 @@
+# Token Optimization Rules
+
+Reduce token waste by ensuring 1 action = 1 task. No vague broad assignments, no scope creep, no unnecessary context.
+
+## Core Principle: 1 Action = 1 Task
+
+Every agent invocation solves exactly ONE atomic task. No more, no less.
+
+## Token Budget Awareness
+
+| Task Size | Max Tokens | Max Time | Example |
+|----------|-----------|----------|---------|
+| Tiny | 2,000 | 1 min | Fix a typo, add a config value |
+| Small | 5,000 | 2 min | Create a model + migration |
+| Medium | 10,000 | 5 min | Create an API endpoint + test |
+| Large | 20,000 | 10 min | Create a full service with 3 methods |
+
+## Optimization Strategies
+
+### 1. Precise Task Descriptions
+
+```
+❌ BAD: "Implement the product feature"
+ - Too broad, no boundaries, will try to do everything
+ - Likely to hang or produce incomplete results
+
+✅ GOOD: "Create Product model at app/Models/Product.php with fields: name, price, category_id, is_active. Create migration at database/migrations/2026_04_18_create_products_table.php"
+ - Specific files, specific fields, atomic scope
+```
+
+### 2. Minimal Context
+
+Only provide context that is directly needed for the task.
+
+```
+❌ BAD: Providing the entire codebase as context
+
+✅ GOOD: Providing only the relevant files and interfaces
+```
+
+### 3. No Scope Creep
+
+```
+❌ BAD: Agent decides to also "improve" nearby code while fixing a bug
+❌ BAD: Agent adds "helpful" features not requested
+❌ BAD: Agent refactors unrelated code
+
+✅ GOOD: Agent does exactly what was asked, nothing more
+✅ GOOD: If agent sees improvement opportunity, REPORT it, don't implement it
+```
+
+### 4. Sequential Decomposition
+
+Break large features into sequential atomic tasks:
+
+```
+Feature: Product Catalog
+ ├── Task 1: Create Product model + migration (php-developer, 5k tokens)
+ ├── Task 2: Create ProductRepository (php-developer, 5k tokens)
+ ├── Task 3: Create ProductService (php-developer, 8k tokens)
+ ├── Task 4: Create ProductController with index/show (php-developer, 5k tokens)
+ ├── Task 5: Create ProductController with store/update/delete (php-developer, 5k tokens)
+ ├── Task 6: Create ProductStoreRequest validation (php-developer, 3k tokens)
+ ├── Task 7: Create ProductResource transformer (php-developer, 3k tokens)
+ ├── Task 8: Create Product API routes (php-developer, 2k tokens)
+ ├── Task 9: Write tests for ProductService (sdet-engineer, 8k tokens)
+ ├── Task 10: Review all Product code (code-skeptic, 5k tokens)
+```
+
+Each task is independent, verifiable, and within token budget.
+
+### 5. Skip Unnecessary Steps
+
+If a task doesn't need design or research, skip those phases:
+
+```
+❌ BAD: Running full pipeline for a config change
+ (requirement-refiner → history-miner → system-analyst → sdet → lead-dev → review)
+
+✅ GOOD: Direct implementation for a config change
+ (lead-developer → code-skeptic)
+```
+
+### 6. Reuse Existing Code
+
+Before writing anything:
+1. Search for existing implementations
+2. Check if a similar pattern already exists
+3. Use existing utilities and helpers
+4. Don't reinvent what's already there
+
+### 7. Verification After Each Task
+
+After each atomic task:
+1. Run relevant tests
+2. Check lint/format
+3. Log execution to `.kilo/logs/agent-executions.jsonl`
+4. Post Gitea comment with results
+5. Only then delegate to next agent
+
+## Anti-Patterns to Avoid
+
+### Kitchen Sink Invocations
+```
+❌ Task: "Build the entire admin panel"
+ → Agent tries to do everything, hangs, wastes tokens, produces incomplete work
+
+✅ Tasks:
+ 1. "Create AdminDashboardController with stats endpoint"
+ 2. "Create AdminProductIndexController with list/search endpoint"
+ 3. "Create AdminProductFormController with create/edit endpoints"
+```
+
+### Over-Contexting
+```
+❌ Including entire file contents when only a few lines are relevant
+✅ Including only the function that needs to change and its interface
+```
+
+### Multiple Responsibilities
+```
+❌ One agent doing both backend AND frontend
+✅ Separate atomic tasks: backend-developer for API, frontend-developer for UI
+```
+
+## Task Routing Matrix
+
+| Task Type | Agent | Typical Tokens |
+|-----------|-------|---------------|
+| Create model + migration | php-developer | 3-5k |
+| Create API endpoint | php-developer | 5-8k |
+| Create service method | php-developer | 3-5k |
+| Create Vue component | frontend-developer | 5-8k |
+| Write test for one function | sdet-engineer | 3-5k |
+| Review code changes | code-skeptic | 3-8k |
+| Fix specific bug | the-fixer | 3-5k |
+| Security audit | security-auditor | 5-10k |
+| Performance review | performance-engineer | 5-8k |
+| Create Docker config | devops-engineer | 3-5k |
+| Create Gitea issue | orchestrator | 1-2k |
+
+## Monitoring Token Usage
+
+Check `.kilo/logs/agent-executions.jsonl` for token usage patterns:
+
+```bash
+# Find most expensive agent invocations
+cat .kilo/logs/agent-executions.jsonl | sort -t'"tokens_used":' -k2 -rn | head -10
+
+# Find failed tasks (tokens wasted)
+cat .kilo/logs/agent-executions.jsonl | grep '"status":"fail"'
+```
+
+## Checklist
+
+- [ ] Each task has exactly ONE atomic deliverable
+- [ ] Task description specifies exact files and changes
+- [ ] No agent tries to do more than its assigned task
+- [ ] Token budget is respected per task type
+- [ ] Verification happens after each atomic task
+- [ ] Unnecessary pipeline steps are skipped
+- [ ] Existing code is reused, not rewritten
+- [ ] Execution is logged for monitoring
\ No newline at end of file
diff --git a/.kilo/shared/gitea-api.md b/.kilo/shared/gitea-api.md
index 8d14752..6f1e3e4 100644
--- a/.kilo/shared/gitea-api.md
+++ b/.kilo/shared/gitea-api.md
@@ -2,13 +2,41 @@
Common Gitea API functions for issue comments, checkbox updates, and issue management.
+## IMPORTANT: Target Project Resolution
+
+**NEVER hardcode `UniqueSoft/APAW` in API calls.** Always detect the target project from git remote.
+
+### How to Detect Target Project
+
+```python
+import re, subprocess
+
+def get_target_repo():
+ """Detect target project from git remote - NEVER hardcode"""
+ result = subprocess.run(
+ ['git', 'remote', 'get-url', 'origin'],
+ capture_output=True, text=True
+ )
+ remote_url = result.stdout.strip()
+
+ # HTTPS: https://git.softuniq.eu/Owner/Repo.git
+ # SSH: git@git.softuniq.eu:Owner/Repo.git
+ match = re.search(r'[:/]([^/]+/[^/]+?)(?:\.git)?$', remote_url)
+ if match:
+ return match.group(1)
+
+ # Fallback: use env var or default
+ return os.environ.get('GITEA_TARGET_REPO', 'UniqueSoft/APAW')
+```
+
## Python Client
```python
-import urllib.request, json, base64, os
+import urllib.request, json, base64, os, re, subprocess
-def gitea_api(path, data=None, method='GET'):
- """Call Gitea API. Auto-creates token if GITEA_TOKEN missing."""
+def gitea_api(path, data=None, method='GET', repo=None):
+ """Call Gitea API. Auto-creates token if GITEA_TOKEN missing. Auto-detects target repo."""
+ target_repo = repo or get_target_repo()
token = os.environ.get('GITEA_TOKEN', '')
if not token:
cred = base64.b64encode(b"NW:eshkink0t").decode()
@@ -18,36 +46,75 @@ def gitea_api(path, data=None, method='GET'):
headers={'Content-Type': 'application/json', 'Authorization': f'Basic {cred}'},
method='POST')
with urllib.request.urlopen(req) as r: token = json.loads(r.read())['sha1']
- url = f"https://git.softuniq.eu/api/v1/repos/UniqueSoft/APAW{path}"
+ url = f"https://git.softuniq.eu/api/v1/repos/{target_repo}{path}"
headers = {'Content-Type': 'application/json', 'Authorization': f'token {token}'}
req = urllib.request.Request(url, data=json.dumps(data).encode() if data else None,
headers=headers, method=method)
with urllib.request.urlopen(req) as r: return json.loads(r.read())
-def post_gitea_comment(issue_number, body):
- """Post comment to Gitea issue."""
- return gitea_api(f"/issues/{issue_number}/comments", {"body": body}, 'POST')
+def get_target_repo():
+ """Detect target project from git remote - NEVER hardcode"""
+ try:
+ result = subprocess.run(
+ ['git', 'remote', 'get-url', 'origin'],
+ capture_output=True, text=True
+ )
+ remote_url = result.stdout.strip()
+ match = re.search(r'[:/]([^/]+/[^/]+?)(?:\.git)?$', remote_url)
+ if match:
+ return match.group(1)
+ except Exception:
+ pass
+ return os.environ.get('GITEA_TARGET_REPO', 'UniqueSoft/APAW')
-def update_issue_checkboxes(issue_number):
+def post_gitea_comment(issue_number, body, repo=None):
+ """Post comment to Gitea issue in the correct project."""
+ target_repo = repo or get_target_repo()
+ return gitea_api(f"/issues/{issue_number}/comments", {"body": body}, 'POST', target_repo)
+
+def update_issue_checkboxes(issue_number, repo=None):
"""Mark all checkboxes as done and close issue."""
+ target_repo = repo or get_target_repo()
import re
- issue = gitea_api(f"/issues/{issue_number}")
+ issue = gitea_api(f"/issues/{issue_number}", repo=target_repo)
body = issue['body']
body = re.sub(r'- \[ \] ', '- [x] ', body)
body = re.sub(r'\* \[ \] ', '* [x] ', body)
- gitea_api(f"/issues/{issue_number}", {"body": body, "state": "closed"}, 'PATCH')
+ gitea_api(f"/issues/{issue_number}", {"body": body, "state": "closed"}, 'PATCH', target_repo)
-def close_issue(issue_number):
- """Close a Gitea issue."""
- gitea_api(f"/issues/{issue_number}", {"state": "closed"}, 'PATCH')
+def close_issue(issue_number, repo=None):
+ """Close a Gitea issue in the correct project."""
+ target_repo = repo or get_target_repo()
+ gitea_api(f"/issues/{issue_number}", {"state": "closed"}, 'PATCH', target_repo)
+
+def create_issue(title, body, labels=None, repo=None):
+ """Create a Gitea issue in the correct project."""
+ target_repo = repo or get_target_repo()
+ return gitea_api("/issues", {"title": title, "body": body, "labels": labels or []}, 'POST', target_repo)
```
## Bash Client
```bash
+# Auto-detect target repo
+TARGET_REPO=$(git remote get-url origin | sed -E 's|.*[:/]([^/]+/[^/]+?)(\.git)?$|\1|')
+
# Post comment
curl -X POST -H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"body":"comment body"}' \
- "https://git.softuniq.eu/api/v1/repos/UniqueSoft/APAW/issues/{issue_number}/comments"
+ "https://git.softuniq.eu/api/v1/repos/${TARGET_REPO}/issues/{issue_number}/comments"
+
+# Create issue
+curl -X POST -H "Authorization: token ${GITEA_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d '{"title":"Issue title","body":"Issue body"}' \
+ "https://git.softuniq.eu/api/v1/repos/${TARGET_REPO}/issues"
```
+
+## CRITICAL REMINDERS
+
+1. **NEVER hardcode `UniqueSoft/APAW`** - always use `get_target_repo()`
+2. **Issues belong in the target project** - the project being worked on
+3. **APAW is the agent framework** - not the default target for all issues
+4. **Use `GITEA_TARGET_REPO` env var** for explicit override when needed
\ No newline at end of file
diff --git a/.kilo/skills/agent-logging/SKILL.md b/.kilo/skills/agent-logging/SKILL.md
new file mode 100644
index 0000000..55097af
--- /dev/null
+++ b/.kilo/skills/agent-logging/SKILL.md
@@ -0,0 +1,160 @@
+---
+name: agent-logging
+description: Agent execution logging and monitoring system - tracks which agent was called, when, duration, tokens, and results for every task
+---
+
+# Agent Execution Logging
+
+## Purpose
+
+Track every agent invocation: who was called, when, for what task, how long it took, how many tokens, and what was the result. This enables project-level monitoring of which agents and skills work and which don't.
+
+## Mandatory Logging
+
+**Every agent MUST log its execution.** This is not optional.
+
+## Log Format
+
+All logs go to `.kilo/logs/agent-executions.jsonl` (one JSON object per line):
+
+```jsonl
+{"ts":"2026-04-18T14:00:00Z","agent":"lead-developer","issue":42,"project":"UniqueSoft/my-shop","task":"Create Product model with migration","subtask_type":"model_creation","duration_ms":45000,"tokens_used":8500,"status":"success","files":["src/Models/Product.php","database/migrations/2026_04_18_create_products_table.php"],"score":8,"next_agent":"code-skeptic"}
+{"ts":"2026-04-18T14:02:00Z","agent":"code-skeptic","issue":42,"project":"UniqueSoft/my-shop","task":"Review Product model implementation","subtask_type":"review","duration_ms":25000,"tokens_used":5200,"status":"pass","files":[],"score":7,"issues_found":2,"next_agent":"performance-engineer"}
+{"ts":"2026-04-18T14:05:00Z","agent":"php-developer","issue":43,"project":"UniqueSoft/my-shop","task":"Add POST /api/products endpoint","subtask_type":"api_endpoint","duration_ms":60000,"tokens_used":12000,"status":"success","files":["app/Http/Controllers/ProductController.php","app/Http/Requests/ProductStoreRequest.php"],"score":9,"next_agent":"code-skeptic"}
+```
+
+## Required Fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `ts` | ISO 8601 | Timestamp of execution start |
+| `agent` | string | Agent name (e.g. `lead-developer`, `php-developer`) |
+| `issue` | number | Gitea issue number |
+| `project` | string | Target project repo (e.g. `UniqueSoft/my-shop`) |
+| `task` | string | Atomic task description |
+| `subtask_type` | string | Type: `model_creation`, `api_endpoint`, `service_method`, `ui_component`, `bug_fix`, `review`, `test`, `config_change` |
+| `duration_ms` | number | Execution time in milliseconds |
+| `tokens_used` | number | Approximate tokens consumed |
+| `status` | string | `success`, `fail`, `pass`, `blocked`, `partial` |
+| `files` | array | Files created or modified |
+| `score` | number | Self-assessment 1-10 |
+| `next_agent` | string | Which agent is delegated to next |
+
+## Log Command
+
+```python
+import json, os, time
+from datetime import datetime, timezone
+
+def log_agent_execution(agent, issue, task, subtask_type,
+ duration_ms, tokens_used, status,
+ files=None, score=None, next_agent=None,
+ project=None):
+ """Log agent execution to JSONL file."""
+ if project is None:
+ project = get_target_repo() # From gitea-api.md
+
+ entry = {
+ "ts": datetime.now(timezone.utc).isoformat(),
+ "agent": agent,
+ "issue": issue,
+ "project": project,
+ "task": task,
+ "subtask_type": subtask_type,
+ "duration_ms": duration_ms,
+ "tokens_used": tokens_used,
+ "status": status,
+ "files": files or [],
+ "score": score,
+ "next_agent": next_agent,
+ }
+
+ log_dir = ".kilo/logs"
+ os.makedirs(log_dir, exist_ok=True)
+ log_file = os.path.join(log_dir, "agent-executions.jsonl")
+
+ with open(log_file, 'a', encoding='utf-8') as f:
+ f.write(json.dumps(entry, ensure_ascii=False) + '\n')
+
+ return entry
+
+
+# Usage in agent code:
+start_time = time.time()
+
+# ... do work ...
+
+duration = int((time.time() - start_time) * 1000)
+
+log_agent_execution(
+ agent="php-developer",
+ issue=42,
+ task="Create Product model with migration",
+ subtask_type="model_creation",
+ duration_ms=duration,
+ tokens_used=8500,
+ status="success",
+ files=["app/Models/Product.php", "database/migrations/2026_04_18_create_products_table.php"],
+ score=8,
+ next_agent="code-skeptic",
+ project="UniqueSoft/my-shop"
+)
+```
+
+## Aggregation Script
+
+```bash
+# Quick stats from log
+bun run .kilo/scripts/agent-stats.ts
+
+# Output:
+# Agent Stats (Last 30 days)
+# ===========================
+# lead-developer: 12 calls, avg 45s, avg score 8.2, 95% success
+# php-developer: 8 calls, avg 55s, avg score 7.8, 87% success
+# code-skeptic: 15 calls, avg 20s, avg score 7.5, 93% pass
+# the-fixer: 3 calls, avg 30s, avg score 6.5, 67% success
+```
+
+## Integration with Gitea Comments
+
+Every Gitea comment MUST include duration and token estimate:
+
+```markdown
+## ✅ php-developer completed
+
+**Task**: Create Product model with migration
+**Issue**: #42
+**Project**: UniqueSoft/my-shop
+**Files**: app/Models/Product.php, database/migrations/2026_04_18_create_products_table.php
+**Duration**: 45s
+**Tokens**: ~8,500
+**Score**: 8/10
+
+### Changes Made
+- Created Product Eloquent model withfillable fields
+- Created migration for products table with indexes
+
+**Next**: @code-skeptic
+```
+
+## Monitoring Dashboard
+
+Log data feeds into the agent evolution dashboard:
+
+- **Agent utilization**: Which agents are called most
+- **Success rate**: Which agents succeed vs fail
+- **Duration trends**: Are agents getting faster or slower
+- **Token efficiency**: Cost per task by agent
+- **Project breakdown**: Which agents are used for which projects
+
+## Checklist
+
+- [ ] Every agent logs execution to `.kilo/logs/agent-executions.jsonl`
+- [ ] Log includes correct project (not hardcoded APAW)
+- [ ] Duration is measured and logged
+- [ ] Token estimate is included
+- [ ] Status is accurate (success/fail/pass/blocked)
+- [ ] Files list is complete
+- [ ] Score is self-assessed honestly
+- [ ] Gitea comment includes duration and tokens
\ No newline at end of file
diff --git a/.kilo/skills/gitea-commenting/SKILL.md b/.kilo/skills/gitea-commenting/SKILL.md
index 8d4fadd..a06da95 100644
--- a/.kilo/skills/gitea-commenting/SKILL.md
+++ b/.kilo/skills/gitea-commenting/SKILL.md
@@ -62,12 +62,15 @@ Please respond with your choice.
## API Usage
```bash
+# Auto-detect target repo
+TARGET_REPO=$(git remote get-url origin | sed -E 's|.*[:/]([^/]+/[^/]+?)(\.git)?$|\1|')
+
# Using curl with GITEA_TOKEN
curl -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"body":"## ✅ lead-developer completed\n\n..."}' \
- "https://git.softuniq.eu/api/v1/repos/UniqueSoft/APAW/issues/{issue_number}/comments"
+ "https://git.softuniq.eu/api/v1/repos/${TARGET_REPO}/issues/{issue_number}/comments"
```
## Python Example
@@ -77,8 +80,26 @@ import urllib.request
import json
import base64
import os
+import re
+import subprocess
-def post_comment(issue_number: int, body: str):
+def get_target_repo():
+ """Detect target project from git remote - NEVER hardcode"""
+ try:
+ result = subprocess.run(
+ ['git', 'remote', 'get-url', 'origin'],
+ capture_output=True, text=True
+ )
+ remote_url = result.stdout.strip()
+ match = re.search(r'[:/]([^/]+/[^/]+?)(?:\.git)?$', remote_url)
+ if match:
+ return match.group(1)
+ except Exception:
+ pass
+ return os.environ.get('GITEA_TARGET_REPO', 'UniqueSoft/APAW')
+
+def post_comment(issue_number: int, body: str, repo: str = None):
+ target_repo = repo or get_target_repo()
token = os.environ.get('GITEA_TOKEN', '')
# If no token, create one from credentials
@@ -88,7 +109,7 @@ def post_comment(issue_number: int, body: str):
credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
# Create token first...
- url = f"https://git.softuniq.eu/api/v1/repos/UniqueSoft/APAW/issues/{issue_number}/comments"
+ url = f"https://git.softuniq.eu/api/v1/repos/{target_repo}/issues/{issue_number}/comments"
data = json.dumps({"body": body}).encode('utf-8')
req = urllib.request.Request(
@@ -183,9 +204,19 @@ All agents must check for GITEA_TOKEN environment variable or create one using c
```python
import urllib.request, json, base64, os
-def upload_screenshot(issue_number, screenshot_path, description="Error screenshot"):
+def upload_screenshot(issue_number, screenshot_path, description="Error screenshot", repo=None):
"""Upload screenshot to Gitea issue and post comment"""
+ # Detect target repo
+ import subprocess, re
+ if repo is None:
+ try:
+ result = subprocess.run(['git', 'remote', 'get-url', 'origin'], capture_output=True, text=True)
+ match = re.search(r'[:/]([^/]+/[^/]+?)(?:\.git)?$', result.stdout.strip())
+ repo = match.group(1) if match else os.environ.get('GITEA_TARGET_REPO', 'UniqueSoft/APAW')
+ except Exception:
+ repo = os.environ.get('GITEA_TARGET_REPO', 'UniqueSoft/APAW')
+
# Get token
username = "NW"
password = "eshkink0t" # with zero
@@ -215,7 +246,7 @@ def upload_screenshot(issue_number, screenshot_path, description="Error screensh
body += f'\r\n--{boundary}--\r\n'.encode()
req = urllib.request.Request(
- f"https://git.softuniq.eu/api/v1/repos/UniqueSoft/APAW/issues/{issue_number}/assets",
+ f"https://git.softuniq.eu/api/v1/repos/{repo}/issues/{issue_number}/assets",
data=body,
headers={
'Content-Type': f'multipart/form-data; boundary={boundary}',
@@ -239,7 +270,7 @@ def upload_screenshot(issue_number, screenshot_path, description="Error screensh
"""
req = urllib.request.Request(
- f"https://git.softuniq.eu/api/v1/repos/UniqueSoft/APAW/issues/{issue_number}/comments",
+ f"https://git.softuniq.eu/api/v1/repos/{repo}/issues/{issue_number}/comments",
data=json.dumps({"body": comment_body}).encode(),
headers={'Content-Type': 'application/json', 'Authorization': f'token {token}'},
method='POST'
diff --git a/.kilo/skills/gitea-workflow/SKILL.md b/.kilo/skills/gitea-workflow/SKILL.md
index 5bb342a..1fa9e68 100644
--- a/.kilo/skills/gitea-workflow/SKILL.md
+++ b/.kilo/skills/gitea-workflow/SKILL.md
@@ -19,9 +19,26 @@ GITEA_USER = os.environ.get('GITEA_USER', 'NW')
GITEA_PASS = os.environ.get('GITEA_PASS', 'eshkink0t')
class GiteaClient:
- def __init__(self):
+ def __init__(self, repo=None):
+ self.repo = repo or self._detect_repo()
self.token = self._get_token()
+ def _detect_repo(self):
+ """Detect target project from git remote - NEVER hardcode"""
+ import subprocess, re, os
+ try:
+ result = subprocess.run(
+ ['git', 'remote', 'get-url', 'origin'],
+ capture_output=True, text=True
+ )
+ remote_url = result.stdout.strip()
+ match = re.search(r'[:/]([^/]+/[^/]+?)(?:\.git)?$', remote_url)
+ if match:
+ return match.group(1)
+ except Exception:
+ pass
+ return os.environ.get('GITEA_TARGET_REPO', 'UniqueSoft/APAW')
+
def _get_token(self):
"""Get or create API token"""
credentials = base64.b64encode(f"{GITEA_USER}:{GITEA_PASS}".encode()).decode()
@@ -34,9 +51,9 @@ class GiteaClient:
with urllib.request.urlopen(req) as r:
return json.loads(r.read())['sha1']
- def create_issue(self, repo, title, body, labels):
+ def create_issue(self, title, body, labels):
"""Create workflow issue"""
- url = f"{GITEA_URL}/api/v1/repos/{repo}/issues"
+ url = f"{GITEA_URL}/api/v1/repos/{self.repo}/issues"
req = urllib.request.Request(
url,
data=json.dumps({
@@ -53,9 +70,9 @@ class GiteaClient:
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
- def post_comment(self, repo, issue_number, body):
+ def post_comment(self, issue_number, body):
"""Post progress comment"""
- url = f"{GITEA_URL}/api/v1/repos/{repo}/issues/{issue_number}/comments"
+ url = f"{GITEA_URL}/api/v1/repos/{self.repo}/issues/{issue_number}/comments"
req = urllib.request.Request(
url,
data=json.dumps({"body": body}).encode(),
@@ -68,9 +85,9 @@ class GiteaClient:
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
- def add_label(self, repo, issue_number, label):
+ def add_label(self, issue_number, label):
"""Add label to issue"""
- url = f"{GITEA_URL}/api/v1/repos/{repo}/issues/{issue_number}/labels"
+ url = f"{GITEA_URL}/api/v1/repos/{self.repo}/issues/{issue_number}/labels"
req = urllib.request.Request(
url,
data=json.dumps({"labels": [label]}).encode(),
@@ -83,10 +100,44 @@ class GiteaClient:
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
- def close_issue(self, repo, issue_number, comment):
+ def get_issue(self, issue_number):
+ """Get issue details"""
+ url = f"{GITEA_URL}/api/v1/repos/{self.repo}/issues/{issue_number}"
+ req = urllib.request.Request(
+ url,
+ headers={
+ 'Content-Type': 'application/json',
+ 'Authorization': f'token {self.token}'
+ },
+ method='GET'
+ )
+ with urllib.request.urlopen(req) as r:
+ return json.loads(r.read())
+
+ def update_issue(self, issue_number, body=None, state=None):
+ """Update issue body and/or state"""
+ data = {}
+ if body is not None:
+ data['body'] = body
+ if state is not None:
+ data['state'] = state
+ url = f"{GITEA_URL}/api/v1/repos/{self.repo}/issues/{issue_number}"
+ req = urllib.request.Request(
+ url,
+ data=json.dumps(data).encode() if data else None,
+ headers={
+ 'Content-Type': 'application/json',
+ 'Authorization': f'token {self.token}'
+ },
+ method='PATCH'
+ )
+ with urllib.request.urlopen(req) as r:
+ return json.loads(r.read())
+
+ def close_issue(self, issue_number, comment):
"""Close issue with final comment"""
- self.post_comment(repo, issue_number, comment)
- url = f"{GITEA_URL}/api/v1/repos/{repo}/issues/{issue_number}"
+ self.post_comment(issue_number, comment)
+ url = f"{GITEA_URL}/api/v1/repos/{self.repo}/issues/{issue_number}"
req = urllib.request.Request(
url,
data=json.dumps({"state": "closed"}).encode(),
@@ -100,13 +151,13 @@ class GiteaClient:
return json.loads(r.read())
-def create_workflow_issue(workflow_type, project_name, issue_number=None):
+def create_workflow_issue(workflow_type, project_name, issue_number=None, repo=None):
"""Create Gitea issue for workflow tracking - MANDATORY FIRST STEP"""
if issue_number:
return issue_number
- client = GiteaClient()
+ client = GiteaClient(repo=repo)
title = f"[{workflow_type}] {project_name}"
@@ -164,7 +215,6 @@ def create_workflow_issue(workflow_type, project_name, issue_number=None):
"""
issue = client.create_issue(
- repo="UniqueSoft/APAW",
title=title,
body=body,
labels=["workflow", workflow_type, "status: new"]
@@ -194,7 +244,7 @@ def comment_start(issue_number, step_name, step_number, agent, files=None):
body += "\n---\n*Progress comment will be updated upon completion.*"
client = GiteaClient()
- client.post_comment("UniqueSoft/APAW", issue_number, body)
+ client.post_comment(issue_number, body)
```
### Step Success
@@ -233,10 +283,10 @@ def comment_success(issue_number, step_name, step_number, result):
"""
client = GiteaClient()
- client.post_comment("UniqueSoft/APAW", issue_number, body)
+ client.post_comment(issue_number, body)
# Update labels
- client.add_label("UniqueSoft/APAW", issue_number, f"step: {step_number}")
+ client.add_label(issue_number, f"step: {step_number}")
```
### Step Error
@@ -277,8 +327,8 @@ Reply with "retry" to re-run this step after fixing.
"""
client = GiteaClient()
- client.post_comment("UniqueSoft/APAW", issue_number, body)
- client.add_label("UniqueSoft/APAW", issue_number, "status: blocked")
+ client.post_comment(issue_number, body)
+ client.add_label(issue_number, "status: blocked")
```
### Final Delivery
@@ -299,7 +349,7 @@ def comment_delivery(issue_number, project_name, workflow_type, checks):
## 📦 Delivery Package
### Source Code
-- **Repository**: UniqueSoft/APAW
+- **Repository**: {checks.get('repository', 'project-repo')}
- **Branch**: main
- **Commit**: `{checks['commit_hash']}`
@@ -332,7 +382,7 @@ def comment_delivery(issue_number, project_name, workflow_type, checks):
```bash
# Clone and run
-git clone https://git.softuniq.eu/UniqueSoft/APAW.git
+git clone https://git.softuniq.eu/{checks.get('repository', 'project-repo')}.git
cd {project_name}
docker-compose up -d
@@ -361,8 +411,8 @@ docker-compose up -d
"""
client = GiteaClient()
- client.post_comment("UniqueSoft/APAW", issue_number, body)
- client.close_issue("UniqueSoft/APAW", issue_number, "Workflow completed successfully")
+ client.post_comment(issue_number, body)
+ client.close_issue(issue_number, "Workflow completed successfully")
```
## Progress Tracking
@@ -372,7 +422,7 @@ def update_progress_table(issue_number, step_number, step_name, status):
"""Update progress table in issue body"""
# Get current issue
client = GiteaClient()
- issue = client.get_issue("UniqueSoft/APAW", issue_number)
+ issue = client.get_issue(issue_number)
# Parse and update progress table
lines = issue['body'].split('\n')
@@ -390,7 +440,7 @@ def update_progress_table(issue_number, step_number, step_name, status):
new_lines.append(line)
# Update issue
- client.update_issue("UniqueSoft/APAW", issue_number, '\n'.join(new_lines))
+ client.update_issue(issue_number, '\n'.join(new_lines))
```
## Quality Gate Validation
diff --git a/.kilo/skills/gitea/SKILL.md b/.kilo/skills/gitea/SKILL.md
index 72a0416..caacf8f 100644
--- a/.kilo/skills/gitea/SKILL.md
+++ b/.kilo/skills/gitea/SKILL.md
@@ -113,7 +113,17 @@ If Gitea token is available:
```
### Gitea Repository URL
-Project URL: `https://git.softuniq.eu/UniqueSoft/APAW`
+
+**CRITICAL: Always detect the target project from git remote, never hardcode APAW.**
+
+```bash
+# Auto-detect target repo
+TARGET_REPO=$(git remote get-url origin | sed -E 's|.*[:/]([^/]+/[^/]+?)(\.git)?$|\1|')
+```
+
+Project URL: `https://git.softuniq.eu/{TARGET_REPO}`
+
+**Rule**: Create issues in the TARGET project repository, NOT in APAW (unless APAW is the project being worked on).
### Password Safety
If plain password is required:
diff --git a/.kilo/skills/php-laravel-patterns/SKILL.md b/.kilo/skills/php-laravel-patterns/SKILL.md
new file mode 100644
index 0000000..8ea110e
--- /dev/null
+++ b/.kilo/skills/php-laravel-patterns/SKILL.md
@@ -0,0 +1,403 @@
+---
+name: php-laravel-patterns
+description: Laravel framework patterns - routing, Eloquent ORM, middleware, queues, events, service container
+---
+
+# PHP Laravel Patterns
+
+## Project Structure
+
+```
+app/
+├── Http/
+│ ├── Controllers/ # Thin controllers, delegate to services
+│ ├── Middleware/ # Auth, CORS, throttle, custom
+│ ├── Requests/ # Form validation (not in controller)
+│ └── Resources/ # JSON API resources (transformers)
+├── Models/ # Eloquent models with scopes
+├── Services/ # Business logic (fat services)
+├── Repositories/ # Data access abstraction
+├── Events/ # Event classes
+├── Listeners/ # Event handlers
+├── Jobs/ # Queued jobs
+├── Policies/ # Authorization gates
+├── Rules/ # Custom validation rules
+└── Exceptions/ # Custom exception hierarchy
+database/
+├── migrations/ # Schema changes
+├── seeders/ # Dev/test data
+└── factories/ # Model factories
+routes/
+├── api.php # API routes (versioned)
+├── web.php # Web routes
+└── console.php # Artisan commands
+config/
+└── *.php # Environment-specific configs
+```
+
+## Routing Patterns
+
+```php
+// routes/api.php - Versioned API routes
+Route::prefix('v1')->group(function () {
+ Route::apiResource('products', ProductController::class);
+ Route::apiResource('orders', OrderController::class);
+
+ Route::middleware('auth:sanctum')->group(function () {
+ Route::get('/me', [UserController::class, 'me']);
+ Route::post('/orders', [OrderController::class, 'store']);
+ });
+});
+```
+
+## Controller Pattern (Thin)
+
+```php
+// app/Http/Controllers/ProductController.php
+class ProductController extends Controller
+{
+ public function __construct(
+ private ProductService $productService
+ ) {}
+
+ public function index(ProductIndexRequest $request): ProductCollection
+ {
+ return new ProductCollection(
+ $this->productService->list($request->validated())
+ );
+ }
+
+ public function store(ProductStoreRequest $request): JsonResponse
+ {
+ $product = $this->productService->create($request->validated());
+ return response()->json($product, 201);
+ }
+
+ public function show(Product $product): ProductResource
+ {
+ return new ProductResource(
+ $product->load(['category', 'variants', 'images'])
+ );
+ }
+}
+```
+
+## Service Pattern (Fat Services, Thin Controllers)
+
+```php
+// app/Services/ProductService.php
+class ProductService
+{
+ public function __construct(
+ private ProductRepository $repository,
+ private ImageService $imageService,
+ private CacheManager $cache
+ ) {}
+
+ public function list(array $filters): LengthAwarePaginator
+ {
+ $cacheKey = 'products:' . md5(json_encode($filters));
+
+ return $this->cache->remember($cacheKey, 3600, fn() =>
+ $this->repository->list($filters)
+ );
+ }
+
+ public function create(array $data): Product
+ {
+ if (isset($data['image'])) {
+ $data['image_path'] = $this->imageService->upload($data['image']);
+ }
+
+ $product = $this->repository->create($data);
+ ProductCreated::dispatch($product);
+
+ return $product;
+ }
+}
+```
+
+## Repository Pattern
+
+```php
+// app/Repositories/ProductRepository.php
+class ProductRepository
+{
+ public function __construct(private Product $model) {}
+
+ public function list(array $filters): LengthAwarePaginator
+ {
+ $query = $this->model->query()->with(['category', 'variants']);
+
+ if (isset($filters['category_id'])) {
+ $query->where('category_id', $filters['category_id']);
+ }
+
+ if (isset($filters['search'])) {
+ $query->where(fn($q) =>
+ $q->where('name', 'like', "%{$filters['search']}%")
+ ->orWhere('description', 'like', "%{$filters['search']}%")
+ );
+ }
+
+ if (isset($filters['min_price'])) {
+ $query->where('price', '>=', $filters['min_price']);
+ }
+
+ return $query->orderBy($filters['sort'] ?? 'created_at', 'desc')
+ ->paginate($filters['per_page'] ?? 20);
+ }
+
+ public function create(array $data): Product
+ {
+ return $this->model->create($data);
+ }
+}
+```
+
+## Eloquent Model Patterns
+
+```php
+// app/Models/Product.php
+class Product extends Model
+{
+ use HasFactory, SoftDeletes;
+
+ protected $fillable = ['name', 'slug', 'description', 'price', 'category_id'];
+ protected $casts = ['price' => 'decimal:2', 'is_active' => 'boolean'];
+
+ // Scopes
+ public function scopeActive(Builder $query): Builder
+ {
+ return $query->where('is_active', true);
+ }
+
+ public function scopeInStock(Builder $query): Builder
+ {
+ return $query->where('stock', '>', 0);
+ }
+
+ // Relationships
+ public function category(): BelongsTo { return $this->belongsTo(Category::class); }
+ public function variants(): HasMany { return $this->hasMany(ProductVariant::class); }
+ public function images(): MorphMany { return $this->morphMany(Image::class, 'imageable'); }
+
+ // Accessors
+ public function getFormattedPriceAttribute(): string
+ {
+ return number_format($this->price / 100, 2);
+ }
+}
+```
+
+## Form Request Validation
+
+```php
+// app/Http/Requests/ProductStoreRequest.php
+class ProductStoreRequest extends FormRequest
+{
+ public function rules(): array
+ {
+ return [
+ 'name' => ['required', 'string', 'max:255'],
+ 'price' => ['required', 'numeric', 'min:0'],
+ 'category_id' => ['required', 'exists:categories,id'],
+ 'description' => ['nullable', 'string'],
+ 'image' => ['nullable', 'image', 'max:2048'],
+ 'variants' => ['nullable', 'array'],
+ 'variants.*.name' => ['required_with:variants', 'string'],
+ 'variants.*.price_adjustment' => ['nullable', 'numeric'],
+ ];
+ }
+}
+```
+
+## API Resource (Transformer)
+
+```php
+// app/Http/Resources/ProductResource.php
+class ProductResource extends JsonResource
+{
+ public function toArray(Request $request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'slug' => $this->slug,
+ 'price' => $this->price,
+ 'formatted_price' => $this->formatted_price,
+ 'category' => new CategoryResource($this->whenLoaded('category')),
+ 'variants' => ProductVariantResource::collection($this->whenLoaded('variants')),
+ 'created_at' => $this->created_at->toISOString(),
+ ];
+ }
+}
+```
+
+## Middleware
+
+```php
+// app/Http/Middleware/EnsureJsonResponse.php
+class EnsureJsonResponse
+{
+ public function handle(Request $request, Closure $next): Response
+ {
+ $request->headers->set('Accept', 'application/json');
+ return $next($request);
+ }
+}
+
+// app/Http/Middleware/RateLimitByUser.php
+class RateLimitByUser
+{
+ public function handle(Request $request, Closure $next, int $maxAttempts = 60): Response
+ {
+ $key = 'rate_limit:' . ($request->user()?->id ?? $request->ip());
+ if (RateLimiter::tooManyAttempts($key, $maxAttempts)) {
+ return response()->json(['message' => 'Too many requests'], 429);
+ }
+ RateLimiter::hit($key);
+ return $next($request);
+ }
+}
+```
+
+## Authentication (Sanctum)
+
+```php
+// app/Http/Controllers/AuthController.php
+class AuthController extends Controller
+{
+ public function register(RegisterRequest $request): JsonResponse
+ {
+ $user = User::create($request->validated());
+ $token = $user->createToken('api-token')->plainTextToken;
+
+ return response()->json([
+ 'user' => new UserResource($user),
+ 'token' => $token,
+ ], 201);
+ }
+
+ public function login(LoginRequest $request): JsonResponse
+ {
+ if (!Auth::attempt($request->validated())) {
+ throw new AuthenticationException('Invalid credentials');
+ }
+
+ $user = Auth::user();
+ $token = $user->createToken('api-token')->plainTextToken;
+
+ return response()->json([
+ 'user' => new UserResource($user),
+ 'token' => $token,
+ ]);
+ }
+
+ public function logout(Request $request): JsonResponse
+ {
+ $request->user()->currentAccessToken()->delete();
+ return response()->json(['message' => 'Logged out']);
+ }
+}
+```
+
+## Events & Listeners
+
+```php
+// app/Events/OrderPlaced.php
+class OrderPlaced
+{
+ use Dispatchable, InteractsWithSockets;
+
+ public function __construct(public Order $order) {}
+}
+
+// app/Listeners/SendOrderConfirmation.php
+class SendOrderConfirmation
+{
+ public function handle(OrderPlaced $event): void
+ {
+ Mail::to($event->order->user)->send(new OrderConfirmation($event->order));
+ }
+}
+
+// app/Providers/EventServiceProvider.php
+protected $listen = [
+ OrderPlaced::class => [
+ SendOrderConfirmation::class,
+ UpdateInventory::class,
+ SendAdminNotification::class,
+ ],
+];
+```
+
+## Queued Jobs
+
+```php
+// app/Jobs/ProcessPayment.php
+class ProcessPayment implements ShouldQueue
+{
+ use Dispatchable, InteractsWithQueue, Queueable, Retryable;
+
+ public int $tries = 3;
+ public int $backoff = 60;
+
+ public function __construct(public Order $order) {}
+
+ public function handle(PaymentService $payment): void
+ {
+ $payment->process($this->order);
+ }
+
+ public function failed(Throwable $exception): void
+ {
+ $this->order->update(['payment_status' => 'failed']);
+ PaymentFailed::dispatch($this->order);
+ }
+}
+
+// Dispatch
+ProcessPayment::dispatch($order);
+```
+
+## Error Handling
+
+```php
+// app/Exceptions/Handler.php
+class Handler extends ExceptionHandler
+{
+ public function register(): void
+ {
+ $this->renderable(function (ValidationException $e) {
+ return response()->json([
+ 'message' => 'Validation failed',
+ 'errors' => $e->errors(),
+ ], 422);
+ });
+
+ $this->renderable(function (ModelNotFoundException $e) {
+ return response()->json(['message' => 'Resource not found'], 404);
+ });
+
+ $this->renderable(function (AuthenticationException $e) {
+ return response()->json(['message' => 'Unauthenticated'], 401);
+ });
+ }
+}
+```
+
+## Checklist
+
+- [ ] Thin controllers, fat services
+- [ ] Form Request classes for validation (not in controller)
+- [ ] API Resources for JSON transformation
+- [ ] Repository pattern for data access
+- [ ] Events + Listeners for side effects
+- [ ] Queued jobs for heavy operations
+- [ ] Eager loading to prevent N+1 (`with()`, `load()`)
+- [ ] Soft deletes on important models
+- [ ] CSRF protection on web routes
+- [ ] API token auth (Sanctum) on API routes
+- [ ] `composer audit` regularly
+- [ ] `phpcs --standard=PSR12` before commit
\ No newline at end of file
diff --git a/.kilo/skills/php-modular-architecture/SKILL.md b/.kilo/skills/php-modular-architecture/SKILL.md
new file mode 100644
index 0000000..1538a90
--- /dev/null
+++ b/.kilo/skills/php-modular-architecture/SKILL.md
@@ -0,0 +1,242 @@
+---
+name: php-modular-architecture
+description: PHP modular architecture patterns - separate modules, packages, service boundaries, microservice readiness
+---
+
+# PHP Modular Architecture
+
+## Core Principle
+
+**Every feature is an independent module. Never write giant monolithic files.**
+
+## Module Structure
+
+```
+src/
+├── Modules/
+│ ├── Product/
+│ │ ├── Controllers/
+│ │ │ └── ProductController.php
+│ │ ├── Services/
+│ │ │ ├── ProductService.php
+│ │ │ └── ProductSearchService.php
+│ │ ├── Repositories/
+│ │ │ └── ProductRepository.php
+│ │ ├── Models/
+│ │ │ └── Product.php
+│ │ ├── Requests/
+│ │ │ ├── ProductStoreRequest.php
+│ │ │ └── ProductUpdateRequest.php
+│ │ ├── Resources/
+│ │ │ └── ProductResource.php
+│ │ ├── Events/
+│ │ │ ├── ProductCreated.php
+│ │ │ └── ProductUpdated.php
+│ │ ├── Listeners/
+│ │ │ └── UpdateSearchIndex.php
+│ │ ├── Jobs/
+│ │ │ └── SyncProductToElasticsearch.php
+│ │ ├── Policies/
+│ │ │ └── ProductPolicy.php
+│ │ ├── Exceptions/
+│ │ │ └── ProductNotFoundException.php
+│ │ ├── Routes/
+│ │ │ └── api.php
+│ │ ├── Database/
+│ │ │ ├── migrations/
+│ │ │ └── seeders/
+│ │ ├── Tests/
+│ │ │ ├── Unit/
+│ │ │ └── Feature/
+│ │ └── ModuleServiceProvider.php
+│ ├── Order/
+│ │ ├── Controllers/
+│ │ ├── Services/
+│ │ ├── ... (same structure)
+│ │ └── ModuleServiceProvider.php
+│ └── User/
+│ └── ...
+├── Shared/
+│ ├── Enums/
+│ ├── Exceptions/
+│ ├── Helpers/
+│ ├── Traits/
+│ └── Interfaces/
+├── Support/
+│ ├── BaseRepository.php
+│ ├── BaseService.php
+│ └── BaseController.php
+└── Providers/
+ └── AppServiceProvider.php
+```
+
+## Module Service Provider
+
+```php
+// src/Modules/Product/ModuleServiceProvider.php
+namespace App\Modules\Product;
+
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\Route;
+
+class ModuleServiceProvider extends ServiceProvider
+{
+ public function boot(): void
+ {
+ Route::middleware('api')
+ ->prefix('api/v1')
+ ->group(module_path('Product', 'Routes/api.php'));
+
+ $this->loadMigrationsFrom(module_path('Product', 'Database/migrations'));
+
+ $this->app->bind(
+ \App\Modules\Product\Repositories\ProductRepositoryInterface::class,
+ \App\Modules\Product\Repositories\ProductRepository::class
+ );
+ }
+}
+```
+
+## Module Routes
+
+```php
+// src/Modules/Product/Routes/api.php
+use App\Modules\Product\Controllers\ProductController;
+
+Route::apiResource('products', ProductController::class);
+Route::get('products/search', [ProductController::class, 'search']);
+```
+
+## Interface Contracts (for testability & decoupling)
+
+```php
+// src/Modules/Product/Repositories/ProductRepositoryInterface.php
+interface ProductRepositoryInterface
+{
+ public function list(array $filters): LengthAwarePaginator;
+ public function find(int $id): Product;
+ public function create(array $data): Product;
+ public function update(int $id, array $data): Product;
+ public function delete(int $id): bool;
+}
+```
+
+## Shared Base Classes
+
+```php
+// src/Support/BaseRepository.php
+abstract class BaseRepository
+{
+ public function __construct(protected Model $model) {}
+
+ public function list(array $filters = []): LengthAwarePaginator
+ {
+ $query = $this->model->query();
+ $this->applyFilters($query, $filters);
+ return $query->paginate($filters['per_page'] ?? 20);
+ }
+
+ public function find(int $id): Model
+ {
+ return $this->model->findOrFail($id);
+ }
+
+ public function create(array $data): Model
+ {
+ return $this->model->create($data);
+ }
+
+ protected function applyFilters(Builder $query, array $filters): void
+ {
+ foreach ($filters as $key => $value) {
+ if (method_exists($this, "filter{$key}")) {
+ $this->{"filter{$key}"}($query, $value);
+ }
+ }
+ }
+}
+```
+
+## Module Boundaries
+
+```
+┌──────────────────────────────────────────────────────────┐
+│ Module Boundaries │
+├──────────────────────────────────────────────────────────┤
+│ │
+│ Product Module Order Module User Module │
+│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
+│ │ Controller │ │ Controller │ │Controller│ │
+│ │ Service │ │ Service │ │ Service │ │
+│ │ Repository │ │ Repository │ │Repository│ │
+│ │ Model │ │ Model │ │ Model │ │
+│ └──────┬───────┘ └──────┬───────┘ └────┬─────┘ │
+│ │ │ │ │
+│ │ ┌───────────────┼──────────────────┘ │
+│ │ │ Shared │ │
+│ │ │ ┌───────────┐ │ │
+│ │ │ │ Interfaces│ │ │
+│ │ │ │ Events │ │ │
+│ │ │ │ Helpers │ │ │
+│ │ │ └───────────┘ │ │
+│ │ └───────────────┘ │
+│ │ │
+│ ═══════╪══════════════════════════════════════════════ │
+│ RULES: │
+│ 1. Modules communicate via Interfaces ONLY │
+│ 2. Modules communicate via Events ONLY │
+│ 3. NEVER import a Model from another module │
+│ 4. NEVER import a Repository from another module │
+│ 5. Use events for cross-module communication │
+└──────────────────────────────────────────────────────────┘
+```
+
+## Cross-Module Communication via Events
+
+```php
+// Product Module dispatches event
+ProductCreated::dispatch($product);
+
+// Order Module listens (no direct dependency)
+class OrderModuleServiceProvider extends ServiceProvider
+{
+ protected $listen = [
+ \App\Modules\Product\Events\ProductCreated::class => [
+ \App\Modules\Order\Listeners\UpdateProductAvailability::class,
+ ],
+ ];
+}
+```
+
+## Microservice Readiness
+
+Each module should be extractable as an independent service:
+
+```yaml
+# docker-compose.yml - modular services
+services:
+ product-service:
+ build: ./src/Modules/Product
+ ports: ["8001:8000"]
+
+ order-service:
+ build: ./src/Modules/Order
+ ports: ["8002:8000"]
+
+ user-service:
+ build: ./src/Modules/User
+ ports: ["8003:8000"]
+```
+
+## Checklist
+
+- [ ] Each feature is an independent module
+- [ ] Modules communicate via interfaces, not direct imports
+- [ ] Cross-module communication via events only
+- [ ] Shared code in `Shared/` directory
+- [ ] Each module has its own tests
+- [ ] Each module can be extracted to microservice
+- [ ] No cross-module model imports
+- [ ] Module ServiceProvider registers routes, migrations, bindings
+- [ ] Files under 100 lines
+- [ ] Functions under 30 lines
\ No newline at end of file
diff --git a/.kilo/skills/php-security/SKILL.md b/.kilo/skills/php-security/SKILL.md
new file mode 100644
index 0000000..73d39a3
--- /dev/null
+++ b/.kilo/skills/php-security/SKILL.md
@@ -0,0 +1,147 @@
+---
+name: php-security
+description: PHP security patterns - OWASP Top 10, CSRF, XSS, SQL injection, authentication, input validation
+---
+
+# PHP Security Checklist
+
+## Input Validation & Sanitization
+
+```php
+// Always validate and sanitize input
+$name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_SPECIAL_CHARS);
+$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
+$price = filter_input(INPUT_POST, 'price', FILTER_VALIDATE_FLOAT);
+$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
+
+// Laravel: Form Request validation
+// Symfony: Form Types with Constraints
+// WordPress: sanitize_text_field(), absint()
+```
+
+## SQL Injection Prevention
+
+```php
+// NEVER: Direct string interpolation
+$sql = "SELECT * FROM users WHERE email = '$email'"; // VULNERABLE!
+
+// ALWAYS: Parameterized queries
+// PDO
+$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
+$stmt->execute(['email' => $email]);
+
+// Laravel Eloquent (safe by default)
+User::where('email', $email)->first();
+
+// WordPress $wpdb
+$wpdb->get_results($wpdb->prepare(
+ 'SELECT * FROM %i WHERE email = %s',
+ $wpdb->users,
+ $email
+));
+```
+
+## XSS Prevention
+
+```php
+// Output encoding
+echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
+
+// Laravel: Blade auto-escapes {{ $variable }}
+// Symfony: Twig auto-escapes {{ variable }}
+
+// NEVER output raw user data
+echo $userInput; // VULNERABLE!
+
+// Content Security Policy header
+header("Content-Security-Policy: default-src 'self'; script-src 'self'");
+```
+
+## CSRF Protection
+
+```php
+// Laravel: @csrf in forms
+
+
+// Symfony: $form->handleRequest($request) handles CSRF
+// WordPress: wp_nonce_field() + wp_verify_nonce()
+
+// API: Use Sanctum tokens instead of CSRF
+```
+
+## Authentication
+
+```php
+// Laravel Sanctum (API)
+$token = $user->createToken('api-token')->plainTextToken;
+
+// Password hashing (NEVER md5/sha1)
+$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
+if (password_verify($input, $hash)) { /* authenticated */ }
+
+// Rate limiting
+RateLimiter::for('login', function (Request $request) {
+ return Limit::perMinute(5)->by($request->ip());
+});
+```
+
+## File Upload Security
+
+```php
+// Validate file type, size, and content
+$allowedMimes = ['image/jpeg', 'image/png', 'image/webp'];
+$maxSize = 2 * 1024 * 1024; // 2MB
+
+if (!in_array($file->getMimeType(), $allowedMimes)) {
+ throw new ValidationException('Invalid file type');
+}
+if ($file->getSize() > $maxSize) {
+ throw new ValidationException('File too large');
+}
+
+// Store outside web root
+$path = $file->store('uploads', 'private');
+
+// NEVER use user-provided filenames
+```
+
+## Session Security
+
+```php
+ini_set('session.cookie_httponly', '1');
+ini_set('session.cookie_secure', '1');
+ini_set('session.cookie_samesite', 'Strict');
+ini_set('session.use_strict_mode', '1');
+
+// Regenerate ID on login
+session_regenerate_id(true);
+```
+
+## Security Headers
+
+```php
+// Add security headers
+header('X-Content-Type-Options: nosniff');
+header('X-Frame-Options: DENY');
+header('X-XSS-Protection: 1; mode=block');
+header('Referrer-Policy: strict-origin-when-cross-origin');
+header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
+```
+
+## OWASP Top 10 Checklist
+
+| # | Risk | Mitigation |
+|---|------|------------|
+| A01 | Broken Access Control | Authorization checks, RBAC, deny by default |
+| A02 | Crypto Failures | TLS, bcrypt, no weak algorithms |
+| A03 | Injection | Parameterized queries, validation |
+| A04 | Insecure Design | Threat modeling, secure defaults |
+| A05 | Security Misconfig | No debug in prod, no defaults, headers |
+| A06 | Vulnerable Components | `composer audit`, update regularly |
+| A07 | Auth Failures | MFA, rate limit, strong passwords |
+| A08 | Data Integrity | Signature verification, CI security |
+| A09 | Logging | Log auth failures, no sensitive data in logs |
+| A10 | SSRF | URL allowlist, no internal requests |
\ No newline at end of file
diff --git a/.kilo/skills/php-symfony-patterns/SKILL.md b/.kilo/skills/php-symfony-patterns/SKILL.md
new file mode 100644
index 0000000..13c0486
--- /dev/null
+++ b/.kilo/skills/php-symfony-patterns/SKILL.md
@@ -0,0 +1,233 @@
+---
+name: php-symfony-patterns
+description: Symfony framework patterns - controllers, services, Doctrine ORM, forms, security, messenger
+---
+
+# PHP Symfony Patterns
+
+## Project Structure
+
+```
+src/
+├── Controller/ # Thin controllers
+├── Service/ # Business logic
+├── Repository/ # Doctrine repositories
+├── Entity/ # Doctrine entities
+├── Form/ # Form types
+├── EventSubscriber/ # Event subscribers
+├── Message/ # Messenger messages
+├── MessageHandler/ # Messenger handlers
+├── Security/ # Voters, authenticators
+├── DTO/ # Data Transfer Objects
+├── Exception/ # Custom exceptions
+└── Trait/ # Reusable traits
+config/
+├── packages/ # Bundle configs
+├── routes/ # Route configs
+└── services.yaml # Service definitions
+migrations/
+templates/
+tests/
+```
+
+## Controller Pattern
+
+```php
+// src/Controller/ProductController.php
+#[Route('/api/v1/products', name: 'api_products_')]
+class ProductController extends AbstractController
+{
+ public function __construct(
+ private ProductService $productService
+ ) {}
+
+ #[Route('', name: 'index', methods: ['GET'])]
+ public function index(ProductFilter $filter): JsonResponse
+ {
+ $products = $this->productService->list($filter);
+ return $this->json($products, 200, [], ['groups' => 'product:read']);
+ }
+
+ #[Route('', name: 'create', methods: ['POST'])]
+ public function create(Request $request): JsonResponse
+ {
+ $product = $this->productService->create($request->request->all());
+ return $this->json($product, 201, [], ['groups' => 'product:read']);
+ }
+
+ #[Route('/{id}', name: 'show', methods: ['GET'])]
+ public function show(int $id): JsonResponse
+ {
+ $product = $this->productService->find($id);
+ return $this->json($product, 200, [], ['groups' => 'product:read']);
+ }
+}
+```
+
+## Doctrine Entity Pattern
+
+```php
+// src/Entity/Product.php
+#[ORM\Entity(repositoryClass: ProductRepository::class)]
+#[ORM\HasLifecycleCallbacks]
+class Product
+{
+ #[ORM\Id]
+ #[ORM\GeneratedValue]
+ #[ORM\Column]
+ private ?int $id = null;
+
+ #[ORM\Column(length: 255)]
+ private ?string $name = null;
+
+ #[ORM\Column(type: 'decimal', precision: 10, scale: 2)]
+ private ?string $price = null;
+
+ #[ORM\ManyToOne(inversedBy: 'products')]
+ #[ORM\JoinColumn(nullable: false)]
+ private ?Category $category = null;
+
+ #[ORM\Column]
+ private ?bool $isActive = true;
+
+ #[ORM\Column]
+ private ?\DateTimeImmutable $createdAt = null;
+
+ // Getters and setters omitted for brevity
+
+ #[ORM\PrePersist]
+ public function setCreatedAtValue(): void
+ {
+ $this->createdAt = new \DateTimeImmutable();
+ }
+}
+```
+
+## Repository Pattern
+
+```php
+// src/Repository/ProductRepository.php
+class ProductRepository extends ServiceEntityRepository
+{
+ public function listFiltered(ProductFilter $filter): Paginator
+ {
+ $qb = $this->createQueryBuilder('p')
+ ->leftJoin('p.category', 'c')->addSelect('c')
+ ->where('p.isActive = true');
+
+ if ($filter->category) {
+ $qb->andWhere('p.category = :cat')
+ ->setParameter('cat', $filter->category);
+ }
+
+ if ($filter->search) {
+ $qb->andWhere('p.name LIKE :search')
+ ->setParameter('search', "%{$filter->search}%");
+ }
+
+ if ($filter->minPrice) {
+ $qb->andWhere('p.price >= :min')
+ ->setParameter('min', $filter->minPrice);
+ }
+
+ return new Paginator($qb->orderBy('p.createdAt', 'DESC'));
+ }
+}
+```
+
+## Service Pattern
+
+```php
+// src/Service/ProductService.php
+class ProductService
+{
+ public function __construct(
+ private ProductRepository $repository,
+ private EntityManagerInterface $em,
+ private EventDispatcherInterface $dispatcher
+ ) {}
+
+ public function list(ProductFilter $filter): Paginator
+ {
+ return $this->repository->listFiltered($filter);
+ }
+
+ public function create(array $data): Product
+ {
+ $product = new Product();
+ $product->setName($data['name']);
+ $product->setPrice($data['price']);
+ $product->setCategory($data['category']);
+
+ $this->em->persist($product);
+ $this->em->flush();
+
+ $this->dispatcher->dispatch(new ProductCreatedEvent($product));
+
+ return $product;
+ }
+}
+```
+
+## Messenger (Async Jobs)
+
+```php
+// src/Message/ProcessPayment.php
+class ProcessPayment
+{
+ public function __construct(public int $orderId) {}
+}
+
+// src/MessageHandler/ProcessPaymentHandler.php
+class ProcessPaymentHandler implements MessageHandlerInterface
+{
+ public function __construct(
+ private OrderRepository $orderRepository,
+ private PaymentService $paymentService
+ ) {}
+
+ public function __invoke(ProcessPayment $message): void
+ {
+ $order = $this->orderRepository->find($message->orderId);
+ $this->paymentService->process($order);
+ }
+}
+
+// Dispatch
+$this->bus->dispatch(new ProcessPayment($order->getId()));
+```
+
+## Security (Voters)
+
+```php
+// src/Security/ProductVoter.php
+class ProductVoter extends Voter
+{
+ protected function supports(string $attribute, mixed $subject): bool
+ {
+ return in_array($attribute, ['EDIT', 'DELETE']) && $subject instanceof Product;
+ }
+
+ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
+ {
+ $user = $token->getUser();
+ return match($attribute) {
+ 'EDIT' => $user === $subject->getOwner() || in_array('ROLE_ADMIN', $user->getRoles()),
+ 'DELETE' => in_array('ROLE_ADMIN', $user->getRoles()),
+ default => false,
+ };
+ }
+}
+```
+
+## Checklist
+
+- [ ] Thin controllers, fat services
+- [ ] Doctrine entities with proper types
+- [ ] Repository for queries (never in controller)
+- [ ] Messenger for async operations
+- [ ] Voters for authorization
+- [ ] Serializer groups for API output
+- [ ] Form types for input validation
+- [ ] Custom exceptions with proper HTTP mapping
+- [ ] `php bin/console lint:container` before commit
\ No newline at end of file
diff --git a/.kilo/skills/php-testing/SKILL.md b/.kilo/skills/php-testing/SKILL.md
new file mode 100644
index 0000000..b1198e6
--- /dev/null
+++ b/.kilo/skills/php-testing/SKILL.md
@@ -0,0 +1,242 @@
+---
+name: php-testing
+description: PHP testing patterns - PHPUnit, Pest, Dusk browser tests, mocking, test organization
+---
+
+# PHP Testing Patterns
+
+## Project Structure
+
+```
+tests/
+├── Unit/
+│ ├── Services/
+│ │ ├── ProductServiceTest.php
+│ │ └── PaymentServiceTest.php
+│ └── Repositories/
+│ └── ProductRepositoryTest.php
+├── Feature/
+│ ├── Http/
+│ │ ├── ProductControllerTest.php
+│ │ └── AuthControllerTest.php
+│ └── Console/
+│ └── ProcessOrdersCommandTest.php
+├── Browser/ # Dusk tests
+│ └── ProductBrowserTest.php
+└── TestCase.php # Base test class
+```
+
+## Base TestCase (Laravel)
+
+```php
+// tests/TestCase.php
+abstract class TestCase extends BaseTestCase
+{
+ use RefreshDatabase;
+ use WithFaker;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+ // Seed essential data
+ $this->seed(DatabaseSeeder::class);
+ }
+}
+```
+
+## PHPUnit Test Patterns
+
+```php
+// tests/Unit/Services/ProductServiceTest.php
+class ProductServiceTest extends TestCase
+{
+ private ProductService $service;
+ private ProductRepository $repository;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+ $this->repository = $this->createMock(ProductRepository::class);
+ $this->service = new ProductService($this->repository);
+ }
+
+ public function testCreateProductWithValidData(): void
+ {
+ $data = [
+ 'name' => 'Test Product',
+ 'price' => 29.99,
+ 'category_id' => 1,
+ ];
+
+ $this->repository
+ ->expects($this->once())
+ ->method('create')
+ ->with($data)
+ ->willReturn(new Product($data));
+
+ $result = $this->service->create($data);
+
+ $this->assertInstanceOf(Product::class, $result);
+ $this->assertEquals('Test Product', $result->name);
+ }
+
+ public function testCreateProductThrowsOnInvalidData(): void
+ {
+ $this->expectException(ValidationException::class);
+ $this->service->create(['name' => '']); // empty name
+ }
+
+ /**
+ * @dataProvider priceProvider
+ */
+ public function testPriceCalculation(float $input, float $tax, float $expected): void
+ {
+ $product = Product::factory()->make(['price' => $input]);
+ $result = $this->service->calculateTotal($product, $tax);
+ $this->assertEquals($expected, $result);
+ }
+
+ public static function priceProvider(): array
+ {
+ return [
+ 'no tax' => [100.0, 0.0, 100.0],
+ '10% tax' => [100.0, 0.10, 110.0],
+ '20% tax' => [100.0, 0.20, 120.0],
+ ];
+ }
+}
+```
+
+## Pest Tests (Laravel)
+
+```php
+// tests/Feature/ProductTest.php
+uses(RefreshDatabase::class);
+
+it('can list products', function () {
+ Product::factory()->count(15)->create();
+
+ $response = $this->getJson('/api/v1/products');
+
+ $response->assertOk()
+ ->assertJsonStructure([
+ 'data' => ['*' => ['id', 'name', 'price']],
+ 'meta' => ['total', 'current_page'],
+ ]);
+});
+
+it('can create a product', function () {
+ $category = Category::factory()->create();
+
+ $response = $this->postJson('/api/v1/products', [
+ 'name' => 'New Product',
+ 'price' => 49.99,
+ 'category_id' => $category->id,
+ ]);
+
+ $response->assertCreated()
+ ->assertJsonPath('data.name', 'New Product');
+});
+
+it('validates required fields', function () {
+ $response = $this->postJson('/api/v1/products', []);
+
+ $response->assertUnprocessable()
+ ->assertJsonValidationErrors(['name', 'price', 'category_id']);
+});
+
+it('requires authentication for protected routes', function () {
+ $this->postJson('/api/v1/orders', [])
+ ->assertUnauthorized();
+});
+```
+
+## API Feature Tests
+
+```php
+// tests/Feature/AuthTest.php
+it('can register', function () {
+ $response = $this->postJson('/api/v1/auth/register', [
+ 'name' => 'Test User',
+ 'email' => 'test@example.com',
+ 'password' => 'password123',
+ 'password_confirmation' => 'password123',
+ ]);
+
+ $response->assertCreated()
+ ->assertJsonStructure(['user', 'token']);
+});
+
+it('can login', function () {
+ $user = User::factory()->create([
+ 'password' => bcrypt('password123'),
+ ]);
+
+ $response = $this->postJson('/api/v1/auth/login', [
+ 'email' => $user->email,
+ 'password' => 'password123',
+ ]);
+
+ $response->assertOk()
+ ->assertJsonStructure(['user', 'token']);
+});
+
+it('cannot login with wrong password', function () {
+ $user = User::factory()->create();
+
+ $this->postJson('/api/v1/auth/login', [
+ 'email' => $user->email,
+ 'password' => 'wrong-password',
+ ])->assertUnauthorized();
+});
+```
+
+## Browser Tests (Dusk)
+
+```php
+// tests/Browser/ProductBrowserTest.php
+class ProductBrowserTest extends DuskTestCase
+{
+ public function testUserCanBrowseProducts(): void
+ {
+ $this->browse(function (Browser $browser) {
+ $browser->visit('/products')
+ ->assertSee('Products')
+ ->clickLink('First Product')
+ ->assertSee('Add to Cart')
+ ->press('Add to Cart')
+ ->assertSeeIn('.cart-count', '1');
+ });
+ }
+}
+```
+
+## Test Commands
+
+```bash
+# Run all tests
+php artisan test
+
+# Run specific test
+php artisan test --filter=ProductServiceTest
+
+# Run with coverage
+php artisan test --coverage --min=80
+
+# Pest only
+./vendor/bin/pest --parallel
+
+# Dusk browser tests
+php artisan dusk
+```
+
+## Checklist
+
+- [ ] Unit tests for services (mocked dependencies)
+- [ ] Feature tests for API endpoints
+- [ ] Browser tests for critical flows
+- [ ] Test factories for all models
+- [ ] Data providers for edge cases
+- [ ] Coverage >= 80%
+- [ ] Tests run in CI pipeline
+- [ ] Each test is independent and repeatable
\ No newline at end of file
diff --git a/.kilo/skills/php-wordpress-patterns/SKILL.md b/.kilo/skills/php-wordpress-patterns/SKILL.md
new file mode 100644
index 0000000..67fdc65
--- /dev/null
+++ b/.kilo/skills/php-wordpress-patterns/SKILL.md
@@ -0,0 +1,276 @@
+---
+name: php-wordpress-patterns
+description: WordPress development patterns - plugins, themes, REST API, hooks, Custom Post Types, Gutenberg blocks
+---
+
+# PHP WordPress Patterns
+
+## Plugin Structure
+
+```
+my-plugin/
+├── my-plugin.php # Main plugin file
+├── composer.json # Dependencies
+├── package.json # Build tools
+├── includes/
+│ ├── Admin/ # Admin settings pages
+│ ├── Frontend/ # Frontend rendering
+│ ├── REST/ # REST API controllers
+│ ├── PostTypes/ # Custom Post Types
+│ ├── Taxonomies/ # Custom Taxonomies
+│ ├── Shortcodes/ # Shortcode handlers
+│ ├── Widgets/ # Widget classes
+│ └── Utils/ # Helper functions
+├── src/
+│ ├── blocks/ # Gutenberg blocks (JSX)
+│ └── store/ # Block data stores
+├── assets/
+│ ├── css/
+│ ├── js/
+│ └── images/
+├── templates/ # Template files
+├── languages/ # i18n
+└── tests/
+ ├── Unit/
+ └── Integration/
+```
+
+## Main Plugin File
+
+```php
+createTables();
+ flush_rewrite_rules();
+ }
+
+ public function deactivate(): void
+ {
+ flush_rewrite_rules();
+ }
+}
+
+Plugin::init();
+```
+
+## Custom Post Type
+
+```php
+// includes/PostTypes/Product.php
+namespace MyPlugin\PostTypes;
+
+class Product
+{
+ public static function register(): void
+ {
+ register_post_type('product', [
+ 'labels' => [
+ 'name' => __('Products', 'my-plugin'),
+ 'singular_name' => __('Product', 'my-plugin'),
+ ],
+ 'public' => true,
+ 'has_archive' => true,
+ 'rewrite' => ['slug' => 'products'],
+ 'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
+ 'show_in_rest' => true, // Enable REST API
+ 'menu_icon' => 'dashicons-cart',
+ ]);
+
+ // Custom fields via meta
+ register_post_meta('product', 'price', [
+ 'show_in_rest' => true,
+ 'single' => true,
+ 'type' => 'number',
+ 'sanitize_callback' => 'absint',
+ ]);
+ }
+}
+
+add_action('init', [Product::class, 'register']);
+```
+
+## REST API Controller
+
+```php
+// includes/REST/ProductController.php
+namespace MyPlugin\REST;
+
+use WP_REST_Controller;
+use WP_REST_Request;
+use WP_REST_Response;
+use WP_Error;
+
+class ProductController extends WP_REST_Controller
+{
+ public function __construct()
+ {
+ $this->namespace = 'my-plugin/v1';
+ $this->rest_base = 'products';
+ }
+
+ public function registerRoutes(): void
+ {
+ register_rest_route($this->namespace, '/' . $this->rest_base, [
+ [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'getItems'],
+ 'permission_callback' => '__return_true',
+ 'args' => [
+ 'page' => ['default' => 1, 'sanitize_callback' => 'absint'],
+ 'per_page' => ['default' => 20, 'sanitize_callback' => 'absint'],
+ 'category' => ['sanitize_callback' => 'absint'],
+ ],
+ ],
+ [
+ 'methods' => 'POST',
+ 'callback' => [$this, 'createItem'],
+ 'permission_callback' => function() {
+ return current_user_can('edit_posts');
+ },
+ ],
+ ]);
+
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P\d+)', [
+ [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'getItem'],
+ 'permission_callback' => '__return_true',
+ ],
+ [
+ 'methods' => 'PUT',
+ 'callback' => [$this, 'updateItem'],
+ 'permission_callback' => function() {
+ return current_user_can('edit_posts');
+ },
+ ],
+ [
+ 'methods' => 'DELETE',
+ 'callback' => [$this, 'deleteItem'],
+ 'permission_callback' => function() {
+ return current_user_can('delete_posts');
+ },
+ ],
+ ]);
+ }
+
+ public function getItems(WP_REST_Request $request): WP_REST_Response
+ {
+ $args = [
+ 'post_type' => 'product',
+ 'posts_per_page' => $request['per_page'],
+ 'paged' => $request['page'],
+ 'post_status' => 'publish',
+ ];
+
+ if ($request['category']) {
+ $args['tax_query'] = [[
+ 'taxonomy' => 'product_category',
+ 'field' => 'term_id',
+ 'terms' => $request['category'],
+ ]];
+ }
+
+ $query = new \WP_Query($args);
+ $products = array_map(fn($p) => $this->prepareItem($p), $query->posts);
+
+ return new WP_REST_Response([
+ 'data' => $products,
+ 'total' => (int) $query->found_posts,
+ 'pages' => (int) $query->max_num_pages,
+ ], 200);
+ }
+
+ private function prepareItem(\WP_Post $post): array
+ {
+ return [
+ 'id' => $post->ID,
+ 'title' => $post->post_title,
+ 'content' => $post->post_content,
+ 'price' => get_post_meta($post->ID, 'price', true),
+ 'thumbnail' => get_the_post_thumbnail_url($post->ID, 'medium'),
+ 'date' => $post->post_date,
+ ];
+ }
+}
+```
+
+## Security Essentials
+
+```php
+// Nonce verification for forms
+if (!wp_verify_nonce($_POST['_wpnonce'], 'my_plugin_action')) {
+ wp_die('Security check failed');
+}
+
+// Data sanitization
+$title = sanitize_text_field($_POST['title']);
+$content = wp_kses_post($_POST['content']);
+$price = floatval($_POST['price']);
+$id = absint($_POST['id']);
+
+// SQL queries - always use $wpdb->prepare()
+$results = $wpdb->get_results($wpdb->prepare(
+ "SELECT * FROM {$wpdb->prefix}products WHERE category_id = %d AND status = %s",
+ $category_id,
+ 'active'
+));
+
+// Escape output
+echo esc_html($title);
+echo esc_url($url);
+echo esc_attr($attr);
+```
+
+## Checklist
+
+- [ ] Namespace all PHP code (no global functions)
+- [ ] Use `declare(strict_types=1)` in all files
+- [ ] Nonce verification on all forms and AJAX
+- [ ] Sanitize input, escape output, prepare SQL
+- [ ] Custom Post Types with `show_in_rest`
+- [ ] REST API controllers extend `WP_REST_Controller`
+- [ ] Capability checks (`current_user_can`)
+- [ ] Use `wp_enqueue_script`/`wp_enqueue_style`
+- [ ] Internationalization (`__()` and `_e()`)
+- [ ] Activation/deactivation hooks
+- [ ] Options API for settings (never custom tables for settings)
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
index ee5ddc5..7febec6 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -43,6 +43,7 @@ These agents are invoked automatically by `/pipeline` or manually via `@mention`
| `@lead-developer` | Implements code | qwen3-coder:480b | thinking | code-skeptic, orchestrator |
| `@frontend-developer` | UI implementation | qwen3-coder:480b | — | code-skeptic, orchestrator |
| `@backend-developer` | Node.js/Express/APIs | qwen3-coder:480b | — | code-skeptic, orchestrator |
+| `@php-developer` | PHP/Laravel/Symfony/WordPress | qwen3-coder:480b | thinking | code-skeptic, security-auditor, orchestrator |
| `@go-developer` | Go backend services | qwen3-coder:480b | — | code-skeptic, orchestrator |
| `@flutter-developer` | Flutter mobile apps | qwen3-coder:480b | — | code-skeptic, orchestrator |
@@ -350,6 +351,75 @@ bun run evolution:open
| **Medium** | Better model available | Consider upgrade |
| **Low** | Optimization possible | Optional improvement |
+## Agent Execution Monitoring
+
+Every agent invocation is logged to `.kilo/logs/agent-executions.jsonl` for project-level monitoring.
+
+### Log Format
+
+```jsonl
+{"ts":"2026-04-18T14:00:00Z","agent":"php-developer","issue":42,"project":"UniqueSoft/my-shop","task":"Create Product model","subtask_type":"model_creation","duration_ms":45000,"tokens_used":8500,"status":"success","files":["app/Models/Product.php"],"score":8,"next_agent":"code-skeptic"}
+```
+
+### Monitoring Commands
+
+```bash
+# Agent stats report
+bun run scripts/agent-stats.ts
+
+# Stats for last 7 days
+bun run scripts/agent-stats.ts --last 7
+
+# Stats for specific project
+bun run scripts/agent-stats.ts --project UniqueSoft/my-shop
+```
+
+### Required Logging Fields
+
+| Field | Description |
+|-------|-------------|
+| `agent` | Agent name |
+| `issue` | Gitea issue number |
+| `project` | Target project repo (NOT hardcoded APAW) |
+| `task` | Atomic task description |
+| `duration_ms` | Execution time |
+| `tokens_used` | Token estimate |
+| `status` | success/fail/pass/blocked |
+
+## Critical Rules
+
+### Target Project (NOT APAW)
+
+**Issues MUST be created in the target project repository, NOT in APAW.** APAW is the agent framework, not the default project.
+
+```bash
+# Auto-detect from git remote
+TARGET_REPO=$(git remote get-url origin | sed -E 's|.*[:/]([^/]+/[^/]+?)(\.git)?$|\1|')
+```
+
+### Atomic Tasks (1 action = 1 task)
+
+Every agent invocation solves exactly ONE atomic task:
+- ❌ "Implement the entire e-commerce backend"
+- ✅ "Create Product model with migration"
+- ✅ "Add POST /api/products endpoint"
+
+### Modular Code
+
+- Maximum 100 lines per file
+- Maximum 30 lines per function
+- Features organized as independent modules
+- Cross-module communication via events/interfaces only
+
+### Token Budgets
+
+| Task Size | Max Tokens | Example |
+|----------|-----------|---------|
+| Tiny | 2,000 | Fix typo, add config |
+| Small | 5,000 | Create model + migration |
+| Medium | 10,000 | Create API endpoint + test |
+| Large | 20,000 | Create service with 3 methods |
+
## Code Style
- Use TypeScript for new files
diff --git a/README.md b/README.md
index a7d50ec..218583d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# APAW — Automatic Programmers Agent Workflow
-**Self-Improving Agent Pipeline** — автономная система из 28+ специализированных ИИ-агентов с автоматической эволюцией промптов.
+**Self-Improving Agent Pipeline** — автономная система из 29+ специализированных ИИ-агентов с автоматической эволюцией промптов, мониторингом выполнения и модульной архитектурой.
---
@@ -9,11 +9,13 @@
```
APAW/
├── .kilo/ # KiloCode конфигурация
-│ ├── agents/ # 28 агентов (YAML frontmatter)
-│ ├── commands/ # Workflow команды
-│ ├── rules/ # Правила кодирования
-│ ├── skills/ # Специализированные навыки
-│ ├── capability-index.yaml # Индекс возможностей
+│ ├── agents/ # 29 агентов (YAML frontmatter)
+│ ├── commands/ # Workflow команды (/pipeline, /laravel, /wordpress, etc.)
+│ ├── rules/ # Правила кодирования (атомарные задачи, модульность, токены)
+│ ├── skills/ # Специализированные навыки (PHP, Go, Node, Docker, Gitea)
+│ ├── shared/ # Общие модули (gitea-api, gitea-commenting, self-evolution)
+│ ├── logs/ # Логи выполнения агентов и fitness-метрики
+│ ├── capability-index.yaml # Индекс возможностей и маршрутизация
│ ├── kilo.jsonc # Конфигурация primary агентов
│ └── KILO_SPEC.md # Спецификация агентов
├── agent-evolution/ # Dashboard эволюции агентов
@@ -21,10 +23,10 @@ APAW/
│ ├── scripts/ # Scripts синхронизации
│ ├── data/ # История изменений
│ └── docker-compose.yml # Docker запуск
-├── src/kilocode/ # TypeScript API
-├── archive/ # Архивные документы
├── scripts/ # Utility scripts
+│ └── agent-stats.ts # Статистика выполнения агентов
├── AGENTS.md # Справка по агентам
+├── STRUCTURE.md # Структура проекта
└── README.md # Этот документ
```
@@ -47,7 +49,7 @@ KiloCode автоматически обнаружит `.kilo/` и загруз
### Запуск Dashboard эволюции
```bash
-# Стandalone (без Docker)
+# Standalone (без Docker)
bun run sync:evolution
open agent-evolution/index.standalone.html
@@ -57,20 +59,33 @@ docker-compose up -d
# Dashboard доступен на http://localhost:3001
```
+### Мониторинг агентов
+
+```bash
+# Статистика выполнения за 30 дней
+bun run agent:stats
+
+# За последнюю неделю
+bun run agent:stats:week
+
+# По конкретному проекту
+bun run agent:stats --project UniqueSoft/my-shop
+```
+
---
-## Команда агентов (28+)
+## Команда агентов (29+)
### Планирование и Анализ
| Агент | Модель | Назначение |
|-------|--------|------------|
| `@orchestrator` | GLM-5 | Главный диспетчер, маршрутизация задач |
-| `@requirement-refiner` | Nemotron-3-Super | Идеи → User Stories |
+| `@requirement-refiner` | GLM-5 | Идеи → User Stories |
| `@history-miner` | Nemotron-3-Super | Поиск дублей в git |
| `@system-analyst` | GLM-5 | Схемы БД, API контракты |
| `@planner` | Nemotron-3-Super | Декомпозиция задач (CoT/ToT) |
-| `@capability-analyst` | Nemotron-3-Super | Gap analysis |
+| `@capability-analyst` | GLM-5 | Gap analysis |
### Разработка
@@ -79,6 +94,7 @@ docker-compose up -d
| `@lead-developer` | Qwen3-Coder 480B | Основной код по TDD |
| `@frontend-developer` | Qwen3-Coder 480B | UI компоненты |
| `@backend-developer` | Qwen3-Coder 480B | Node.js/Express APIs |
+| `@php-developer` | Qwen3-Coder 480B | PHP/Laravel/Symfony/WordPress |
| `@go-developer` | Qwen3-Coder 480B | Go/Gin/Echo APIs |
| `@flutter-developer` | Qwen3-Coder 480B | Flutter mobile apps |
| `@devops-engineer` | Nemotron-3-Super | Docker, K8s, CI/CD |
@@ -97,10 +113,11 @@ docker-compose up -d
| Агент | Модель | Назначение |
|-------|--------|------------|
-| `@release-manager` | Devstral-2 123B | Git Flow, SemVer |
-| `@evaluator` | Nemotron-3-Super | Оценка агентов 1-10 |
-| `@prompt-optimizer` | Qwen3.6-Plus | Улучшение промптов |
-| `@product-owner` | Qwen3.6-Plus | Управление Issues |
+| `@release-manager` | GLM-5 | Git Flow, SemVer |
+| `@evaluator` | GLM-5 | Оценка агентов 1-10 |
+| `@pipeline-judge` | GLM-5 | Объективный fitness score |
+| `@prompt-optimizer` | GLM-5 | Улучшение промптов |
+| `@product-owner` | GLM-5 | Управление Issues |
### Когнитивное усиление
@@ -109,22 +126,12 @@ docker-compose up -d
| `@reflector` | Reflexion | Анализ ошибок |
| `@memory-manager` | Memory Arch | Управление контекстом |
-### Специализированные
-
-| Агент | Модель | Назначение |
-|-------|--------|------------|
-| `@browser-automation` | Qwen3-Coder 480B | Playwright E2E |
-| `@visual-tester` | Qwen3-Coder 480B | Visual regression |
-| `@workflow-architect` | Qwen3.6-Plus | Workflow определения |
-| `@markdown-validator` | Nemotron-3-Nano | Валидация Markdown |
-| `@agent-architect` | Nemotron-3-Super | Создание агентов |
-
---
## Pipeline Workflow
```
-[Issue]
+[Issue в целевом проекте]
↓
[@requirement-refiner] → User Story + Acceptance Criteria
↓
@@ -134,25 +141,62 @@ docker-compose up -d
↓
[@sdet-engineer] → TDD Red Phase (тесты падают)
↓
-[@lead-developer] → TDD Green Phase (тесты проходят)
+[@lead-developer] / [@php-developer] → TDD Green Phase
↓
[@code-skeptic] → Adversarial review
↓ (fail) ↓ (pass)
[@the-fixer] [@performance-engineer]
↓ ↓
─────────────────→ [@security-auditor]
- ↓
- [@release-manager]
- ↓
- [@evaluator] → Score 1-10
- ↓ (score < 7)
- [@prompt-optimizer]
- ↓
- [@product-owner] → Close Issue
+ ↓
+ [@release-manager]
+ ↓
+ [@evaluator] → Score 1-10
+ ↓ (score ≥ 7)
+ [@pipeline-judge] → Fitness 0.0-1.0
+ ↓
+ fitness ≥ 0.85 → COMPLETED
+ fitness < 0.85 → [@prompt-optimizer] → evolving
```
---
+## Критические правила
+
+### Целевой проект (НЕ APAW!)
+
+**Issues создаются в целевом проекте, а НЕ в APAW.** APAW — фреймворк агентов, а не проект по умолчанию.
+
+```bash
+# Автоопределение проекта из git remote
+TARGET_REPO=$(git remote get-url origin | sed -E 's|.*[:/]([^/]+/[^/]+?)(\.git)?$|\1|')
+```
+
+### Атомарные задачи (1 действие = 1 задача)
+
+Каждый вызов агента решает ровно ОДНУ атомарную задачу:
+- ❌ "Реализуй весь бэкенд интернет-магазина"
+- ✅ "Создай модель Product с миграцией"
+- ✅ "Добавь POST /api/products endpoint"
+
+### Модульный код
+
+- Максимум 100 строк на файл
+- Максимум 30 строк на функцию
+- Фичи организованы как независимые модули
+- Коммуникация между модулями — только через events/interfaces
+
+### Бюджет токенов
+
+| Размер задачи | Макс. токенов | Пример |
+|-------------|-------------|---------|
+| Tiny | 2,000 | Исправить опечатку |
+| Small | 5,000 | Модель + миграция |
+| Medium | 10,000 | API endpoint + тест |
+| Large | 20,000 | Сервис с 3 методами |
+
+---
+
## Конфигурация
### Models (kilo.jsonc)
@@ -160,9 +204,9 @@ docker-compose up -d
Primary агенты для UI:
- `orchestrator` — GLM-5 (главный диспетчер)
- `code` — Qwen3-Coder 480B (быстрый код)
-- `ask` — Qwen3.6-Plus (вопросы по коду)
+- `ask` — GLM-5 (вопросы по коду)
- `plan` — Nemotron-3-Super (планирование)
-- `debug` — Gemma4 31B (диагностика)
+- `debug` — GLM-5 (диагностика)
Subagent модели определены в `.md` файлах агентов.
@@ -172,6 +216,9 @@ Subagent модели определены в `.md` файлах агентов.
- `code_writing` → `lead-developer`
- `code_review` → `code-skeptic`
- `test_writing` → `sdet-engineer`
+- `php_web_development` → `php-developer`
+- `laravel_development` → `php-developer`
+- `wordpress_development` → `php-developer`
- `security` → `security-auditor`
- и т.д.
@@ -182,7 +229,9 @@ Subagent модели определены в `.md` файлах агентов.
Система автоматически отслеживает:
- Изменения моделей
- Оценки производительности
+- Fitness score (объективные метрики)
- Рекомендации по улучшению
+- Логи выполнения каждого агента
```bash
# Синхронизировать данные
@@ -190,6 +239,9 @@ bun run sync:evolution
# Открыть dashboard
bun run evolution:open
+
+# Статистика агентов
+bun run agent:stats
```
---
@@ -197,10 +249,54 @@ bun run evolution:open
## Skills System
Навыки в `.kilo/skills/`:
-- `gitea-workflow` — Gitea интеграция
+
+### PHP
+- `php-laravel-patterns` — Laravel routing, Eloquent, middleware, queues
+- `php-symfony-patterns` — Symfony controllers, services, Doctrine
+- `php-wordpress-patterns` — WordPress plugins, themes, REST API
+- `php-security` — OWASP, CSRF, XSS, SQL injection
+- `php-testing` — PHPUnit, Pest, Dusk
+- `php-modular-architecture` — Modules, packages, service separation
+
+### Инфраструктура
+- `gitea-workflow` — Gitea интеграция (автодетекция проекта)
- `gitea-commenting` — Автоматические комментарии
-- `research-cycle` — Self-improvement
-- `planning-patterns` — CoT/ToT паттерны
+- `agent-logging` — Логирование выполнения агентов
+
+### Docker / БД
+- `docker-compose`, `docker-swarm`, `docker-security`, `docker-monitoring`
+- `postgresql-patterns`, `sqlite-patterns`, `clickhouse-patterns`
+
+### Go / Node
+- `go-web-patterns`, `go-middleware`, `go-testing`, `go-security`
+- `nodejs-express-patterns`, `nodejs-auth-jwt`, `nodejs-security-owasp`
+
+### Тестирование / Анализ
+- `web-testing`, `visual-testing`, `playwright`
+- `research-cycle`, `planning-patterns`, `task-analysis`
+- `quality-controller`, `scoped-labels`
+
+---
+
+## Workflow Commands
+
+| Команда | Назначение |
+|---------|------------|
+| `/pipeline ` | Полный пайплайн для issue |
+| `/laravel` | Laravel приложение |
+| `/wordpress` | WordPress сайт/плагин |
+| `/feature` | Разработка фичи |
+| `/commerce` | E-commerce сайт |
+| `/booking` | Бронирование |
+| `/blog` | Блог |
+| `/hotfix` | Срочное исправление |
+| `/status ` | Статус пайплайна |
+| `/evolve` | Цикл эволюции |
+| `/plan` | Планирование задач |
+| `/debug` | Диагностика багов |
+| `/ask` | Вопросы по коду |
+| `/code` | Быстрый код |
+| `/research` | Исследование |
---
@@ -209,19 +305,29 @@ bun run evolution:open
```bash
GITEA_API_URL=https://git.softuniq.eu/api/v1
GITEA_TOKEN=your-token-here
+GITEA_TARGET_REPO=UniqueSoft/my-project # Переопределение проекта
```
---
-## Последние изменения
+## Мониторинг выполнения
-| Дата | Коммит | Описание |
-| |------|---------|
-| 2026-04-05 | `ff00b8e` | Синхронизация моделей агентов |
-| 2026-04-05 | `4af7355` | Обновление моделей по research-рекомендациям |
-| 2026-04-05 | `15a7b4b` | Agent Evolution Dashboard |
-| 2026-04-05 | `b899119` | html-to-flutter skill |
-| 2026-04-05 | `af5f401` | Flutter development support |
+Каждый вызов агента логируется в `.kilo/logs/agent-executions.jsonl`:
+
+```jsonl
+{"ts":"2026-04-18T14:00:00Z","agent":"php-developer","issue":42,"project":"UniqueSoft/my-shop","task":"Create Product model","subtask_type":"model_creation","duration_ms":45000,"tokens_used":8500,"status":"success","files":["app/Models/Product.php"],"score":8,"next_agent":"code-skeptic"}
+```
+
+```bash
+# Статистика агентов
+bun run agent:stats
+
+# За неделю
+bun run agent:stats:week
+
+# По проекту
+bun run agent:stats:project --project UniqueSoft/my-shop
+```
---
@@ -229,39 +335,25 @@ GITEA_TOKEN=your-token-here
| Layer | Technology |
|-------|------------|
-| Runtime | TypeScript / Node.js |
+| Runtime | TypeScript / Node.js / Bun |
| Agent Runtime | KiloCode VS Code Extension |
| Version Control | Gitea + Git Flow |
-| Languages | TypeScript / Node.js / Go |
-| Testing | TDD (Red-Green-Refactor) |
+| Languages | TypeScript / PHP / Go / Dart |
+| Testing | TDD (Red-Green-Refactor), PHPUnit, Pest |
| Containerization | Docker / Docker Compose |
---
-## API (TypeScript)
-
-```typescript
-import {
- PipelineRunner,
- GiteaClient
-} from 'apaw'
-
-const runner = await createPipelineRunner({
- giteaToken: process.env.GITEA_TOKEN
-})
-
-await runner.run({ issueNumber: 42 })
-```
-
----
-
## Статус проекта
✅ Production Ready
-✅ 28+ агентов
-✅ Self-improving pipeline
-✅ Gitea интеграция
+✅ 29 агентов
+✅ Self-improving pipeline с fitness scoring
+✅ Gitea интеграция с автодетекцией проекта
✅ Agent Evolution Dashboard
+✅ Мониторинг выполнения агентов
+✅ PHP/Laravel/Symfony/WordPress поддержка
+✅ Атомарные задачи и модульная архитектура
---
diff --git a/STRUCTURE.md b/STRUCTURE.md
index d89819e..5840f1e 100644
--- a/STRUCTURE.md
+++ b/STRUCTURE.md
@@ -7,191 +7,239 @@ This document describes the organized structure of the APAW project.
```
APAW/
├── .kilo/ # Kilo Code configuration
-│ ├── agents/ # Agent definitions
+│ ├── agents/ # 29 agent definitions (YAML frontmatter)
+│ │ ├── orchestrator.md # Main dispatcher
+│ │ ├── php-developer.md # PHP/Laravel/Symfony/WordPress
+│ │ ├── lead-developer.md # Primary code writer
+│ │ ├── code-skeptic.md # Adversarial review
+│ │ └── ... (25 more)
│ ├── commands/ # Slash commands
-│ ├── rules/ # Global rules
+│ │ ├── pipeline.md # Full agent pipeline
+│ │ ├── laravel.md # Laravel web app pipeline
+│ │ ├── wordpress.md # WordPress development pipeline
+│ │ ├── feature.md # Feature development
+│ │ ├── commerce.md # E-commerce site
+│ │ └── ... (15 more)
+│ ├── rules/ # Global rules (loaded for all agents)
+│ │ ├── global.md # Base rules
+│ │ ├── atomic-tasks.md # 1 action = 1 task principle
+│ │ ├── modular-code.md # Modules, libraries, microservices
+│ │ ├── token-optimization.md # Token budget rules
+│ │ ├── gitea-centric-workflow.md # Gitea as center of work
+│ │ └── ... (10 more)
│ ├── skills/ # Agent skills
-│ └── KILO_SPEC.md # Kilo specification
+│ │ ├── php-laravel-patterns/ # Laravel patterns
+│ │ ├── php-symfony-patterns/ # Symfony patterns
+│ │ ├── php-wordpress-patterns/ # WordPress patterns
+│ │ ├── php-security/ # OWASP, CSRF, XSS
+│ │ ├── php-testing/ # PHPUnit, Pest
+│ │ ├── php-modular-architecture/ # Module separation
+│ │ ├── agent-logging/ # Execution logging
+│ │ ├── gitea-workflow/ # Gitea integration (auto-detect project)
+│ │ ├── gitea-commenting/ # Comment format (auto-detect project)
+│ │ └── ... (30+ more)
+│ ├── shared/ # Shared modules
+│ │ ├── gitea-api.md # Centralized API client (auto-detect repo)
+│ │ ├── gitea-commenting.md # Comment format
+│ │ └── self-evolution.md # Evolution protocol
+│ ├── logs/ # Execution logs
+│ │ ├── agent-executions.jsonl # Every agent invocation
+│ │ ├── fitness-history.jsonl # Fitness scores
+│ │ └── efficiency_score.json # Efficiency history
+│ ├── capability-index.yaml # Agent capabilities & routing
+│ ├── kilo.jsonc # Primary agent config
+│ ├── KILO_SPEC.md # Specification
+│ └── EVOLUTION_LOG.md # Evolution timeline
+├── agent-evolution/ # Evolution Dashboard
+│ ├── index.standalone.html # Standalone dashboard
+│ ├── scripts/ # Sync & build scripts
+│ ├── data/ # Agent version history
+│ └── docker-compose.yml # Docker launch
+├── scripts/ # Utility scripts
+│ └── agent-stats.ts # Agent execution statistics
├── docker/ # Docker configurations
│ ├── Dockerfile.playwright # Playwright MCP container
-│ ├── docker-compose.yml # Base Docker config
+│ ├── docker-compose.yml # Base config
│ └── docker-compose.web-testing.yml
-├── scripts/ # Utility scripts
-│ └── web-test.sh # Web testing script
-├── tests/ # Test suite
-│ ├── scripts/ # Test scripts
-│ │ ├── compare-screenshots.js
-│ │ ├── console-error-monitor.js
-│ │ └── link-checker.js
-│ ├── visual/ # Visual regression
-│ │ ├── baseline/ # Reference screenshots
-│ │ ├── current/ # Current screenshots
-│ │ └── diff/ # Diff images
-│ ├── reports/ # Test reports
-│ ├── console/ # Console logs
-│ ├── links/ # Link check results
-│ ├── forms/ # Form test data
-│ ├── run-all-tests.js # Main test runner
-│ ├── package.json # Test dependencies
-│ └── README.md # Test documentation
-├── src/ # Source code
-├── archive/ # Deprecated files
-├── AGENTS.md # Agent reference
+├── AGENTS.md # Agent reference (main config)
+├── STRUCTURE.md # This document
└── README.md # Project overview
```
-## Docker Configurations
+## Key Configuration Files
-All Docker files are in `docker/`:
+### capability-index.yaml
-| File | Purpose |
-|------|---------|
-| `docker-compose.yml` | Base configuration |
-| `docker-compose.web-testing.yml` | Web testing with Playwright MCP |
-| `Dockerfile.playwright` | Custom Playwright container |
-
-### Usage
-
-```bash
-# Start from project root
-docker compose -f docker/docker-compose.web-testing.yml up -d
-
-# Or create alias
-alias dc='docker compose -f docker/docker-compose.web-testing.yml'
-dc up -d
-```
-
-## Scripts
-
-All utility scripts are in `scripts/`:
-
-| Script | Purpose |
-|--------|---------|
-| `web-test.sh` | Run web tests with Docker |
-
-### Usage
-
-```bash
-# Run from project root
-./scripts/web-test.sh https://your-app.com
-
-# With options
-./scripts/web-test.sh https://your-app.com --auto-fix
-./scripts/web-test.sh https://your-app.com --visual-only
-```
-
-## Tests
-
-All tests are in `tests/`:
-
-### Test Types
-
-| Directory | Test Type |
-|-----------|-----------|
-| `visual/` | Visual regression testing |
-| `console/` | Console error capture |
-| `links/` | Link checking results |
-| `forms/` | Form testing data |
-| `reports/` | HTML/JSON reports |
-
-### Running Tests
-
-```bash
-# From project root
-cd tests && npm install && npm test
-
-# Or use script
-./scripts/web-test.sh https://your-app.com
-```
-
-## Archive
-
-Deprecated files are in `archive/`:
-
-- Old scripts
-- Old documentation
-- Old test files
-
-Do not reference these files - they may be removed in future.
-
-## Kilo Code Structure
-
-`.kilo/` directory contains all Kilo Code configuration:
-
-### Agents (`.kilo/agents/`)
-
-Each agent has its own file with YAML frontmatter:
+Maps agent capabilities for orchestrator routing:
```yaml
----
-model: ollama-cloud/qwen3-coder:480b
-mode: subagent
-color: "#DC2626"
-description: Agent description
-permission:
- read: allow
- edit: allow
- write: allow
- bash: allow
- task:
- "*": deny
- "specific-agent": allow
----
+agents:
+ php-developer:
+ capabilities:
+ - php_web_development
+ - laravel_development
+ - symfony_development
+ - wordpress_development
+ model: ollama-cloud/qwen3-coder:480b
+ mode: subagent
+ delegates_to:
+ - code-skeptic
+ - security-auditor
+
+capability_routing:
+ php_web_development: php-developer
+ laravel_development: php-developer
```
-### Commands (`.kilo/commands/`)
+### kilo.jsonc
-Slash commands available in Kilo Code:
+Primary agents configuration:
-| Command | Purpose |
-|---------|---------|
-| `/web-test` | Run web tests |
-| `/web-test-fix` | Run tests with auto-fix |
-| `/pipeline` | Run agent pipeline |
+```jsonc
+{
+ "model": "ollama-cloud/glm-5.1",
+ "default_agent": "orchestrator",
+ "agent": {
+ "orchestrator": { "model": "ollama-cloud/glm-5.1", "variant": "thinking" },
+ "code": { "model": "ollama-cloud/qwen3-coder:480b" },
+ "ask": { "model": "ollama-cloud/glm-5.1", "variant": "instant" },
+ "plan": { "model": "ollama-cloud/nemotron-3-super" },
+ "debug": { "model": "ollama-cloud/glm-5.1", "variant": "thinking" }
+ }
+}
+```
-### Skills (`.kilo/skills/`)
+## Rules System
-Agent skills (capabilities):
+Rules in `.kilo/rules/` are loaded globally for all agents:
+
+### Critical Rules
+
+| Rule | Purpose |
+|------|---------|
+| `atomic-tasks.md` | 1 action = 1 task, max 100 lines/file, task sizing |
+| `modular-code.md` | Modules, services, repositories, events |
+| `token-optimimization.md` | Token budgets, no scope creep |
+| `gitea-centric-workflow.md` | Issues before work, progress tracking, research |
+| `global.md` | Base coding rules |
+
+### Language-Specific Rules
+
+| Rule | Purpose |
+|------|---------|
+| `php.md` | PSR-12, TDD, security |
+| `nodejs.md` | Express, JWT, async |
+| `go.md` | Error wrapping, context, table-driven tests |
+| `flutter.md` | Riverpod, Clean Architecture |
+| `docker.md` | Multi-stage, security |
+
+## Skills System
+
+Skills are capability modules agents reference:
+
+### PHP Development (NEW)
+
+| Skill | Lines | Purpose |
+|-------|-------|---------|
+| `php-laravel-patterns` | 403 | Routing, Eloquent, Services, Repositories, Auth, Queues |
+| `php-symfony-patterns` | 233 | Controllers, Doctrine, Messenger, Voters |
+| `php-wordpress-patterns` | 276 | Plugins, CPT, REST API, Security |
+| `php-security` | 147 | OWASP Top 10, CSRF, XSS, SQL injection |
+| `php-testing` | 242 | PHPUnit, Pest, Dusk browser tests |
+| `php-modular-architecture` | 242 | Module separation, interfaces, events |
+
+### Infrastructure
| Skill | Purpose |
|-------|---------|
-| `web-testing` | Web testing infrastructure |
-| `playwright` | Playwright MCP integration |
+| `gitea-workflow` | Issue creation, quality gates, progress tracking |
+| `gitea-commenting` | Comment format with auto-project detection |
+| `agent-logging` | Execution logging to agent-executions.jsonl |
+| `docker-compose` | Docker Compose patterns |
+| `docker-swarm` | Docker Swarm orchestration |
-### Rules (`.kilo/rules/`)
+## Execution Logging
-Global rules loaded for all agents:
+Every agent invocation is logged to `.kilo/logs/agent-executions.jsonl`:
-- `global.md` - Base rules
-- `lead-developer.md` - Developer rules
-- `code-skeptic.md` - Code review rules
-- etc.
+```jsonl
+{"ts":"2026-04-18T14:00:00Z","agent":"php-developer","issue":42,"project":"UniqueSoft/my-shop","task":"Create Product model","subtask_type":"model_creation","duration_ms":45000,"tokens_used":8500,"status":"success","files":["app/Models/Product.php"],"score":8,"next_agent":"code-skeptic"}
+```
+
+### Required Fields
+
+| Field | Description |
+|-------|-------------|
+| `agent` | Agent name |
+| `issue` | Gitea issue number |
+| `project` | Target project repo (auto-detected, NOT hardcoded) |
+| `task` | Atomic task description |
+| `duration_ms` | Execution time |
+| `tokens_used` | Token estimate |
+| `status` | success/fail/pass/blocked |
+| `score` | Self-assessment 1-10 |
+| `next_agent` | Next agent in pipeline |
+
+### Statistics
+
+```bash
+bun run agent:stats # Last 30 days
+bun run agent:stats:week # Last 7 days
+bun run agent:stats:project # Filter by project
+```
+
+## Gitea Integration
+
+### Critical: Target Project Detection
+
+Issues MUST be created in the target project, NOT in APAW:
+
+```python
+def get_target_repo():
+ """Auto-detect from git remote - NEVER hardcode"""
+ result = subprocess.run(['git', 'remote', 'get-url', 'origin'], capture_output=True, text=True)
+ match = re.search(r'[:/]([^/]+/[^/]+?)(?:\.git)?$', result.stdout.strip())
+ if match:
+ return match.group(1)
+ return os.environ.get('GITEA_TARGET_REPO', 'UniqueSoft/APAW')
+```
+
+All API calls use `get_target_repo()` instead of hardcoded repo.
## Environment Variables
-### Web Testing
-
| Variable | Default | Description |
|----------|---------|-------------|
-| `TARGET_URL` | `http://localhost:3000` | URL to test |
-| `PLAYWRIGHT_MCP_URL` | `http://localhost:8931/mcp` | MCP endpoint |
-| `PIXELMATCH_THRESHOLD` | `0.05` | Visual diff tolerance |
-| `AUTO_CREATE_ISSUES` | `false` | Auto-create Gitea issues |
+| `GITEA_API_URL` | `https://git.softuniq.eu/api/v1` | Gitea API endpoint |
| `GITEA_TOKEN` | - | Gitea API token |
-| `REPORTS_DIR` | `./tests/reports` | Output directory |
+| `GITEA_TARGET_REPO` | auto-detect | Override target project |
+| `GITEA_USER` | `NW` | Gitea username |
+| `GITEA_PASS` | - | Gitea password |
+| `TARGET_URL` | `http://localhost:3000` | URL for testing |
+| `PIXELMATCH_THRESHOLD` | `0.05` | Visual diff tolerance |
## Quick Reference
```bash
-# Start Docker containers
+# Pipeline for issue
+/pipeline 42
+
+# Laravel app
+/laravel project_name
+
+# WordPress plugin
+/wordpress plugin_name
+
+# Agent statistics
+bun run agent:stats
+
+# Evolution dashboard
+bun run sync:evolution
+bun run evolution:open
+
+# Docker containers
docker compose -f docker/docker-compose.web-testing.yml up -d
-# Run web tests
+# Web tests
./scripts/web-test.sh https://your-app.com
-
-# View reports
-open tests/reports/web-test-report.html
-
-# Stop containers
-docker compose -f docker/docker-compose.web-testing.yml down
```
\ No newline at end of file
diff --git a/package.json b/package.json
index cb00948..98a98b8 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,10 @@
"evolution:stop": "docker stop apaw-evolution-dashboard && docker rm apaw-evolution-dashboard",
"evolution:start": "bash agent-evolution/docker-run.sh run",
"evolution:dev": "docker-compose -f docker-compose.evolution.yml up -d",
- "evolution:logs": "docker logs -f apaw-evolution-dashboard"
+ "evolution:logs": "docker logs -f apaw-evolution-dashboard",
+ "agent:stats": "bun run scripts/agent-stats.ts",
+ "agent:stats:week": "bun run scripts/agent-stats.ts --last 7",
+ "agent:stats:project": "bun run scripts/agent-stats.ts --project"
},
"dependencies": {
"zod": "^3.24.1"
diff --git a/scripts/agent-stats.ts b/scripts/agent-stats.ts
new file mode 100644
index 0000000..46f951d
--- /dev/null
+++ b/scripts/agent-stats.ts
@@ -0,0 +1,192 @@
+#!/usr/bin/env bun
+/**
+ * Agent Stats - Analyze agent execution logs
+ *
+ * Usage:
+ * bun run scripts/agent-stats.ts
+ * bun run scripts/agent-stats.ts --last 7
+ * bun run scripts/agent-stats.ts --project UniqueSoft/my-shop
+ */
+
+import { readFileSync, existsSync } from 'fs';
+import { join } from 'path';
+
+interface AgentExecution {
+ ts: string;
+ agent: string;
+ issue: number;
+ project: string;
+ task: string;
+ subtask_type: string;
+ duration_ms: number;
+ tokens_used: number;
+ status: string;
+ files: string[];
+ score: number | null;
+ next_agent: string | null;
+}
+
+interface AgentStats {
+ calls: number;
+ avgDuration: number;
+ avgTokens: number;
+ avgScore: number;
+ successRate: number;
+ totalDuration: number;
+ totalTokens: number;
+}
+
+function parseArgs(): { lastDays: number; project: string | null } {
+ const args = process.argv.slice(2);
+ let lastDays = 30;
+ let project: string | null = null;
+
+ for (let i = 0; i < args.length; i++) {
+ if (args[i] === '--last' && args[i + 1]) {
+ lastDays = parseInt(args[i + 1], 10);
+ }
+ if (args[i] === '--project' && args[i + 1]) {
+ project = args[i + 1];
+ }
+ }
+
+ return { lastDays, project };
+}
+
+function loadExecutions(logPath: string): AgentExecution[] {
+ if (!existsSync(logPath)) {
+ console.log('No execution log found. Start using agents to build history.');
+ return [];
+ }
+
+ const content = readFileSync(logPath, 'utf-8');
+ return content
+ .split('\n')
+ .filter(line => line.trim())
+ .map(line => {
+ try { return JSON.parse(line); }
+ catch { return null; }
+ })
+ .filter((e): e is AgentExecution => e !== null);
+}
+
+function filterByDate(executions: AgentExecution[], days: number): AgentExecution[] {
+ const cutoff = new Date();
+ cutoff.setDate(cutoff.getDate() - days);
+ return executions.filter(e => new Date(e.ts) >= cutoff);
+}
+
+function filterByProject(executions: AgentExecution[], project: string): AgentExecution[] {
+ return executions.filter(e => e.project === project);
+}
+
+function computeStats(executions: AgentExecution[]): Map {
+ const stats = new Map();
+
+ for (const e of executions) {
+ const existing = stats.get(e.agent) || {
+ calls: 0,
+ avgDuration: 0,
+ avgTokens: 0,
+ avgScore: 0,
+ successRate: 0,
+ totalDuration: 0,
+ totalTokens: 0,
+ };
+
+ existing.calls++;
+ existing.totalDuration += e.duration_ms;
+ existing.totalTokens += e.tokens_used;
+ if (e.score) existing.avgScore = (existing.avgScore * (existing.calls - 1) + e.score) / existing.calls;
+ if (e.status === 'success' || e.status === 'pass') {
+ existing.successRate = (existing.successRate * (existing.calls - 1) + 1) / existing.calls;
+ }
+
+ stats.set(e.agent, existing);
+ }
+
+ // Compute averages
+ for (const [, s] of stats) {
+ s.avgDuration = s.calls > 0 ? s.totalDuration / s.calls : 0;
+ s.avgTokens = s.calls > 0 ? s.totalTokens / s.calls : 0;
+ }
+
+ return stats;
+}
+
+function formatDuration(ms: number): string {
+ if (ms < 1000) return `${ms}ms`;
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
+ return `${(ms / 60000).toFixed(1)}m`;
+}
+
+function formatTokens(tokens: number): string {
+ if (tokens < 1000) return `${tokens}`;
+ return `${(tokens / 1000).toFixed(1)}k`;
+}
+
+const logPath = join(process.cwd(), '.kilo', 'logs', 'agent-executions.jsonl');
+const { lastDays, project } = parseArgs();
+
+let executions = loadExecutions(logPath);
+
+if (executions.length === 0) {
+ console.log('\n📊 Agent Stats - No data yet\n');
+ console.log('Log file:', logPath);
+ console.log('Start using agents to build execution history.\n');
+ process.exit(0);
+}
+
+if (lastDays < 9999) {
+ executions = filterByDate(executions, lastDays);
+}
+
+if (project) {
+ executions = filterByProject(executions, project);
+}
+
+const stats = computeStats(executions);
+
+console.log(`\n📊 Agent Stats (Last ${lastDays} days${project ? `, Project: ${project}` : ''})`);
+console.log('═'.repeat(70));
+
+const sortedStats = [...stats.entries()].sort((a, b) => b[1].calls - a[1].calls);
+
+for (const [agent, s] of sortedStats) {
+ console.log(
+ `${agent.padEnd(20)} ${String(s.calls).padStart(3)} calls, ` +
+ `avg ${formatDuration(s.avgDuration).padStart(6)}, ` +
+ `score ${s.avgScore.toFixed(1)}/10, ` +
+ `${(s.successRate * 100).toFixed(0)}% success, ` +
+ `~${formatTokens(s.avgTokens)} tokens`
+ );
+}
+
+console.log('═'.repeat(70));
+console.log(`Total: ${executions.length} executions\n`);
+
+// Project breakdown
+const projects = new Map();
+for (const e of executions) {
+ projects.set(e.project, (projects.get(e.project) || 0) + 1);
+}
+
+if (projects.size > 1) {
+ console.log('📁 By Project:');
+ for (const [proj, count] of projects) {
+ console.log(` ${proj}: ${count} executions`);
+ }
+ console.log('');
+}
+
+// Status breakdown
+const statuses = new Map();
+for (const e of executions) {
+ statuses.set(e.status, (statuses.get(e.status) || 0) + 1);
+}
+
+console.log('📈 By Status:');
+for (const [status, count] of statuses) {
+ console.log(` ${status}: ${count}`);
+}
+console.log('');
\ No newline at end of file