CQRSTemplate
CLAUDE.md0 starsChanged 6 months agoMITView on GitHub
This project uses Serena MCP for semantic code intelligence. ALL code generated by AI agents MUST follow Serena optimization guidelines. Serena works at the SYMBOL level (classes, methods, properties), not the TEXT level (strings). Properly structured PHP code enables: - ✅ Instant symbol finding across the entire codebase - ✅ Precise code editing at symbol boundaries - ✅ Accurate refactoring across multiple files - ✅ Efficient navigation without reading entire files - ✅ 10x faster development with AI assistance ✅…
- Reads credentials
- Deletes or force-pushes
- Commits and pushes
In this repository14 other files it gives its agents.
- api-endpoint-creationSkill
- database-migrationsSkill
- local-dev-testingSkill
- testing-cqrs-securitySkill
- business-rule-additionSkill
- chrome-devtools-expertSkill
- code-reviewSkill
- context7-docsSkill
- domain-scaffoldingSkill
- markitdown-converterSkill
- playwright-automationSkill
- property-additionSkill
- serena-code-generatorSkill
- strategic-plannerSkill
The file
Show its rules and text
We split this CLAUDE.md into the rules it gives an agent
62 rules. About 0 are common, 41 are distinctive, and 21 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈41
Distinctive · estimate
This author's own choices. Worth a look.
21
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 21
Use clear class names (Single Responsibility Principle)
- Distinctive: line 22
Write small methods (< 50 lines, prefer < 20 lines)
- Distinctive: line 24
Keep flat structures (clear namespace hierarchy, PSR-4)
- Distinctive: line 25
Add DocBlocks for all public APIs
- Distinctive: line 139
All code reviews by agents must verify:
- Distinctive: line 180
Never
git push --force: Use git push --force-with-lease --force-if-includes (alias: git fpush). - Distinctive: line 188
Do not disable signing.
- Very specific: line 189
AI-assisted commits: must include a Co-Authored-By: trailer.
- Distinctive: line 201
Commit messages that don't match Conventional Commits
- Distinctive: line 216
All status checks passing
- Very specific: line 241
If any of those don't match, run bin/setup-hooks (or composer install, which runs it automatically via post-install).
- Distinctive: line 258
BEFORE making ANY code changes, you MUST:
- Very specific: line 259
Use phpstan-specialist to check current state
- Very specific: line 260
Use slevomat-specialist to verify coding standards
- Very specific: line 261
Use test-specialist if modifying existing features
- Distinctive: line 265
WHEN writing code, you MUST:
- Very specific: line 266
Use php-specialist for all PHP files
- Very specific: line 267
Use clean-code-specialist for all methods/classes
- Very specific: line 268
Use cqrs-specialist for commands/queries/events/handlers
- Very specific: line 269
Use ddd-specialist for entities/value objects
- Very specific: line 270
Use codeigniter4-specialist for migrations/controllers/routing
- Very specific: line 271
Use test-specialist when adding or modifying features
- Distinctive: line 275
AFTER making changes, you MUST:
- Very specific: line 276
Run phpstan-specialist (MUST pass Level 8 with 0 errors)
- Very specific: line 277
Run slevomat-specialist (MUST pass with 0 violations)
- Very specific: line 278
Run test-specialist (MUST maintain 90% coverage)
- Distinctive: line 330
Every task MUST satisfy SMART-E criteria:
- Distinctive: line 368
When user asks: *"Do I have any unfinished tasks?"* or *"Resume my tasks"*
- Very specific: line 384
Read .claude/planning/current/execution.json for complete state
- Very specific: line 385
Read .claude/planning/current/tasks.json for task definitions
- Very specific: line 386
Check todowrite_mapping to find pending tasks
- Distinctive: line 391
Verify any claimed completions using verification_command
- Distinctive: line 393
Update execution state as progress is made
- Distinctive: line 402
Verify what's completed: - Check TodoWrite and execution log:
- Distinctive: line 405
Use verification commands to confirm claimed completions
- Distinctive: line 409
Start from that task's group
- Distinctive: line 410
Update execution state as you progress
- Distinctive: line 438
ALWAYS use native tools: instead of bash commands for file operations:
- Distinctive: line 454
Do NOT ask for permission to use Read, Write, Edit, Glob, Grep tools
- Distinctive: line 455
Do NOT wait for user confirmation to invoke specialists
- Distinctive: line 456
Do proceed with quality fixes automatically (phpcbf, etc.)
- Distinctive: line 457
Do execute multiple specialists in parallel without asking
- Distinctive: line 458
Do reject non-compliant code automatically (don't ask)
- Very specific: line 496
Use claude-code-specialist agent for guidance
- Very specific: line 497
Follow existing patterns in .claude/agents/, .claude/skills/, .claude/commands/
- Distinctive: line 498
Test with simple example first
- Distinctive: line 505
Document which other specialists to collaborate with
- Distinctive: line 608
All logs are in JSON format for AI parsing and analysis.
- Distinctive: line 667
Use dot notation for channel names:
- Distinctive: line 733
Use appropriate log levels:
- Distinctive: line 761
Only replace LoggingServiceProvider with framework-specific adapter
- Distinctive: line 762
All handlers keep their logging code
- Distinctive: line 884
Value Objects and Entities: do NOT use DomainLogger directly.
- Distinctive: line 1033
All parameters have type declarations
- Distinctive: line 1034
All return types declared
- Very specific: line 1036
Use === for all comparisons
- Distinctive: line 1043
No business logic in controllers
- Distinctive: line 1131
All code uses guard clauses and early returns for readability.
- Very specific: line 1134
All code uses declare(strict_types=1) and full type hints (PHPStan level 8).
- Distinctive: line 1137
All entities support soft deletion (deleted_at timestamp).
- Distinctive: line 1140
All DTOs are readonly classes for thread safety and clarity.
- Distinctive: line 1202
Use Cookie domain as a reference for all new domains.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Project Memory - CodeIgniter 4 CQRS Template
🎯 CRITICAL: Serena-Optimized Code (MANDATORY)
This project uses Serena MCP for semantic code intelligence.
ALL code generated by AI agents MUST follow Serena optimization guidelines.
Why Serena Optimization Matters
Serena works at the SYMBOL level (classes, methods, properties), not the TEXT level (strings). Properly structured PHP code enables:
- ✅ Instant symbol finding across the entire codebase
- ✅ Precise code editing at symbol boundaries
- ✅ Accurate refactoring across multiple files
- ✅ Efficient navigation without reading entire files
- ✅ 10x faster development with AI assistance
Mandatory Code Patterns (PHP)
✅ ALWAYS:
- Use clear class names (Single Responsibility Principle)
- Write small methods (< 50 lines, prefer < 20 lines)
- Give descriptive names (PSR-12 compliant, self-documenting)
- Keep flat structures (clear namespace hierarchy, PSR-4)
- Add DocBlocks for all public APIs
- One clear symbol per logical unit
- Strict types (
declare(strict_types=1)) - Type hints for all parameters and returns
- Readonly for Value Objects
- Final classes by default
❌ NEVER:
- Generic service classes (
UserServicewith 50 methods) - Anonymous classes or closures as primary logic
- Deeply nested structures
- Mega-classes (> 300 lines)
- Dynamic method calls (
$obj->$method()) - Static helper classes with mixed responsibilities
- Missing DocBlocks
- Generic names (
Utils,Helper,Manager)
CQRS-Specific Symbol Patterns
Commands (Write Operations):
// ✅ GOOD - Each command is a clear, discoverable symbol
final readonly class CreateUserCommand
{
public function __construct(
public string $email,
public string $name
) {}
}
final readonly class CreateUserCommandHandler
{
public function handle(CreateUserCommand $command): UserId
{
$this->validateCommand($command);
$user = $this->createUser($command);
$this->saveUser($user);
return $user->getId();
}
private function validateCommand(CreateUserCommand $command): void {}
private function createUser(CreateUserCommand $command): User {}
private function saveUser(User $user): void {}
}Value Objects:
// ✅ GOOD - Immutable, self-validating, discoverable
final class Email
{
private function __construct(private readonly string $value)
{
$this->validate();
}
public static function fromString(string $email): self
{
return new self($email);
}
public function getValue(): string
{
return $this->value;
}
private function validate(): void
{
if (!$this->isValidFormat()) {
throw new \InvalidArgumentException('Invalid email');
}
}
private function isValidFormat(): bool
{
return filter_var($this->value, FILTER_VALIDATE_EMAIL) !== false;
}
}Complete Guidelines
See: .claude/SERENA_CODE_OPTIMIZATION.md for comprehensive PHP-specific guidelines, examples, and patterns.
AI agents MUST review this document before generating code.
serena-code-generator Skill
The serena-code-generator skill is available for this project.
This skill provides templates for:
- Value Objects (immutable, validated)
- Commands and Command Handlers (CQRS)
- Queries and Query Handlers (CQRS)
- Domain Entities (DDD patterns)
- Controllers (CodeIgniter4)
Usage: The skill activates automatically when generating new code.
serena-code-assistant Agent
The serena-code-assistant agent uses Serena MCP for semantic code navigation.
Available operations:
find_symbol("ClassName")→ Find exact classfind_symbol("ClassName/methodName")→ Find specific methodfind_referencing_symbols("ClassName")→ Find all usagesreplace_symbol_body("method")→ Replace method implementationinsert_after_symbol("method")→ Add code after method
Example: "Find all usages of CreateUserCommand and add logging"
Enforcement
All code reviews by agents must verify:
- Named classes with clear purposes
- Methods < 50 lines (prefer < 20 lines)
- Descriptive symbol names (PSR-12 compliant)
- Flat namespace structure (PSR-4)
- DocBlocks for all public APIs
- No circular dependencies
- Clear module boundaries
- Strict type declarations
- CQRS/DDD patterns followed
Serena-incompatible code will be REJECTED.
Integration with Existing Specialists
Serena optimization works WITH existing specialists:
- php-specialist → Ensures PHP syntax + Serena patterns
- cqrs-specialist → CQRS patterns + Serena symbols
- ddd-specialist → DDD patterns + Serena value objects
- clean-code-specialist → Small methods + Serena structure
🔒 Git Conventions (MANDATORY)
This project enforces git policy at THREE layers. All three must pass.
| Layer | Where | Skippable? |
| Local git hooks | .githooks/ | --no-verify (blocked) |
| Claude Code PreToolUse hook | .claude/hooks/git-guard.sh | env-var GIT_GUARD_DISABLE=1 |
| GitHub status checks + ruleset | .github/workflows/ + ruleset | server-side, no bypass |
Hard rules — all agents
- Use the
git-specialistagent for any git operation. It enforces the rules in this section. Other specialists must delegate to it. - Conventional Commits 1.0 — required. Format:
type(scope)[!]: subject. Allowed types:feat | fix | docs | style | refactor | perf | test | build | ci | chore | revert. Allowed scopes are listed in.commitlintrc.json(scope-enum). - Never use
--no-verify. Fix the hook complaint instead. - Never
git push --force. Usegit push --force-with-lease --force-if-includes(alias:git fpush). - Never commit directly to
mainunless the user says the exact phrase "commit directly to main". Default flow: branch → commit → PR. - Branch names:
<type>/<scope>-<short-description>, kebab-case. Include the issue number when one exists:fix/order-123-negative-qty. Theprepare-commit-msghook auto-prefixes subjects from this. - Signed commits: commits are SSH-signed via
~/.ssh/git_signing_ed25519. Do not disable signing. - AI-assisted commits must include a
Co-Authored-By:trailer.
What the layers block
The local hooks + git-guard.sh reject (with helpful hints):
git commit --no-verify ...git push --no-verify ...git push --force/git push -fgit reset --hard origin/<branch>(silent work loss)git config --global / --system(must stay local)- Commits on
main/master - Commit messages that don't match Conventional Commits
- Staged files matching
.env*,secrets/,*.pem,*.p12,*.key - Real secrets found by
gitleakscontent scan
Server-side enforcement
On every push and PR to main:
ci.yml— PHPStan L8, PHPCS, PHPUnit (≥90% coverage), gitleakscommitlint.yml— re-validates every commit message in the PRdependency-review.yml— blocks high-severity CVEs + non-allowed licensescodeql.yml— SAST on Actions and JS/TSscorecard.yml— weekly OpenSSF supply-chain score- Repository ruleset on
mainrequires:- PR (no direct pushes)
- All status checks passing
- Linear history, no force-pushes, no deletions, signed commits
Aliases worth knowing
git lg # pretty graph log
git recent # branches sorted by last activity
git sync # pull --rebase --autostash && push
git cleanup # delete branches already merged into main
git fpush # push --force-with-lease --force-if-includes
git aliases # list every alias defined in this repo
git staged # show staged diff
git wipe # save a WIP commit then drop it (safe scratch reset)Verify your setup
git config --local --get core.hooksPath # → .githooks
git config --local --get commit.gpgsign # → true
git config --local --get user.signingkey # → ~/.ssh/git_signing_ed25519.pub
git log --show-signature -1 # last commit signed?If any of those don't match, run bin/setup-hooks (or composer install,
which runs it automatically via post-install).
See also
.claude/agents/git-specialist.md— full playbook.github/CONTRIBUTING.md— developer flow.githooks/— local hook scripts.claude/hooks/git-guard.sh— agent-level enforcement.gitmessage— commit template
Mandatory Agent Usage (Orchestrator Pattern)
Pre-Execution Rules
BEFORE making ANY code changes, you MUST:
- Use
phpstan-specialistto check current state - Use
slevomat-specialistto verify coding standards - Use
test-specialistif modifying existing features
Execution Rules
WHEN writing code, you MUST:
- Use
php-specialistfor all PHP files - Use
clean-code-specialistfor all methods/classes - Use
cqrs-specialistfor commands/queries/events/handlers - Use
ddd-specialistfor entities/value objects - Use
codeigniter4-specialistfor migrations/controllers/routing - Use
test-specialistwhen adding or modifying features
Post-Execution Rules
AFTER making changes, you MUST:
- Run
phpstan-specialist(MUST pass Level 8 with 0 errors) - Run
slevomat-specialist(MUST pass with 0 violations) - Run
test-specialist(MUST maintain 90% coverage)
Orchestrator Pattern - Parallel Delegation
For complex tasks, AUTOMATICALLY delegate to 2-3 specialists in PARALLEL:
- Add new property →
ddd-specialist+test-specialist+clean-code-specialist - Create new domain →
cqrs-specialist+ddd-specialist+test-specialist - Refactor code →
clean-code-specialist+php-specialist+test-specialist - Add business rule →
ddd-specialist+test-specialist+phpstan-specialist - Fix bug →
test-specialist+php-specialist+phpstan-specialist
Rejection Policy
REJECT any code changes that:
- Fail PHPStan Level 8
- Fail Slevomat checks with any violations
- Reduce test coverage below 90%
- Have methods exceeding 20 lines
- Violate CQRS/DDD patterns
- Lack tests for new features
Mandatory Planning Rules (Strategic Planner Skill)
CRITICAL: Always use the strategic-planner skill for ANY planning task.
Automatically invoke strategic-planner when:
- User asks "How do I implement [feature]?"
- User requests "Create a plan for [task]"
- User says "Break down [complex work]"
- User asks "What steps are needed to [goal]?"
- User requests todo list generation
- Task involves 5+ steps or multiple files (15+)
- Task is HIGH or CRITICAL risk (auth, payment, data migration)
- Task is COMPLEX or VERY COMPLEX
The skill provides:
- 🌳 Tree-of-Thought: Explores 2-3 solution approaches for complex tasks
- 💭 Chain-of-Thought: Shows transparent reasoning process
- ⚛️ SMART-E Atomic Tasks: Generates precise, executable task definitions
- 🐍 Python Validation: Atomicity validator + dependency analyzer
- 📊 Critical Path Optimization: 25-50% time savings via parallelization
- 🎓 Pattern Learning: Improves recommendations over time
5-Phase Planning Process:
- Understanding & Analysis (Chain-of-Thought)
- Explore Solution Space (Tree-of-Thought for complex tasks)
- Risk Assessment (Pre-Mortem for HIGH/CRITICAL risk)
- Generate SMART-E Atomic Tasks (with all 6 criteria + 5 atomicity rules)
- Python Validation & Optimization (critical path analysis)
SMART-E + Atomicity Requirements: Every task MUST satisfy SMART-E criteria:
- Specific, Measurable, Achievable, Relevant, Testable, Executable
AND 5 Atomicity Rules:
- ≤3 files | <30 min | Binary done | Independent verify | Single responsibility
Rejection Policy: REJECT and RE-PLAN if Python validator returns FAIL or detects:
- ❌ Atomicity violations
- ❌ Circular dependencies
- ❌ Missing SMART-E criteria
- ❌ No verification commands
Quick Example:
User: "Add payment system"
→ Invoke: Skill strategic-planner
→ Phase 1: CoT understanding (VERY COMPLEX, HIGH risk)
→ Phase 2: ToT exploration (Stripe vs PayPal vs Custom)
→ Phase 3: Pre-Mortem (7 risks identified, mitigated)
→ Phase 4: 47 SMART-E atomic tasks generated
→ Phase 5: Python validation (PASS, 42 min critical path, 50% time savings)
→ Present plan → User confirms → Execute with TodoWriteProgressive Disclosure: The skill uses lightweight core documentation with detailed references:
- Core:
.claude/skills/strategic-planner/SKILL.md(~300 lines) - Frameworks: SMART-E, Atomicity, ToT, CoT (separate files)
- Guides: Python scripts, TodoWrite integration
- Examples: Simple (3 tasks) and complex (47 tasks) scenarios
See: .claude/skills/strategic-planner/SKILL.md for complete documentation
Resuming Interrupted Executions
Automatic Unfinished Task Detection:
When user asks: "Do I have any unfinished tasks?" or "Resume my tasks"
Claude Code should automatically:
- Check for unfinished executions:
# Check if current symlink exists and points to active execution if [ -L .claude/planning/current ]; then STATUS=$(jq -r '.status' .claude/planning/current/execution.json 2>/dev/null) if [ "$STATUS" = "in_progress" ] || [ "$STATUS" = "paused" ]; then echo "Found unfinished execution!" fi fi - Load execution context:
- Read
.claude/planning/current/execution.jsonfor complete state - Read
.claude/planning/current/tasks.jsonfor task definitions - Check
todowrite_mappingto find pending tasks
- Read
- Resume from next pending task:
- Find first task with
"status": "pending"in todowrite_mapping - Load task details from tasks.json
- Verify any claimed completions using verification_command
- Continue execution from current_group
- Update execution state as progress is made
- Find first task with
Quick Recovery (3 steps):
- Read execution state - Get complete context:
cat .claude/planning/current/execution.json - Verify what's completed - Check TodoWrite and execution log:
- TodoWrite shows user-visible progress with
[task_X.Y]references - Execution state shows completed_tasks, pending_tasks, current_group
- Use verification commands to confirm claimed completions
- TodoWrite shows user-visible progress with
- Resume from next task - Continue execution:
- Find first pending task in todowrite_mapping
- Start from that task's group
- Update execution state as you progress
Check for unfinished executions manually:
# List all in-progress or paused executions
find .claude/planning/executions -name "execution.json" -exec sh -c \
'jq -r "select(.status == \"in_progress\" or .status == \"paused\") | .execution_id" "$1"' _ {} \;
# Or check current execution
jq -r '.status, .plan_name, .completed_tasks, .total_tasks' .claude/planning/current/execution.jsonAll execution history is preserved:
- Execution folders never deleted:
.claude/planning/executions/exec-{id}/ - Complete state in
execution.json - Task definitions in
tasks.json - Optional metadata in
metadata.json - Symlink for easy access:
.claude/planning/current/
Validate execution integrity:
python .claude/skills/strategic-planner/scripts/validate_execution.py exec-{id}See: .claude/documentation/TASK-RECOVERY-GUIDE.md for detailed recovery workflows
Tool Usage for Maximum Autonomy
ALWAYS use native tools instead of bash commands for file operations:
File Operations:
- ✅ Use
Readtool to read files (NOTcat,head,tail) - ✅ Use
Writetool to create files (NOTecho >,cat <<EOF) - ✅ Use
Edittool to modify files (NOTsed,awk) - ✅ Use
Globtool to find files (NOTfind,ls) - ✅ Use
Greptool to search code (NOTgrep,rg)
Bash Tool Reserved For:
- ✅ Running commands:
composer,php spark,vendor/bin/* - ✅ Database operations:
mysql - ✅ Git operations:
git status,git diff - ✅ Build tools:
npm,webpack
Autonomous Behavior:
- Do NOT ask for permission to use Read, Write, Edit, Glob, Grep tools
- Do NOT wait for user confirmation to invoke specialists
- Do proceed with quality fixes automatically (phpcbf, etc.)
- Do execute multiple specialists in parallel without asking
- Do reject non-compliant code automatically (don't ask)
Be PROACTIVE and AUTONOMOUS:
- See a PHP file? → Automatically invoke
php-specialist - See a long method? → Automatically invoke
clean-code-specialist - See missing tests? → Automatically invoke
test-specialistand create them - See PHPStan errors? → Automatically fix them
- See Slevomat violations? → Run
phpcbfand fix automatically
File Organization Rules (MANDATORY)
Documentation Files:
- ❌ NEVER create .md files in root directory (summaries, reports, etc.)
- ✅ ALWAYS update existing .md files in
.claude/documentation/ - ✅ Add new sections to existing files instead of creating new ones
- ✅ If truly new topic, create in
.claude/documentation/only
Code Files:
- ❌ NEVER create .php or .sh files in root directory
- ✅ ALWAYS use
temp/folder for temporary/experimental files - ✅ Create
temp/folder if it doesn't exist:mkdir -p temp - ✅ Production code goes in proper domain directories
Examples:
# ❌ Wrong
Write(SUMMARY.md, ...) # Root directory
Write(test-script.php, ...) # Root directory
# ✅ Correct
Edit(.claude/documentation/COMPLETE_FILE_INVENTORY.md, ...) # Update existing
Write(temp/experimental.php, ...) # Temporary filesCreating New Agents, Skills, and Commands
Before creating new agent/skill/command:
- Research Claude Code documentation for best practices
- Use
claude-code-specialistagent for guidance - Follow existing patterns in
.claude/agents/,.claude/skills/,.claude/commands/ - Test with simple example first
Agent Creation Guidelines:
- YAML frontmatter with
name,description,tools - Concise description with "Use PROACTIVELY" or "MUST BE USED"
- Max 200 lines total (focused responsibility)
- Include common violations & fixes section
- Document which other specialists to collaborate with
Skill Creation Guidelines:
- SKILL.md with YAML frontmatter
- Supporting files in same directory
- Multi-step workflows that reuse specialists
- Include examples and usage documentation
Command Creation Guidelines:
- Markdown file with clear command syntax
- Interactive prompts if needed
- Delegate to skills or specialists
- Include usage examples
Use claude-code-specialist for creating new Claude Code extensions.
Architecture Quick Reference
CQRS Pattern
Commands (Write Operations)
- Represent intent to change system state
- Named in imperative (CreateCookie, UpdateCookie)
- Handled by exactly ONE handler
- May dispatch domain events
- Located in:
app/Domain/{Domain}/Commands/{CommandName}/ - Each command has its own folder containing Command + Handler
Queries (Read Operations)
- Represent requests for data (no state changes)
- Named as questions (GetCookieById, GetAllCookies)
- Handled by exactly ONE handler
- NEVER modify state
- Located in:
app/Domain/{Domain}/Queries/{QueryName}/ - Each query has its own folder containing Query + Handler
Events (Domain Events)
- Represent things that happened
- Named in past tense (CookieCreated, CookieDeleted)
- Can have MULTIPLE listeners
- Immutable (readonly class)
- Located in:
app/Domain/{Domain}/Events/{EventName}/ - Each event has its own folder with Event + EventHandler
Directory Structure
app/Domain/{Domain}/
├── {Domain}ServiceProvider.php # Auto-discovered with #[DomainServiceProvider]
├── Commands/
│ ├── Create{Entity}/
│ │ ├── Create{Entity}Command.php
│ │ └── Create{Entity}Handler.php
│ ├── Update{Entity}/
│ └── Delete{Entity}/
├── Queries/
│ ├── Get{Entity}ById/
│ │ ├── Get{Entity}ByIdQuery.php
│ │ └── Get{Entity}ByIdHandler.php
│ ├── GetAll{Entities}/
│ └── Get{Entities}Paginated/
├── Events/
│ ├── {Entity}Created/
│ │ ├── {Entity}CreatedEvent.php
│ │ └── {Entity}CreatedEventHandler.php
│ ├── {Entity}Updated/
│ └── {Entity}Deleted/
├── Entities/
│ └── {Entity}.php
└── ValueObjects/
├── {Entity}Name.php
└── {Entity}Price.phpComplete File List
45+ files/touchpoints per domain:
- See
.claude/documentation/COMPLETE_FILE_INVENTORY.mdfor detailed list
Logging Architecture
Framework-Agnostic Logging with Monolog
This template uses Monolog for logging, ensuring the logging infrastructure survives framework changes.
Key Components:
LoggerFactory- PSR-3 compliant factory for creating logger instancesLoggingServiceProvider- CodeIgniter service provider (framework adapter)Local Logs MCP Server- AI-readable log access via Model Context Protocol
Log File Locations
writable/logs/
├── app-2025-10-22.json # Application logs (JSON format)
├── app-2025-10-23.json # Daily rotation (30-day retention)
└── .gitkeepAll logs are in JSON format for AI parsing and analysis.
Using Logging in CQRS Handlers
Inject PSR-3 LoggerInterface in handler constructors:
use Psr\Log\LoggerInterface;
final readonly class CreateCookieHandler implements CommandHandlerInterface
{
public function __construct(
private CookieRepository $repository,
private EventDispatcherInterface $eventDispatcher,
private LoggerInterface $logger, // ← Inject logger
) {}
public function handle(CommandInterface $command): int
{
assert($command instanceof CreateCookieCommand);
// Log command start with context
$this->logger->info('Creating cookie', [
'domain' => 'Cookie',
'command' => 'CreateCookieCommand',
'cookieName' => $command->name,
'price' => $command->price,
]);
try {
$cookie = Cookie::create(/* ... */);
$cookieId = $this->repository->save($cookie);
// Log success
$this->logger->info('Cookie created successfully', [
'domain' => 'Cookie',
'command' => 'CreateCookieCommand',
'cookieId' => $cookieId,
]);
return $cookieId;
} catch (\Throwable $e) {
// Log errors
$this->logger->error('Failed to create cookie', [
'domain' => 'Cookie',
'command' => 'CreateCookieCommand',
'exception' => $e->getMessage(),
'exceptionClass' => $e::class,
]);
throw $e;
}
}
}Log Channel Naming Convention
Use dot notation for channel names:
{domain}.command.{action}- Commands (e.g.,cookie.command.create){domain}.query.{action}- Queries (e.g.,cookie.query.getById){domain}.event.{action}- Events (e.g.,cookie.event.created)
Example:
$logger = LoggerFactory::create('cookie.command.create');JSON Log Format
Each log entry contains:
{
"message": "Creating cookie",
"context": {
"domain": "Cookie",
"command": "CreateCookieCommand",
"cookieName": "Chocolate Chip",
"price": 9.99
},
"level": 200,
"level_name": "INFO",
"channel": "cookie.command.create",
"datetime": "2025-10-22T10:30:00.000000+00:00",
"extra": {
"memory_usage": "2MB"
}
}AI Access via MCP Server
The Local Logs MCP Server enables AI assistants to read and analyze logs in real-time.
Configuration: See .claude/documentation/MCP_SERVER_CONFIG.md
AI Query Examples:
"Show me the last 50 lines from today's application log"
"Find all ERROR level logs from the Cookie domain"
"What happened with CreateCookieCommand in the last hour?"
"Show me logs related to cookie creation failures"Testing Logging
Test logger directly:
php temp/test-logger-directly.phpVerify JSON format:
head -1 writable/logs/app-$(date +%Y-%m-%d).json | python3 -m json.toolCheck log file:
tail -f writable/logs/app-$(date +%Y-%m-%d).jsonLog Levels
Use appropriate log levels:
- DEBUG: Detailed debug information
- INFO: Interesting events (command execution, success)
- WARNING: Exceptional occurrences that are not errors
- ERROR: Runtime errors that should be logged but don't require immediate action
- CRITICAL: Critical conditions (database unavailable, etc.)
CQRS Context Processor
LoggerFactory automatically adds CQRS context when using channel naming convention:
Channel: "cookie.command.create"
→ Extracts: domain=Cookie, command=createThis makes logs queryable by domain and operation type.
Framework Independence
Why Monolog?
- ✅ PSR-3 compliant (PHP standard)
- ✅ Works with any framework (Laravel, Symfony, CI4, or standalone)
- ✅ 50+ handlers (files, databases, cloud services, Slack, etc.)
- ✅ Mature and battle-tested
What happens if you remove CodeIgniter?
- LoggerFactory continues to work unchanged
- Only replace LoggingServiceProvider with framework-specific adapter
- All handlers keep their logging code
Error Codes
Standardized error codes enable:
- Automated alerting and monitoring
- Error categorization and metrics
- AI-based log analysis
- Client-side error handling
Error Code Registry (app/Domain/Cookie/ErrorCodes.php):
// Validation errors (100-199)
public const int COOKIE_VALIDATION_NAME = 101;
public const int COOKIE_VALIDATION_PRICE = 102;
// Not found errors (200-299)
public const int COOKIE_NOT_FOUND = 201;
// Business rule violations (300-399)
public const int COOKIE_BUSINESS_RULE_STOCK_NEGATIVE = 301;
// Repository errors (400-499)
public const int COOKIE_REPOSITORY_SAVE_FAILED = 401;Usage in Exceptions:
// All exceptions now support error codes
throw ValidationException::required('name', ErrorCodes::COOKIE_VALIDATION_NAME);
throw DomainException::businessRuleViolation('Stock negative', 'details', ErrorCodes::COOKIE_BUSINESS_RULE_STOCK_NEGATIVE);
// Retrieve error code
$errorCode = $exception->getErrorCode();Usage in Logging:
$this->logger->error('Validation failed', [
'domain' => 'Cookie',
'error_code' => ErrorCodes::COOKIE_VALIDATION_NAME,
'attempted_value' => $value,
]);Correlation IDs
Correlation IDs enable distributed request tracing across multiple handlers, repositories, and events.
Auto-Injection:
The CorrelationIdProcessor automatically adds correlation_id to ALL log entries:
{
"message": "Cookie created",
"context": {"domain": "Cookie", "cookie_id": 123},
"extra": {
"correlation_id": "3fd536da-71a6-4e85-8b62-769d4c86b99b"
}
}Manual Usage:
use App\Infrastructure\Logging\CorrelationIdService;
// Generate new correlation ID
$correlationId = CorrelationIdService::generate();
// Get current correlation ID
$currentId = CorrelationIdService::get();
// Set specific correlation ID (e.g., from HTTP header)
CorrelationIdService::set($incomingCorrelationId);Request Lifecycle Example:
- HTTP Request arrives → Generate correlation ID
- Command handler executes → Logs include correlation ID
- Event dispatched → Same correlation ID
- Event handler executes → Same correlation ID
- Repository queries → Same correlation ID
Result: Entire request traceable with single ID
Query Logging Configuration
Configuration File (app/Config/Logging.php):
public string $queryLoggingLevel = 'errors'; // 'all'|'errors'|'slow'|'sampling'
public int $slowQueryThresholdMs = 100;
public float $samplingRate = 0.01; // 1%
public bool $correlationIdEnabled = true;
public bool $businessMetricsEnabled = true;Logging Levels:
- all - Log every query (development only)
- errors - Log only failed queries (recommended for production)
- slow - Log only slow queries (>threshold)
- sampling - Log random sample (e.g., 1%)
Implementation Pattern:
private function shouldLog(Config\Logging $config, float $duration, mixed $result): bool
{
// Always log slow queries
if ($duration > $config->slowQueryThresholdMs) {
return true;
}
return match ($config->queryLoggingLevel) {
'all' => true,
'errors' => $result === null,
'slow' => false,
'sampling' => (mt_rand() / mt_getrandmax()) < $config->samplingRate,
default => false
};
}Domain Objects Logging
Value Objects and Entities do NOT use DomainLogger directly. Validation exceptions thrown by value objects and entities are logged by the command handler's catch block. This keeps domain objects free of infrastructure dependencies.
// Value objects throw exceptions (logged by handler):
throw ValidationException::required('name', ErrorCodes::COOKIE_VALIDATION_NAME);
// Entities throw exceptions (logged by handler):
throw DomainException::businessRuleViolation(
'Stock cannot be negative',
sprintf('Attempted to decrease stock by %d when only %d available', $quantity, $this->stock),
ErrorCodes::COOKIE_BUSINESS_RULE_STOCK_NEGATIVE
);Cookie Domain: Logging Template
The Cookie domain is the reference implementation for all logging patterns. All logging features are fully implemented:
✅ Command handlers with duration tracking and error codes ✅ Query handlers with configurable logging ✅ Event handlers with PSR-3 logging ✅ Repository with database error, slow query, and business metrics logging ✅ Value objects with validation logging ✅ Entities with business rule logging ✅ Correlation IDs automatically injected ✅ Error codes in all exceptions ✅ Comprehensive test coverage
Use Cookie domain as template when creating new domains.
Testing Logging
Run comprehensive logging test suite:
php temp/test-comprehensive-logging.phpTests verify:
- JSON format and structure
- Correlation IDs present
- Error codes functional
- Value object validation logging
- Entity business rule logging
- AI parsability
Related Documentation
- Logging Best Practices:
.claude/documentation/LOGGING_BEST_PRACTICES.md(comprehensive guide) - MCP Server Configuration:
.claude/documentation/MCP_SERVER_CONFIG.md - Monolog Documentation: https://github.com/Seldaek/monolog
- PSR-3 Standard: https://www.php-fig.org/psr/psr-3/
Common Commands
Quality Checks (MUST pass before commit)
# PHPStan Level 8 (MUST return 0 errors)
composer phpstan
# Slevomat + PSR-12 (MUST return 0 violations)
composer phpcs
# All tests (MUST maintain 90% coverage)
composer test
# Run all checks together
composer checkDevelopment
# Start dev server
php spark serve
# Run migrations
php spark migrate --all
# Run tests with readable output
vendor/bin/phpunit --testdox
# Run tests with coverage
vendor/bin/phpunit --coverage-text
# Auto-fix coding standards
vendor/bin/phpcbfDomain Creation (AI-Optimized Workflows)
# Use slash command (fastest)
/add-domain Order
# Or request skill
Use domain-scaffolding skill to create complete Order domain
# Manual creation (not recommended)
See ADDING_DOMAINS.md for step-by-step guideProperty Addition (AI-Optimized Workflows)
# Use slash command (fastest)
/add-property Cookie flavor string
# Or request skill
Use property-addition skill to add flavor property to Cookie
# Manual modification (not recommended)
See MODIFYING_ENTITIES.md for checklistProject Conventions (Ubiquitous Language)
Naming Standards
- Commands:
Create{Entity}Command,Update{Entity}Command,Delete{Entity}Command - Queries:
Get{Entity}ByIdQuery,GetAll{Entities}Query,Get{Entities}PaginatedQuery - Events:
{Entity}CreatedEvent,{Entity}UpdatedEvent,{Entity}DeletedEvent - Handlers:
{Command/Query/Event}Handler - Value Objects:
{Entity}{Property}(e.g., CookieName, CookiePrice) - Entities:
{Entity}(e.g., Cookie, Order) - Repositories:
{Entity}Repository - Models:
{Entity}Model
Code Quality Rules (Enforced by Specialists)
Method-Level:
- Max 20 lines per method (including braces)
- Max 3 parameters (use DTOs/commands for more)
- Early returns (no else after return)
- Guard clauses for validation
- Descriptive names (verb + noun)
Class-Level:
- Max 200 lines per class
- Single Responsibility Principle
- Final by default (unless designed for extension)
- Private by default (encapsulation)
Type Safety:
declare(strict_types=1)in all files- All parameters have type declarations
- All return types declared
- No
mixedtype unless necessary - Use
===for all comparisons - Readonly value objects
CQRS/DDD:
- Commands/Queries/Events are readonly DTOs
- Value Objects are immutable and self-validating
- Entities use factory methods (create/reconstitute)
- No business logic in controllers
- Repository pattern for data access
Auto-Discovery System
Zero-Configuration Domain Registration
This template uses native PHP attributes for automatic domain registration:
- Create domain folder:
app/Domain/{YourDomain}/ - Create ServiceProvider with
#[DomainServiceProvider]attribute - Done! System automatically discovers and registers everything
Example:
use App\Infrastructure\Attributes\DomainServiceProvider;
#[DomainServiceProvider] // <-- This attribute enables auto-discovery!
final class OrderServiceProvider implements DomainServiceProviderInterface
{
// Registration methods...
}No editing Services.php or any central configuration files!
Testing Strategy
Test Pyramid (Enforced by test-specialist)
- 70% Unit Tests - Handlers, Value Objects, Entities, Events
- 20% Integration Tests - Repositories with database
- 10% Feature Tests - Complete HTTP request flow
Minimum Coverage: 90%
Run coverage: vendor/bin/phpunit --coverage-text
Test Locations
tests/
├── Unit/
│ └── Domain/{Domain}/
│ ├── ValueObjects/
│ ├── Entities/
│ ├── Commands/
│ ├── Queries/
│ └── Events/
├── Integration/
│ └── Repositories/
└── Feature/
└── {Domain}/Import Additional Documentation
For detailed protocols and guidelines, see:
- Domain Creation:
.claude/documentation/DOMAIN_CREATION_PROTOCOL.md - Property Addition:
.claude/documentation/PROPERTY_ADDITION_PROTOCOL.md - Business Rules:
.claude/documentation/BUSINESS_RULE_PROTOCOL.md - Testing Guidelines:
.claude/documentation/TESTING_GUIDELINES.md - Architecture Decisions:
.claude/documentation/ARCHITECTURE_DECISIONS.md - Complete File List:
.claude/documentation/COMPLETE_FILE_INVENTORY.md - Git Workflow:
.claude/documentation/GIT_WORKFLOW.md(hooks, signing, ruleset, releases)
Key Design Decisions
1. No Business Logic in Controllers
Controllers ONLY orchestrate commands/queries via buses. All business rules are in handlers.
2. Repository Pattern
Repositories abstract data access and return domain entities, not arrays.
3. Value Objects for Validation
Properties with validation rules (e.g., CookieName, CookiePrice) are Value Objects.
4. Early Returns, No Else Statements
All code uses guard clauses and early returns for readability.
5. Strict Type Safety
All code uses declare(strict_types=1) and full type hints (PHPStan level 8).
6. Soft Deletes
All entities support soft deletion (deleted_at timestamp).
7. Immutable Commands/Queries/Events
All DTOs are readonly classes for thread safety and clarity.
Dependencies
- PHP: 8.3+ (CI currently tests on 8.4)
- CodeIgniter: 4.6.3+
- MySQL: 8.0+
- PHPStan: Level 8
- PHPCS: PSR-12 + Slevomat
- PHPUnit: 12.5+
- PCOV: For fast code coverage
Architecture Benefits
✅ Clear Separation of Concerns - Domain, Infrastructure, Application layers ✅ Testability - Easy to test in isolation with mocks ✅ Maintainability - Changes localized to specific layers ✅ Scalability - Can separate read/write databases easily ✅ AI-Friendly - Clear patterns, comprehensive documentation ✅ Type-Safe - PHPStan level 8, full type coverage ✅ Auto-Discovery - Zero configuration for new domains ✅ Quality-Enforced - Specialists prevent violations
Quick Q&A for AI Agents
Q: Where is business logic?
A: In Command/Query Handlers in app/Domain/{Domain}/Commands/ and app/Domain/{Domain}/Queries/
Q: Where is database access?
A: In repository implementations under app/Infrastructure/Persistence/Repositories/, backed by CodeIgniter models in app/Models/{Domain}/ or app/Infrastructure/Persistence/Models/.
Q: How do I add a new field to an entity?
A: Use /add-property {Domain} {property} {type} command or see .claude/documentation/PROPERTY_ADDITION_PROTOCOL.md
Q: How do I create a new domain?
A: Use /add-domain {DomainName} command or see .claude/documentation/DOMAIN_CREATION_PROTOCOL.md
Q: Where are routes defined?
A: In app/Config/Routes.php
Q: How do I run tests?
A: composer test or vendor/bin/phpunit
Q: How do I add validation?
A: Create Value Object in app/Domain/{Domain}/ValueObjects/ with validation in constructor
Q: Which specialist should review my code? A: Use multiple specialists in parallel - the orchestrator pattern determines which ones based on the task
Q: What if PHPStan or Slevomat fail? A: Code MUST NOT be committed. Use specialists to fix violations first.
Reference Domain
Use Cookie domain as a reference for all new domains. It demonstrates:
- Complete CQRS implementation
- DDD patterns (entities, value objects, aggregates)
- Comprehensive test coverage (192 tests, 100% passing)
- PHPStan Level 8 compliance
- Slevomat compliance
- AI-optimized docblocks
Location: app/Domain/Cookie/
This project follows best practices for CQRS, DDD, and Clean Architecture, optimized for AI agent collaboration through specialized subagents and orchestrator patterns.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

