From b46a1a20a8ea028eaf4365bd785c2d9a6847119a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A8NW=C2=A8?= <¨neroworld@mail.ru¨> Date: Sat, 18 Apr 2026 23:43:04 +0100 Subject: [PATCH] feat: add PHP development stack, atomic tasks, modular code rules, agent monitoring, fix target project detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 7 evolutionary tasks implemented: 1. PHP web development: php-developer agent + 6 skills (Laravel, Symfony, WordPress, security, testing, modular architecture) + 2 pipeline commands (/laravel, /wordpress) 2. Atomic task decomposition: 1 action = 1 task rule, task sizing guide, decomposition protocol for orchestrator, token budgets per complexity 3. Modular code rules: max 100 lines/file, max 30 lines/function, service/repository patterns, cross-module communication via events only 4. Gitea-centric workflow: mandatory issue creation before work, research with links, progress checkboxes, screenshots on test, git history as knowledge base 5. Fix: target project auto-detection — removed all hardcoded UniqueSoft/APAW from API calls, added get_target_repo() via git remote, GITEA_TARGET_REPO env override 6. Agent execution monitoring: agent-executions.jsonl logging, agent-stats.ts statistics script, required fields per invocation, Gitea comment includes duration/tokens 7. Token optimization: 1 action = 1 task principle, token budgets by task type, routing matrix, no scope creep, skip unnecessary pipeline steps --- .kilo/EVOLUTION_LOG.md | 77 ++++ .kilo/agents/orchestrator.md | 2 + .kilo/agents/php-developer.md | 65 +++ .kilo/capability-index.yaml | 41 ++ .kilo/commands/laravel.md | 225 ++++++++++ .kilo/commands/pipeline.md | 19 +- .kilo/commands/wordpress.md | 131 ++++++ .kilo/logs/agent-executions.jsonl | 1 + .kilo/rules/atomic-tasks.md | 102 +++++ .kilo/rules/gitea-centric-workflow.md | 206 +++++++++ .kilo/rules/modular-code.md | 200 +++++++++ .kilo/rules/token-optimization.md | 163 +++++++ .kilo/shared/gitea-api.md | 95 ++++- .kilo/skills/agent-logging/SKILL.md | 160 +++++++ .kilo/skills/gitea-commenting/SKILL.md | 43 +- .kilo/skills/gitea-workflow/SKILL.md | 98 +++-- .kilo/skills/gitea/SKILL.md | 12 +- .kilo/skills/php-laravel-patterns/SKILL.md | 403 ++++++++++++++++++ .../skills/php-modular-architecture/SKILL.md | 242 +++++++++++ .kilo/skills/php-security/SKILL.md | 147 +++++++ .kilo/skills/php-symfony-patterns/SKILL.md | 233 ++++++++++ .kilo/skills/php-testing/SKILL.md | 242 +++++++++++ .kilo/skills/php-wordpress-patterns/SKILL.md | 276 ++++++++++++ AGENTS.md | 70 +++ README.md | 236 ++++++---- STRUCTURE.md | 348 ++++++++------- package.json | 5 +- scripts/agent-stats.ts | 192 +++++++++ 28 files changed, 3760 insertions(+), 274 deletions(-) create mode 100644 .kilo/agents/php-developer.md create mode 100644 .kilo/commands/laravel.md create mode 100644 .kilo/commands/wordpress.md create mode 100644 .kilo/logs/agent-executions.jsonl create mode 100644 .kilo/rules/atomic-tasks.md create mode 100644 .kilo/rules/gitea-centric-workflow.md create mode 100644 .kilo/rules/modular-code.md create mode 100644 .kilo/rules/token-optimization.md create mode 100644 .kilo/skills/agent-logging/SKILL.md create mode 100644 .kilo/skills/php-laravel-patterns/SKILL.md create mode 100644 .kilo/skills/php-modular-architecture/SKILL.md create mode 100644 .kilo/skills/php-security/SKILL.md create mode 100644 .kilo/skills/php-symfony-patterns/SKILL.md create mode 100644 .kilo/skills/php-testing/SKILL.md create mode 100644 .kilo/skills/php-wordpress-patterns/SKILL.md create mode 100644 scripts/agent-stats.ts 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 +![Test Result](/attachments/{uuid}) + +### 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 +
+ @csrf + ... +
+ +// 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