start-vibing-stacks 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -0
- package/dist/detector.d.ts +16 -0
- package/dist/detector.js +103 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +239 -0
- package/dist/installer.d.ts +30 -0
- package/dist/installer.js +224 -0
- package/dist/setup.d.ts +17 -0
- package/dist/setup.js +161 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.js +4 -0
- package/dist/ui.d.ts +11 -0
- package/dist/ui.js +47 -0
- package/package.json +57 -0
- package/stacks/_shared/agents/claude-md-compactor.md +44 -0
- package/stacks/_shared/agents/commit-manager.md +65 -0
- package/stacks/_shared/agents/documenter.md +82 -0
- package/stacks/_shared/agents/domain-updater.md +51 -0
- package/stacks/_shared/agents/research-web.md +60 -0
- package/stacks/_shared/agents/tester.md +61 -0
- package/stacks/_shared/commands/feature.md +13 -0
- package/stacks/_shared/commands/fix.md +9 -0
- package/stacks/_shared/commands/validate.md +10 -0
- package/stacks/_shared/config/domain-mapping.json +41 -0
- package/stacks/_shared/config/security-rules.json +31 -0
- package/stacks/_shared/hooks/stop-validator.ts +171 -0
- package/stacks/_shared/hooks/user-prompt-submit.ts +77 -0
- package/stacks/_shared/skills/debugging-patterns/SKILL.md +39 -0
- package/stacks/_shared/skills/docker-patterns/SKILL.md +47 -0
- package/stacks/_shared/skills/git-workflow/SKILL.md +35 -0
- package/stacks/nodejs/stack.json +87 -0
- package/stacks/php/config/quality-gates.json +23 -0
- package/stacks/php/skills/composer-workflow/SKILL.md +78 -0
- package/stacks/php/skills/php-patterns/SKILL.md +119 -0
- package/stacks/php/skills/phpstan-analysis/SKILL.md +68 -0
- package/stacks/php/skills/phpunit-testing/SKILL.md +122 -0
- package/stacks/php/skills/security-scan-php/SKILL.md +80 -0
- package/stacks/php/stack.json +95 -0
- package/templates/CLAUDE-default.md +54 -0
- package/templates/CLAUDE-php.md +88 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Debugging Patterns
|
|
2
|
+
|
|
3
|
+
## Universal Approach
|
|
4
|
+
|
|
5
|
+
1. **Read the error** — fully, including stack trace
|
|
6
|
+
2. **Reproduce** — confirm you can trigger it
|
|
7
|
+
3. **Isolate** — minimal reproduction
|
|
8
|
+
4. **Fix** — one change at a time
|
|
9
|
+
5. **Test** — verify fix, add regression test
|
|
10
|
+
6. **Document** — add to domain attention points
|
|
11
|
+
|
|
12
|
+
## Stack-Specific Tools
|
|
13
|
+
|
|
14
|
+
### PHP
|
|
15
|
+
```bash
|
|
16
|
+
# Error logs
|
|
17
|
+
tail -f /var/log/php/error.log
|
|
18
|
+
# Xdebug
|
|
19
|
+
php -dxdebug.mode=debug script.php
|
|
20
|
+
# Built-in server with errors
|
|
21
|
+
php -S localhost:8000 -d display_errors=1
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Node.js
|
|
25
|
+
```bash
|
|
26
|
+
# Debug mode
|
|
27
|
+
node --inspect script.js
|
|
28
|
+
# Verbose logging
|
|
29
|
+
DEBUG=* node script.js
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Anti-Patterns
|
|
33
|
+
|
|
34
|
+
| Don't | Do |
|
|
35
|
+
|-------|-----|
|
|
36
|
+
| `var_dump` / `console.log` everywhere | Use proper debugger |
|
|
37
|
+
| Fix symptom, not cause | Trace to root cause |
|
|
38
|
+
| Skip writing test for fix | Always add regression test |
|
|
39
|
+
| Leave debug code in commit | Clean before commit |
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Docker Patterns
|
|
2
|
+
|
|
3
|
+
## PHP (PHP-FPM + Nginx)
|
|
4
|
+
|
|
5
|
+
```dockerfile
|
|
6
|
+
FROM php:8.3-fpm-alpine
|
|
7
|
+
|
|
8
|
+
RUN apk add --no-cache \
|
|
9
|
+
libzip-dev \
|
|
10
|
+
&& docker-php-ext-install pdo_mysql zip opcache
|
|
11
|
+
|
|
12
|
+
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
|
13
|
+
COPY . /var/www/html
|
|
14
|
+
WORKDIR /var/www/html
|
|
15
|
+
|
|
16
|
+
RUN composer install --no-dev --optimize-autoloader
|
|
17
|
+
RUN chown -R www-data:www-data /var/www/html
|
|
18
|
+
|
|
19
|
+
EXPOSE 9000
|
|
20
|
+
CMD ["php-fpm"]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Node.js (Multi-stage)
|
|
24
|
+
|
|
25
|
+
```dockerfile
|
|
26
|
+
FROM node:20-alpine AS builder
|
|
27
|
+
WORKDIR /app
|
|
28
|
+
COPY package*.json ./
|
|
29
|
+
RUN npm ci
|
|
30
|
+
COPY . .
|
|
31
|
+
RUN npm run build
|
|
32
|
+
|
|
33
|
+
FROM node:20-alpine
|
|
34
|
+
WORKDIR /app
|
|
35
|
+
COPY --from=builder /app/dist ./dist
|
|
36
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
37
|
+
EXPOSE 3000
|
|
38
|
+
CMD ["node", "dist/index.js"]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Rules
|
|
42
|
+
|
|
43
|
+
1. **Multi-stage builds** — smaller images
|
|
44
|
+
2. **Alpine base** — minimal attack surface
|
|
45
|
+
3. **.dockerignore** — exclude node_modules, .git
|
|
46
|
+
4. **Non-root user** — security
|
|
47
|
+
5. **Health checks** — always include
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Git Workflow
|
|
2
|
+
|
|
3
|
+
## Branch Naming
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
feature/short-description
|
|
7
|
+
fix/bug-description
|
|
8
|
+
refactor/what-changed
|
|
9
|
+
chore/maintenance-task
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Commit Format (Conventional)
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
type(scope): description
|
|
16
|
+
|
|
17
|
+
Body explaining what and why.
|
|
18
|
+
|
|
19
|
+
Generated with Claude Code
|
|
20
|
+
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Flow
|
|
24
|
+
|
|
25
|
+
1. Create branch from main: `git checkout -b feature/name`
|
|
26
|
+
2. Work, commit incrementally
|
|
27
|
+
3. When done: merge to main, delete branch
|
|
28
|
+
4. Push main to remote
|
|
29
|
+
|
|
30
|
+
## Rules
|
|
31
|
+
|
|
32
|
+
- NEVER force push main
|
|
33
|
+
- ALWAYS conventional commits
|
|
34
|
+
- ALWAYS end on main branch
|
|
35
|
+
- ONE feature per branch
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nodejs",
|
|
3
|
+
"name": "Node.js / TypeScript",
|
|
4
|
+
"icon": "📦",
|
|
5
|
+
"runtime": "Bun / Node.js 20+",
|
|
6
|
+
"minVersion": "20.0.0",
|
|
7
|
+
"packageManager": "bun|npm|pnpm",
|
|
8
|
+
"extensions": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
9
|
+
"testExtensions": ["*.test.ts", "*.spec.ts", "*.test.tsx"],
|
|
10
|
+
"detectFiles": ["package.json", "tsconfig.json", "bun.lockb", "next.config.js"],
|
|
11
|
+
"commands": {
|
|
12
|
+
"test": "bun run test",
|
|
13
|
+
"lint": "bun run lint",
|
|
14
|
+
"format": "bun run format",
|
|
15
|
+
"serve": "bun run dev",
|
|
16
|
+
"build": "bun run build",
|
|
17
|
+
"typecheck": "bun run typecheck"
|
|
18
|
+
},
|
|
19
|
+
"qualityGates": [
|
|
20
|
+
{ "name": "TypeCheck", "command": "bun run typecheck", "required": true, "order": 1 },
|
|
21
|
+
{ "name": "Lint", "command": "bun run lint", "required": true, "order": 2 },
|
|
22
|
+
{ "name": "Tests", "command": "bun run test", "required": true, "order": 3 },
|
|
23
|
+
{ "name": "Build", "command": "bun run build", "required": true, "order": 4 }
|
|
24
|
+
],
|
|
25
|
+
"frameworks": [
|
|
26
|
+
{ "id": "nextjs", "name": "Next.js (App Router)", "icon": "▲", "detectFiles": ["next.config.js", "next.config.ts", "next.config.mjs"] },
|
|
27
|
+
{ "id": "nuxt", "name": "Nuxt", "icon": "💚", "detectFiles": ["nuxt.config.ts"] },
|
|
28
|
+
{ "id": "astro", "name": "Astro", "icon": "🚀", "detectFiles": ["astro.config.mjs"] },
|
|
29
|
+
{ "id": "express", "name": "Express", "icon": "⚡" },
|
|
30
|
+
{ "id": "fastify", "name": "Fastify", "icon": "🏎️" },
|
|
31
|
+
{ "id": "vanilla", "name": "Vanilla Node.js", "icon": "📄" }
|
|
32
|
+
],
|
|
33
|
+
"databases": [
|
|
34
|
+
{ "id": "mongodb", "name": "MongoDB", "icon": "🍃" },
|
|
35
|
+
{ "id": "postgresql", "name": "PostgreSQL", "icon": "🐘" },
|
|
36
|
+
{ "id": "mysql", "name": "MySQL / MariaDB", "icon": "🐬" },
|
|
37
|
+
{ "id": "sqlite", "name": "SQLite (Turso / libSQL)", "icon": "📁" },
|
|
38
|
+
{ "id": "redis", "name": "Redis (Upstash)", "icon": "🔴" },
|
|
39
|
+
{ "id": "none", "name": "None", "icon": "❌" }
|
|
40
|
+
],
|
|
41
|
+
"frontendOptions": [
|
|
42
|
+
{ "id": "react-tailwind", "name": "React 19+ / TailwindCSS 4+", "icon": "⚛️" },
|
|
43
|
+
{ "id": "vue", "name": "Vue.js / Nuxt", "icon": "💚" },
|
|
44
|
+
{ "id": "svelte", "name": "Svelte / SvelteKit", "icon": "🔥" },
|
|
45
|
+
{ "id": "shadcn", "name": "shadcn/ui + Tailwind", "icon": "🎨" },
|
|
46
|
+
{ "id": "none", "name": "API only — no frontend", "icon": "❌" }
|
|
47
|
+
],
|
|
48
|
+
"deployTargets": [
|
|
49
|
+
{ "id": "vercel", "name": "Vercel", "icon": "▲" },
|
|
50
|
+
{ "id": "docker", "name": "Docker", "icon": "🐳" },
|
|
51
|
+
{ "id": "vps-ssh", "name": "VPS (SSH)", "icon": "☁️" },
|
|
52
|
+
{ "id": "railway", "name": "Railway", "icon": "🚂" },
|
|
53
|
+
{ "id": "fly", "name": "Fly.io", "icon": "✈️" },
|
|
54
|
+
{ "id": "netlify", "name": "Netlify", "icon": "🌐" }
|
|
55
|
+
],
|
|
56
|
+
"skills": [
|
|
57
|
+
"typescript-strict",
|
|
58
|
+
"react-patterns",
|
|
59
|
+
"nextjs-app-router",
|
|
60
|
+
"zod-validation",
|
|
61
|
+
"vitest-testing"
|
|
62
|
+
],
|
|
63
|
+
"requirements": [
|
|
64
|
+
{
|
|
65
|
+
"name": "Node.js",
|
|
66
|
+
"command": "node",
|
|
67
|
+
"versionFlag": "--version",
|
|
68
|
+
"minVersion": "20.0.0",
|
|
69
|
+
"installCommand": {
|
|
70
|
+
"macos": "brew install node",
|
|
71
|
+
"linux": "curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt install -y nodejs"
|
|
72
|
+
},
|
|
73
|
+
"versionRegex": "v?(\\d+\\.\\d+\\.\\d+)"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "Bun",
|
|
77
|
+
"command": "bun",
|
|
78
|
+
"versionFlag": "--version",
|
|
79
|
+
"minVersion": "1.0.0",
|
|
80
|
+
"installCommand": {
|
|
81
|
+
"macos": "brew install oven-sh/bun/bun",
|
|
82
|
+
"linux": "curl -fsSL https://bun.sh/install | bash"
|
|
83
|
+
},
|
|
84
|
+
"versionRegex": "(\\d+\\.\\d+\\.\\d+)"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"gates": {
|
|
3
|
+
"phpstan": {
|
|
4
|
+
"command": "vendor/bin/phpstan analyse --level=6",
|
|
5
|
+
"description": "PHPStan static analysis (level 6)",
|
|
6
|
+
"required": true,
|
|
7
|
+
"order": 1
|
|
8
|
+
},
|
|
9
|
+
"phpunit": {
|
|
10
|
+
"command": "vendor/bin/phpunit",
|
|
11
|
+
"description": "PHPUnit tests",
|
|
12
|
+
"required": true,
|
|
13
|
+
"order": 2
|
|
14
|
+
},
|
|
15
|
+
"csFixer": {
|
|
16
|
+
"command": "vendor/bin/php-cs-fixer fix --dry-run --diff",
|
|
17
|
+
"description": "PHP-CS-Fixer code style",
|
|
18
|
+
"required": false,
|
|
19
|
+
"order": 3
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"runAll": "vendor/bin/phpstan analyse --level=6 && vendor/bin/phpunit"
|
|
23
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Composer Workflow
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
- **Composer >= 2.0**
|
|
6
|
+
- **PHP >= 8.3**
|
|
7
|
+
|
|
8
|
+
## Essential Commands
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
composer install # Install from lock file (CI/deploy)
|
|
12
|
+
composer update # Update dependencies (dev only)
|
|
13
|
+
composer require package/name # Add dependency
|
|
14
|
+
composer require --dev pkg # Add dev dependency
|
|
15
|
+
composer dump-autoload -o # Optimize autoloader (production)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## composer.json Structure
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"name": "vendor/project",
|
|
23
|
+
"type": "project",
|
|
24
|
+
"require": {
|
|
25
|
+
"php": ">=8.3"
|
|
26
|
+
},
|
|
27
|
+
"require-dev": {
|
|
28
|
+
"phpunit/phpunit": "^11.0",
|
|
29
|
+
"phpstan/phpstan": "^2.0",
|
|
30
|
+
"friendsofphp/php-cs-fixer": "^3.0"
|
|
31
|
+
},
|
|
32
|
+
"autoload": {
|
|
33
|
+
"psr-4": {
|
|
34
|
+
"App\\": "src/"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"autoload-dev": {
|
|
38
|
+
"psr-4": {
|
|
39
|
+
"Tests\\": "tests/"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"test": "phpunit",
|
|
44
|
+
"lint": "phpstan analyse --level=6",
|
|
45
|
+
"format": "php-cs-fixer fix",
|
|
46
|
+
"check": [
|
|
47
|
+
"@lint",
|
|
48
|
+
"@test"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"config": {
|
|
52
|
+
"sort-packages": true,
|
|
53
|
+
"allow-plugins": {}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## PSR-4 Autoloading
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
src/
|
|
62
|
+
├── Controllers/
|
|
63
|
+
│ └── UserController.php → App\Controllers\UserController
|
|
64
|
+
├── Services/
|
|
65
|
+
│ └── UserService.php → App\Services\UserService
|
|
66
|
+
├── Models/
|
|
67
|
+
│ └── User.php → App\Models\User
|
|
68
|
+
└── Middleware/
|
|
69
|
+
└── AuthMiddleware.php → App\Middleware\AuthMiddleware
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Rules
|
|
73
|
+
|
|
74
|
+
1. **Always commit composer.lock**
|
|
75
|
+
2. **Never edit vendor/**
|
|
76
|
+
3. **Use `composer install` in CI/production** (not update)
|
|
77
|
+
4. **PHP >= 8.3 in require** — enforce minimum version
|
|
78
|
+
5. **PSR-4 autoloading** — no manual includes
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# PHP 8.3+ Patterns
|
|
2
|
+
|
|
3
|
+
## Version Requirements
|
|
4
|
+
|
|
5
|
+
- **PHP >= 8.3** — MANDATORY. Do not use deprecated patterns.
|
|
6
|
+
- **Composer >= 2.0** — For dependency management.
|
|
7
|
+
- Use strict types: `declare(strict_types=1);` in EVERY file.
|
|
8
|
+
|
|
9
|
+
## Modern PHP Features (USE THESE)
|
|
10
|
+
|
|
11
|
+
### Typed Properties & Constructor Promotion
|
|
12
|
+
|
|
13
|
+
```php
|
|
14
|
+
class User {
|
|
15
|
+
public function __construct(
|
|
16
|
+
private readonly string $name,
|
|
17
|
+
private readonly string $email,
|
|
18
|
+
private readonly int $age,
|
|
19
|
+
) {}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Enums
|
|
24
|
+
|
|
25
|
+
```php
|
|
26
|
+
enum Status: string {
|
|
27
|
+
case Active = 'active';
|
|
28
|
+
case Inactive = 'inactive';
|
|
29
|
+
case Suspended = 'suspended';
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Readonly Classes (8.2+)
|
|
34
|
+
|
|
35
|
+
```php
|
|
36
|
+
readonly class UserDTO {
|
|
37
|
+
public function __construct(
|
|
38
|
+
public string $name,
|
|
39
|
+
public string $email,
|
|
40
|
+
) {}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Typed Class Constants (8.3+)
|
|
45
|
+
|
|
46
|
+
```php
|
|
47
|
+
class Config {
|
|
48
|
+
public const string APP_NAME = 'MyApp';
|
|
49
|
+
public const int MAX_RETRIES = 3;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Match Expression
|
|
54
|
+
|
|
55
|
+
```php
|
|
56
|
+
$result = match($status) {
|
|
57
|
+
'active' => handleActive(),
|
|
58
|
+
'inactive' => handleInactive(),
|
|
59
|
+
default => throw new \InvalidArgumentException("Unknown status: {$status}"),
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Named Arguments
|
|
64
|
+
|
|
65
|
+
```php
|
|
66
|
+
$response = new Response(
|
|
67
|
+
content: $html,
|
|
68
|
+
status: 200,
|
|
69
|
+
headers: ['Content-Type' => 'text/html'],
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Null-safe Operator
|
|
74
|
+
|
|
75
|
+
```php
|
|
76
|
+
$country = $user?->address?->country?->name;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Fibers (8.1+)
|
|
80
|
+
|
|
81
|
+
```php
|
|
82
|
+
$fiber = new Fiber(function (): void {
|
|
83
|
+
$value = Fiber::suspend('fiber started');
|
|
84
|
+
echo "Resumed with: {$value}";
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## FORBIDDEN Patterns
|
|
89
|
+
|
|
90
|
+
| Don't | Do Instead |
|
|
91
|
+
|-------|------------|
|
|
92
|
+
| `$var = isset($x) ? $x : $default` | `$var = $x ?? $default` |
|
|
93
|
+
| `function foo($x)` (no types) | `function foo(string $x): void` |
|
|
94
|
+
| `array()` | `[]` |
|
|
95
|
+
| `mysql_*` functions | PDO or Doctrine |
|
|
96
|
+
| Global variables | Dependency injection |
|
|
97
|
+
| `eval()` | Never use eval |
|
|
98
|
+
| Untyped properties | Always type properties |
|
|
99
|
+
|
|
100
|
+
## Error Handling
|
|
101
|
+
|
|
102
|
+
```php
|
|
103
|
+
try {
|
|
104
|
+
$result = riskyOperation();
|
|
105
|
+
} catch (SpecificException $e) {
|
|
106
|
+
logger()->error('Operation failed', [
|
|
107
|
+
'error' => $e->getMessage(),
|
|
108
|
+
'trace' => $e->getTraceAsString(),
|
|
109
|
+
]);
|
|
110
|
+
throw new DomainException('Friendly message', 0, $e);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## PSR Standards
|
|
115
|
+
|
|
116
|
+
- **PSR-4**: Autoloading
|
|
117
|
+
- **PSR-7**: HTTP Messages (if not using framework)
|
|
118
|
+
- **PSR-12**: Coding Style
|
|
119
|
+
- **PSR-15**: HTTP Handlers
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# PHPStan Static Analysis
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
composer require --dev phpstan/phpstan
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Configuration (phpstan.neon)
|
|
10
|
+
|
|
11
|
+
```neon
|
|
12
|
+
parameters:
|
|
13
|
+
level: 6
|
|
14
|
+
paths:
|
|
15
|
+
- src
|
|
16
|
+
- app
|
|
17
|
+
excludePaths:
|
|
18
|
+
- vendor
|
|
19
|
+
- storage
|
|
20
|
+
- bootstrap/cache
|
|
21
|
+
checkMissingIterableValueType: false
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Levels
|
|
25
|
+
|
|
26
|
+
| Level | What it checks |
|
|
27
|
+
|-------|---------------|
|
|
28
|
+
| 0 | Basic checks (unknown classes, functions) |
|
|
29
|
+
| 1 | Possibly undefined variables, return types |
|
|
30
|
+
| 2 | Unknown methods on all expressions |
|
|
31
|
+
| 3 | Return types, property types |
|
|
32
|
+
| 4 | Dead code, always true/false |
|
|
33
|
+
| 5 | Argument types |
|
|
34
|
+
| **6** | **MINIMUM for this project** — strict types |
|
|
35
|
+
| 7 | Union types |
|
|
36
|
+
| 8 | Nullable types everywhere |
|
|
37
|
+
| 9 | Mixed type restrictions |
|
|
38
|
+
|
|
39
|
+
## Running
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
vendor/bin/phpstan analyse # Default config
|
|
43
|
+
vendor/bin/phpstan analyse --level=6 src/ # Specific level & path
|
|
44
|
+
vendor/bin/phpstan analyse --generate-baseline # Generate baseline for legacy code
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Common Fixes
|
|
48
|
+
|
|
49
|
+
```php
|
|
50
|
+
// ❌ PHPStan error: Parameter $data has no type hint
|
|
51
|
+
function process($data) {}
|
|
52
|
+
|
|
53
|
+
// ✅ Fixed
|
|
54
|
+
function process(array $data): void {}
|
|
55
|
+
|
|
56
|
+
// ❌ PHPStan error: Method returns mixed
|
|
57
|
+
function getData() { return $this->db->query(); }
|
|
58
|
+
|
|
59
|
+
// ✅ Fixed
|
|
60
|
+
/** @return array<string, mixed> */
|
|
61
|
+
function getData(): array { return $this->db->query(); }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Rules
|
|
65
|
+
|
|
66
|
+
1. **Level 6 minimum** — no exceptions
|
|
67
|
+
2. **Fix errors, don't ignore** — use baseline only for legacy code
|
|
68
|
+
3. **Run before commit** — part of quality gates
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# PHPUnit Testing Skill
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
composer require --dev phpunit/phpunit
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Test File Location
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
tests/
|
|
13
|
+
├── Unit/
|
|
14
|
+
│ ├── Services/
|
|
15
|
+
│ │ └── UserServiceTest.php
|
|
16
|
+
│ └── Helpers/
|
|
17
|
+
│ └── StringHelperTest.php
|
|
18
|
+
├── Feature/
|
|
19
|
+
│ └── Api/
|
|
20
|
+
│ └── UserApiTest.php
|
|
21
|
+
└── phpunit.xml
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Test Template
|
|
25
|
+
|
|
26
|
+
```php
|
|
27
|
+
<?php
|
|
28
|
+
|
|
29
|
+
declare(strict_types=1);
|
|
30
|
+
|
|
31
|
+
namespace Tests\Unit\Services;
|
|
32
|
+
|
|
33
|
+
use PHPUnit\Framework\TestCase;
|
|
34
|
+
use App\Services\UserService;
|
|
35
|
+
|
|
36
|
+
final class UserServiceTest extends TestCase
|
|
37
|
+
{
|
|
38
|
+
private UserService $service;
|
|
39
|
+
|
|
40
|
+
protected function setUp(): void
|
|
41
|
+
{
|
|
42
|
+
parent::setUp();
|
|
43
|
+
$this->service = new UserService();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public function testCreateUserWithValidData(): void
|
|
47
|
+
{
|
|
48
|
+
// Arrange
|
|
49
|
+
$data = ['name' => 'John', 'email' => 'john@test.com'];
|
|
50
|
+
|
|
51
|
+
// Act
|
|
52
|
+
$user = $this->service->create($data);
|
|
53
|
+
|
|
54
|
+
// Assert
|
|
55
|
+
$this->assertSame('John', $user->name);
|
|
56
|
+
$this->assertSame('john@test.com', $user->email);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public function testCreateUserThrowsOnInvalidEmail(): void
|
|
60
|
+
{
|
|
61
|
+
$this->expectException(\InvalidArgumentException::class);
|
|
62
|
+
$this->expectExceptionMessage('Invalid email');
|
|
63
|
+
|
|
64
|
+
$this->service->create(['name' => 'John', 'email' => 'invalid']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @dataProvider invalidDataProvider
|
|
69
|
+
*/
|
|
70
|
+
public function testCreateUserRejectsInvalidData(array $data, string $expectedError): void
|
|
71
|
+
{
|
|
72
|
+
$this->expectException(\InvalidArgumentException::class);
|
|
73
|
+
$this->expectExceptionMessage($expectedError);
|
|
74
|
+
|
|
75
|
+
$this->service->create($data);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public static function invalidDataProvider(): array
|
|
79
|
+
{
|
|
80
|
+
return [
|
|
81
|
+
'empty name' => [['name' => '', 'email' => 'a@b.com'], 'Name required'],
|
|
82
|
+
'empty email' => [['name' => 'John', 'email' => ''], 'Email required'],
|
|
83
|
+
'null data' => [[], 'Name required'],
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Mocking
|
|
90
|
+
|
|
91
|
+
```php
|
|
92
|
+
public function testServiceCallsRepository(): void
|
|
93
|
+
{
|
|
94
|
+
$repo = $this->createMock(UserRepository::class);
|
|
95
|
+
$repo->expects($this->once())
|
|
96
|
+
->method('save')
|
|
97
|
+
->with($this->isInstanceOf(User::class))
|
|
98
|
+
->willReturn(true);
|
|
99
|
+
|
|
100
|
+
$service = new UserService($repo);
|
|
101
|
+
$result = $service->create(['name' => 'John', 'email' => 'j@t.com']);
|
|
102
|
+
|
|
103
|
+
$this->assertTrue($result);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Running
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
vendor/bin/phpunit # All tests
|
|
111
|
+
vendor/bin/phpunit tests/Unit/ # Unit only
|
|
112
|
+
vendor/bin/phpunit --filter testCreateUser # Specific test
|
|
113
|
+
vendor/bin/phpunit --coverage-text # With coverage
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Rules
|
|
117
|
+
|
|
118
|
+
1. **One assertion concept per test**
|
|
119
|
+
2. **Descriptive method names**: `testMethodName_Scenario_ExpectedResult`
|
|
120
|
+
3. **Use data providers** for multiple inputs
|
|
121
|
+
4. **setUp/tearDown** for shared state
|
|
122
|
+
5. **Never skip tests** in commits
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# PHP Security Scan
|
|
2
|
+
|
|
3
|
+
## OWASP Top 10 for PHP
|
|
4
|
+
|
|
5
|
+
### A01 - Broken Access Control
|
|
6
|
+
|
|
7
|
+
```php
|
|
8
|
+
// ❌ NEVER trust user input for authorization
|
|
9
|
+
$userId = $_GET['user_id'];
|
|
10
|
+
$user = User::find($userId);
|
|
11
|
+
|
|
12
|
+
// ✅ Use authenticated session
|
|
13
|
+
$user = Auth::user();
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### A02 - Cryptographic Failures
|
|
17
|
+
|
|
18
|
+
```php
|
|
19
|
+
// ❌ NEVER
|
|
20
|
+
md5($password);
|
|
21
|
+
sha1($password);
|
|
22
|
+
|
|
23
|
+
// ✅ ALWAYS
|
|
24
|
+
password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
|
|
25
|
+
password_verify($input, $hash);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### A03 - Injection
|
|
29
|
+
|
|
30
|
+
```php
|
|
31
|
+
// ❌ SQL Injection
|
|
32
|
+
$db->query("SELECT * FROM users WHERE id = {$_GET['id']}");
|
|
33
|
+
|
|
34
|
+
// ✅ Prepared statements (PDO)
|
|
35
|
+
$stmt = $db->prepare("SELECT * FROM users WHERE id = :id");
|
|
36
|
+
$stmt->execute([':id' => $id]);
|
|
37
|
+
|
|
38
|
+
// ✅ Prepared statements (MySQLi)
|
|
39
|
+
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
|
|
40
|
+
$stmt->bind_param('i', $id);
|
|
41
|
+
$stmt->execute();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### A07 - XSS Prevention
|
|
45
|
+
|
|
46
|
+
```php
|
|
47
|
+
// ❌ Raw output
|
|
48
|
+
echo $userInput;
|
|
49
|
+
|
|
50
|
+
// ✅ Always escape
|
|
51
|
+
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
|
|
52
|
+
|
|
53
|
+
// ✅ In templates (Blade)
|
|
54
|
+
{{ $userInput }} // Auto-escaped
|
|
55
|
+
{!! $trustedHtml !!} // Only for trusted content
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Security Checklist
|
|
59
|
+
|
|
60
|
+
- [ ] All SQL uses prepared statements
|
|
61
|
+
- [ ] All output is escaped (htmlspecialchars)
|
|
62
|
+
- [ ] CSRF tokens on all forms
|
|
63
|
+
- [ ] Passwords use password_hash/verify
|
|
64
|
+
- [ ] File uploads validated (type, size, extension)
|
|
65
|
+
- [ ] Sessions use httpOnly + secure cookies
|
|
66
|
+
- [ ] Input validated before processing
|
|
67
|
+
- [ ] No `eval()`, `exec()` with user input
|
|
68
|
+
- [ ] No `extract()` on user data
|
|
69
|
+
- [ ] Headers: X-Content-Type-Options, X-Frame-Options, CSP
|
|
70
|
+
|
|
71
|
+
## Sensitive Data Patterns (FORBIDDEN)
|
|
72
|
+
|
|
73
|
+
| Pattern | Risk |
|
|
74
|
+
|---------|------|
|
|
75
|
+
| `$_GET` used directly in SQL | SQL Injection |
|
|
76
|
+
| `echo $_POST[...]` | XSS |
|
|
77
|
+
| `md5()` / `sha1()` for passwords | Weak hashing |
|
|
78
|
+
| `eval($userInput)` | RCE |
|
|
79
|
+
| `include $_GET['page']` | LFI/RFI |
|
|
80
|
+
| `serialize()` user data | Object injection |
|