Laravel + Vue.js starter template with AI-assisted development tooling.
Tech Stack: Laravel 12, Vue 3, Inertia.js, PHP 8.3, TypeScript, Tailwind CSS
Development Environment: Devcontainer with PHP running directly (no Sail)
NEVER write code directly. When the user requests implementation work (creating features, fixing bugs, refactoring), delegate to the software-engineer agent.
| Task Type | Action |
|---|---|
| Investigation, research, exploring code | Do directly |
| Understanding errors, reading logs | Do directly |
| Proposing solutions, discussing approaches | Do directly |
| Writing/modifying code | Use software-engineer agent |
| Creating new files | Use software-engineer agent |
| Refactoring | Use software-engineer agent |
Why? The software-engineer agent delegates to specialized engineers and uses review:code for code review, catching convention violations and issues before they become problems.
How to invoke:
Task(
description: "Implement [brief description]",
prompt: "[USER'S REQUEST WITH CONTEXT]",
subagent_type: "software-engineer"
)
| Agent | Purpose |
|---|---|
software-engineer |
Orchestrates implementation + review via review:code |
architecture-reviewer |
Reviews code organization and patterns |
frontend-engineer |
Vue/TypeScript implementation |
frontend-reviewer |
Reviews Vue/TypeScript code |
ALWAYS align with the user before writing code. Never jump straight into implementation.
-
Understand the Request
- Ask clarifying questions if anything is unclear
- Confirm your understanding of the requirements
- Identify any ambiguities or edge cases
-
Propose a Solution
- Present your planned approach clearly and concisely
- Explain which files you'll modify or create
- Mention any architectural decisions or trade-offs
- Wait for user approval before proceeding
-
Stay Within Scope
- ONLY implement what was explicitly requested or agreed upon
- NEVER add "nice-to-have" features without asking first
- DO NOT add fields, statuses, or features that weren't discussed
-
Seek Approval for Changes
- If you identify improvements during implementation, STOP and ask before adding them
- What seems obvious to you may not align with the user's vision
Before completing ANY implementation task, run tarnished status to see which checks need re-running.
Tarnished is a CLI tool that tracks which quality checks need re-running after code changes. It solves a critical problem: Claude Code often forgets to run tests/linters after making changes, leaving the codebase in an unknown state.
Think of it like this:
- When you modify files, those files become "tarnished" (dirty, needs checking)
- When you run a check successfully, those files become "clean" (verified working)
- Tarnished remembers which files were clean when each check last passed
Without tarnished, this happens constantly:
- Claude modifies 5 PHP files
- Claude runs
test:php- tests pass - Claude modifies 2 more PHP files
- Claude says "Done!" - but never re-ran tests after step 3
- User discovers broken tests later
With tarnished:
- Claude modifies 5 PHP files →
test:phpbecomes tarnished - Claude runs
test:php- tests pass →test:phpbecomes clean - Claude modifies 2 more PHP files →
test:phpbecomes tarnished again - Claude runs
tarnished status→ seestest:phpis tarnished - Claude runs
test:phpagain → tests pass → clean - Claude says "Done!" - with confidence everything works
| Status | Meaning | Action |
|---|---|---|
clean |
No files changed since check last passed | Safe to skip |
tarnished |
Files changed since last pass | MUST re-run |
never_saved |
Check has never passed | MUST run |
tarnished status # Check all profiles (use frequently!)
# {"lint:php": "tarnished", "lint:js": "clean", "test:php": "tarnished"}
tarnished check lint:php # Check specific profile
# Exit code: 0 = clean, 1 = tarnishedIf a check is tarnished, you MUST run it before completing your task.
This is non-negotiable. The workflow is:
- Make code changes
- Run
tarnished statusto see what's tarnished - Run ALL tarnished checks relevant to your changes
- If any check fails → FIX IT → re-run
- Only mark your task complete when relevant profiles are clean
Never say "Done" with tarnished checks.
Use the devtools scripts instead of raw commands. They include environment setup and AI-powered failure diagnosis via lumby:
| Command | Wraps | Accepts |
|---|---|---|
test:php |
php artisan test |
All artisan test flags (--filter, --parallel, file paths) |
lint:php |
./vendor/bin/phpstan |
All PHPStan flags (--generate-baseline, etc.) |
lint:js |
ESLint | File paths |
lint:ts |
vue-tsc (TypeScript) | File paths |
review:code |
reldo review |
PROMPT (positional), --verbose, --exit-code |
qa |
All checks | --skip-phpstan, --skip-eslint |
# PHP Tests
test:php # Run all tests
test:php --filter=TestName # Run specific test(s)
test:php tests/Feature/Dir/ # Run tests in a directory
# Static Analysis
lint:php # Run PHPStan
lint:js # Run ESLint on all files
lint:ts # Run TypeScript type-check
# Code Review
review:code "Review my changes" # AI code review
review:code "Review app/Models/" --verbose
# Run All Checks
qa # Run all quality checks
qa --skip-phpstan # Skip PHPStanWhy devtools? These scripts auto-setup the environment and use lumby for AI diagnosis on failures—reducing context usage in your session.
Knip analyzes the frontend dependency graph. Use it to check impact before modifying files and to find dead code.
# Check what depends on a file (before modifying/deleting it)
npx knip --trace-file resources/js/components/MyComponent.vue
npx knip --trace-file resources/js/composables/useMyComposable.ts
# Find all unused files, exports, and dependencies
npx knipConfiguration: knip.config.ts (includes custom Vue compiler fix for <script setup> tracing).
test:php uses a queue to prevent concurrent runs from bottlenecking each other. If tests are already running, your run waits automatically — no action needed.
| Command | Purpose |
|---|---|
test:php --status |
Show who's running/waiting in the PHP test queue |
test:php --wait-timeout=N |
Set max wait time in seconds (default: 600) |
For AI agents: Do NOT kill or retry test processes that appear to be "hanging" — they may be waiting in the queue. Use --status to check. The queue handles serialization automatically.
VS Code Task (recommended):
- Press
Ctrl+Shift+P→ "Tasks: Run Task" → "Dev: Start"
CLI:
dev:start # Full setup + services
dev:start --quick # Services only (skip setup)| Command | Purpose |
|---|---|
dev:start |
Start everything (setup + services) |
dev:start --quick |
Start services only (skip setup) |
dev:stop |
Stop app services (keeps Docker running) |
dev:stop --all |
Stop all services including Docker |
dev:status |
Show status of all services |
Long-running services run in tmux sessions, allowing both VS Code and Claude to access them.
| Service | Command | What it runs |
|---|---|---|
| Database | service:database |
PostgreSQL via Docker |
| Cache | service:cache |
Redis via Docker |
| Serve | service:serve |
Laravel dev server (port 8080) |
| Vite | service:vite |
Vite HMR dev server (port 5173) |
| Logs | service:logs |
Laravel Pail log tailing |
Each service supports:
service:serve start # Start the service
service:serve start --attach # Start and attach to see output
service:serve stop # Stop the service
service:serve restart # Restart the service
service:serve status # Check if running (exit code 0 = running)
service:serve logs 50 # Show last 50 lines of output
service:serve logs --watch # Follow logs continuouslydev:status # Human-readable status
dev:status --json # JSON output for scriptsUser reports an error → Check the logs:
service:serve logs 100 # See recent Laravel server output
service:logs start # Start log tailing to watch for errorsService not responding → Restart it:
service:serve restart
service:vite restart- Validation: Use Data classes (Spatie Laravel Data), not FormRequests
- Controllers: Organize in nested directories by domain
- Services: Business logic goes in service classes
- Testing: Use factories, write feature tests
- Always:
<script setup lang="ts"> - Composition API: No Options API
- Types: Generate from PHP with
php artisan typescript:transform
Rules in .claude/rules/ auto-load based on file paths via YAML frontmatter:
---
paths: app/Services/**/*.php
---
# Rule content here...How it works:
- Edit a
.phpfile → backend rules auto-load - Edit a
.vuefile → frontend rules auto-load - Rules without
pathsfrontmatter load for all files
Directories:
techstack/- Shared conventions (synced viaphp artisan dev-rules:update)project/- Your custom rules (not synced)
.claude/ # Claude Code configuration
rules/ # Auto-loading code conventions
agents/ # Specialized task agents
hooks/ # Session hooks (user profile)
skills/ # On-demand skills (/setup-profile)
settings.json # Permissions, plugins, MCP servers
.tarnished/ # Change tracking configuration
.reldo/ # Code review configuration
devtools/ # Development scripts (test, lint, review)
docs/guides/ # User guides (ai-first and hands-on)
docs/development/ # Technical documentation
- laravel-boost - Laravel tools (tinker, docs, database queries)
- serena - Semantic code navigation
The liv-hooks plugin provides validation:
- Blocks FormRequest usage (suggests Data classes)
- Enforces
<script setup lang="ts">in Vue - Validates E2E test paths