start-vibing-stacks 2.1.1 → 2.3.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 +2 -2
- package/dist/detector.js +4 -6
- package/dist/index.js +63 -2
- package/dist/scanner.d.ts +12 -0
- package/dist/scanner.js +480 -0
- package/dist/setup.js +29 -0
- package/dist/types.d.ts +20 -0
- package/dist/ui.js +1 -1
- package/package.json +1 -1
- package/stacks/_shared/hooks/user-prompt-submit.ts +26 -2
- package/stacks/frontend/react-inertia/skills/inertia-react/SKILL.md +342 -0
- package/stacks/frontend/react-inertia/skills/react-standards/SKILL.md +267 -0
- package/stacks/php/skills/api-security/SKILL.md +431 -0
- package/stacks/php/skills/laravel-octane/SKILL.md +155 -53
- package/stacks/php/skills/laravel-patterns/SKILL.md +244 -39
- package/stacks/php/skills/php-patterns/SKILL.md +113 -53
- package/stacks/php/skills/security-scan-php/SKILL.md +161 -43
- package/stacks/php/stack.json +19 -6
- package/templates/CLAUDE-php.md +108 -29
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# PHP 8.3+ Patterns
|
|
1
|
+
# PHP 8.3+ Patterns for Laravel
|
|
2
2
|
|
|
3
3
|
## Version Requirements
|
|
4
4
|
|
|
5
|
-
- **PHP >= 8.3** — MANDATORY.
|
|
5
|
+
- **PHP >= 8.3** — MANDATORY. Use all modern features.
|
|
6
6
|
- **Composer >= 2.0** — For dependency management.
|
|
7
7
|
- Use strict types: `declare(strict_types=1);` in EVERY file.
|
|
8
8
|
|
|
@@ -11,32 +11,48 @@
|
|
|
11
11
|
### Typed Properties & Constructor Promotion
|
|
12
12
|
|
|
13
13
|
```php
|
|
14
|
-
class
|
|
14
|
+
class CreateUserDTO {
|
|
15
15
|
public function __construct(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
public readonly string $name,
|
|
17
|
+
public readonly string $email,
|
|
18
|
+
public readonly int $age,
|
|
19
19
|
) {}
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
### Enums
|
|
23
|
+
### Enums (Use for Status, Types, Roles)
|
|
24
24
|
|
|
25
25
|
```php
|
|
26
|
-
enum
|
|
27
|
-
case
|
|
28
|
-
case
|
|
29
|
-
case
|
|
26
|
+
enum OrderStatus: string {
|
|
27
|
+
case Pending = 'pending';
|
|
28
|
+
case Processing = 'processing';
|
|
29
|
+
case Completed = 'completed';
|
|
30
|
+
case Cancelled = 'cancelled';
|
|
31
|
+
|
|
32
|
+
public function label(): string {
|
|
33
|
+
return match($this) {
|
|
34
|
+
self::Pending => 'Awaiting Processing',
|
|
35
|
+
self::Processing => 'In Progress',
|
|
36
|
+
self::Completed => 'Done',
|
|
37
|
+
self::Cancelled => 'Cancelled',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
30
40
|
}
|
|
41
|
+
|
|
42
|
+
// In Eloquent model:
|
|
43
|
+
protected $casts = [
|
|
44
|
+
'status' => OrderStatus::class,
|
|
45
|
+
];
|
|
31
46
|
```
|
|
32
47
|
|
|
33
|
-
### Readonly Classes
|
|
48
|
+
### Readonly Classes for DTOs
|
|
34
49
|
|
|
35
50
|
```php
|
|
36
|
-
readonly class
|
|
51
|
+
readonly class PaymentResult {
|
|
37
52
|
public function __construct(
|
|
38
|
-
public string $
|
|
39
|
-
public
|
|
53
|
+
public string $transactionId,
|
|
54
|
+
public float $amount,
|
|
55
|
+
public bool $success,
|
|
40
56
|
) {}
|
|
41
57
|
}
|
|
42
58
|
```
|
|
@@ -44,45 +60,103 @@ readonly class UserDTO {
|
|
|
44
60
|
### Typed Class Constants (8.3+)
|
|
45
61
|
|
|
46
62
|
```php
|
|
47
|
-
class
|
|
48
|
-
public const
|
|
49
|
-
public const int
|
|
63
|
+
class RateLimiter {
|
|
64
|
+
public const int MAX_ATTEMPTS = 5;
|
|
65
|
+
public const int DECAY_SECONDS = 60;
|
|
66
|
+
public const string CACHE_PREFIX = 'rate_limit';
|
|
50
67
|
}
|
|
51
68
|
```
|
|
52
69
|
|
|
53
|
-
### Match Expression
|
|
70
|
+
### Match Expression (Prefer over switch)
|
|
54
71
|
|
|
55
72
|
```php
|
|
56
|
-
$result = match($
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
|
|
73
|
+
$result = match($request->input('action')) {
|
|
74
|
+
'approve' => $service->approve($record),
|
|
75
|
+
'reject' => $service->reject($record),
|
|
76
|
+
'escalate' => $service->escalate($record),
|
|
77
|
+
default => throw new \InvalidArgumentException("Unknown action"),
|
|
60
78
|
};
|
|
61
79
|
```
|
|
62
80
|
|
|
63
81
|
### Named Arguments
|
|
64
82
|
|
|
65
83
|
```php
|
|
66
|
-
$response =
|
|
67
|
-
|
|
84
|
+
$response = Response::json(
|
|
85
|
+
data: $collection,
|
|
68
86
|
status: 200,
|
|
69
|
-
headers: ['
|
|
87
|
+
headers: ['X-Total-Count' => $total],
|
|
70
88
|
);
|
|
71
89
|
```
|
|
72
90
|
|
|
73
91
|
### Null-safe Operator
|
|
74
92
|
|
|
75
93
|
```php
|
|
76
|
-
$country = $user?->address?->country?->name;
|
|
94
|
+
$country = $user?->address?->country?->name ?? 'Unknown';
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### First-class Callable Syntax
|
|
98
|
+
|
|
99
|
+
```php
|
|
100
|
+
$filtered = $collection->filter($this->isEligible(...));
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Clean Code Patterns
|
|
104
|
+
|
|
105
|
+
### Dependency Injection (Octane-safe)
|
|
106
|
+
|
|
107
|
+
```php
|
|
108
|
+
// CORRECT: Constructor injection
|
|
109
|
+
class OrderService {
|
|
110
|
+
public function __construct(
|
|
111
|
+
private readonly PaymentGateway $gateway,
|
|
112
|
+
private readonly OrderRepository $orders,
|
|
113
|
+
private readonly LoggerInterface $logger,
|
|
114
|
+
) {}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// WRONG: Service locator
|
|
118
|
+
class OrderService {
|
|
119
|
+
public function process(): void {
|
|
120
|
+
$gateway = app(PaymentGateway::class); // Avoid in Octane
|
|
121
|
+
}
|
|
122
|
+
}
|
|
77
123
|
```
|
|
78
124
|
|
|
79
|
-
###
|
|
125
|
+
### Service Architecture
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
App\Services\
|
|
129
|
+
├── UserService.php # Core user operations
|
|
130
|
+
├── PaymentService.php # Payment processing
|
|
131
|
+
└── AdPlatforms\
|
|
132
|
+
├── AdPlatformService.php # Main service
|
|
133
|
+
└── Helpers\
|
|
134
|
+
├── GoogleAdsHelper.php # Extracted complex logic
|
|
135
|
+
└── MetaAdsHelper.php
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Rule:** When a service class exceeds ~200 lines, extract specific logic into `Helpers` sub-namespace.
|
|
139
|
+
|
|
140
|
+
### Return Types & Exceptions
|
|
80
141
|
|
|
81
142
|
```php
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
143
|
+
public function findOrFail(string $id): User
|
|
144
|
+
{
|
|
145
|
+
return User::findOrFail($id);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public function process(Order $order): PaymentResult
|
|
149
|
+
{
|
|
150
|
+
try {
|
|
151
|
+
return $this->gateway->charge($order);
|
|
152
|
+
} catch (GatewayException $e) {
|
|
153
|
+
$this->logger->error('Payment failed', [
|
|
154
|
+
'order_id' => $order->id,
|
|
155
|
+
'error' => $e->getMessage(),
|
|
156
|
+
]);
|
|
157
|
+
throw new PaymentFailedException($order, $e);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
86
160
|
```
|
|
87
161
|
|
|
88
162
|
## FORBIDDEN Patterns
|
|
@@ -92,28 +166,14 @@ $fiber = new Fiber(function (): void {
|
|
|
92
166
|
| `$var = isset($x) ? $x : $default` | `$var = $x ?? $default` |
|
|
93
167
|
| `function foo($x)` (no types) | `function foo(string $x): void` |
|
|
94
168
|
| `array()` | `[]` |
|
|
95
|
-
| `mysql_*` functions | PDO or Doctrine |
|
|
96
|
-
| Global variables | Dependency injection |
|
|
97
|
-
| `eval()` | Never use eval |
|
|
98
169
|
| Untyped properties | Always type properties |
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
```
|
|
170
|
+
| `static` properties on services | Instance properties (Octane-safe) |
|
|
171
|
+
| Global variables | Dependency injection |
|
|
172
|
+
| `switch` with simple mapping | `match` expression |
|
|
173
|
+
| Large service classes (200+ lines) | Extract into Helpers |
|
|
174
|
+
| `mixed` type without justification | Use specific types or union types |
|
|
113
175
|
|
|
114
176
|
## PSR Standards
|
|
115
177
|
|
|
116
|
-
- **PSR-4**: Autoloading
|
|
117
|
-
- **PSR-
|
|
118
|
-
- **PSR-12**: Coding Style
|
|
119
|
-
- **PSR-15**: HTTP Handlers
|
|
178
|
+
- **PSR-4**: Autoloading (Laravel default)
|
|
179
|
+
- **PSR-12**: Coding Style (enforced by PHP-CS-Fixer)
|
|
@@ -1,80 +1,198 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Laravel Security Scan
|
|
2
2
|
|
|
3
|
-
## OWASP Top 10 for
|
|
3
|
+
## OWASP Top 10 for Laravel
|
|
4
4
|
|
|
5
5
|
### A01 - Broken Access Control
|
|
6
6
|
|
|
7
7
|
```php
|
|
8
|
-
//
|
|
9
|
-
$userId = $
|
|
8
|
+
// WRONG: Trust user input for authorization
|
|
9
|
+
$userId = $request->input('user_id');
|
|
10
10
|
$user = User::find($userId);
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
$user =
|
|
12
|
+
// CORRECT: Use authenticated session
|
|
13
|
+
$user = $request->user();
|
|
14
|
+
|
|
15
|
+
// CORRECT: Scope queries by authenticated user
|
|
16
|
+
$query = Resource::query();
|
|
17
|
+
if (!$request->user()->isAdmin()) {
|
|
18
|
+
$query->where('user_id', $request->user()->id);
|
|
19
|
+
}
|
|
14
20
|
```
|
|
15
21
|
|
|
22
|
+
**Rules:**
|
|
23
|
+
- NEVER return unscoped queries — always filter by authenticated user
|
|
24
|
+
- Use Policies for authorization logic
|
|
25
|
+
- Use Form Requests with `authorize()` method
|
|
26
|
+
- Use Route Model Binding with scoping
|
|
27
|
+
|
|
16
28
|
### A02 - Cryptographic Failures
|
|
17
29
|
|
|
18
30
|
```php
|
|
19
|
-
//
|
|
31
|
+
// WRONG
|
|
20
32
|
md5($password);
|
|
21
33
|
sha1($password);
|
|
22
34
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
// CORRECT (Laravel handles this via Hash facade)
|
|
36
|
+
Hash::make($password);
|
|
37
|
+
Hash::check($input, $hashedPassword);
|
|
38
|
+
|
|
39
|
+
// For API tokens: use Laravel Sanctum
|
|
40
|
+
// Table: users_access_tokens (UUIDs + Soft Deletes)
|
|
41
|
+
// All token actions logged via Loggable trait
|
|
26
42
|
```
|
|
27
43
|
|
|
28
44
|
### A03 - Injection
|
|
29
45
|
|
|
30
46
|
```php
|
|
31
|
-
//
|
|
32
|
-
$
|
|
47
|
+
// WRONG: SQL Injection via DB::raw
|
|
48
|
+
$users = DB::select("SELECT * FROM users WHERE id = {$id}");
|
|
49
|
+
DB::raw("WHERE name = '{$name}'");
|
|
33
50
|
|
|
34
|
-
//
|
|
35
|
-
$
|
|
36
|
-
$stmt->execute([':id' => $id]);
|
|
51
|
+
// CORRECT: Eloquent (preferred)
|
|
52
|
+
$user = User::findOrFail($id);
|
|
37
53
|
|
|
38
|
-
//
|
|
39
|
-
$
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
// CORRECT: Query Builder with bindings
|
|
55
|
+
$users = DB::table('users')->where('name', $name)->get();
|
|
56
|
+
|
|
57
|
+
// CORRECT: Raw with parameter binding (last resort)
|
|
58
|
+
$results = DB::select('SELECT * FROM users WHERE id = ?', [$id]);
|
|
42
59
|
```
|
|
43
60
|
|
|
44
61
|
### A07 - XSS Prevention
|
|
45
62
|
|
|
46
63
|
```php
|
|
47
|
-
//
|
|
48
|
-
|
|
64
|
+
// WRONG: Unescaped output in Blade
|
|
65
|
+
{!! $userInput !!}
|
|
49
66
|
|
|
50
|
-
//
|
|
51
|
-
|
|
67
|
+
// CORRECT: Auto-escaped (Blade default)
|
|
68
|
+
{{ $userInput }}
|
|
52
69
|
|
|
53
|
-
//
|
|
54
|
-
{
|
|
55
|
-
{!! $trustedHtml !!} // Only for trusted content
|
|
70
|
+
// Only use {!! !!} for TRUSTED, pre-sanitized HTML
|
|
71
|
+
{!! $trustedHtmlFromCMS !!}
|
|
56
72
|
```
|
|
57
73
|
|
|
58
|
-
|
|
74
|
+
### A08 - Insecure Deserialization
|
|
75
|
+
|
|
76
|
+
```php
|
|
77
|
+
// WRONG: Unserialize user data
|
|
78
|
+
$data = unserialize($request->input('data'));
|
|
79
|
+
|
|
80
|
+
// CORRECT: Use JSON with Model casts
|
|
81
|
+
protected $casts = [
|
|
82
|
+
'metadata' => 'array',
|
|
83
|
+
'settings' => 'array',
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
// CORRECT: Defensive JSON handling
|
|
87
|
+
$data = is_string($model->data)
|
|
88
|
+
? json_decode($model->data, true)
|
|
89
|
+
: $model->data;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Laravel-Specific Security
|
|
93
|
+
|
|
94
|
+
### Mass Assignment Protection
|
|
95
|
+
|
|
96
|
+
```php
|
|
97
|
+
// WRONG: No protection
|
|
98
|
+
$user = User::create($request->all());
|
|
99
|
+
|
|
100
|
+
// CORRECT: Define $fillable
|
|
101
|
+
class User extends Model {
|
|
102
|
+
protected $fillable = ['name', 'email'];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// CORRECT: Use Form Request validated data
|
|
106
|
+
$user = User::create($request->validated());
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Environment Variables
|
|
110
|
+
|
|
111
|
+
```php
|
|
112
|
+
// WRONG: env() outside config files (null when cached)
|
|
113
|
+
$apiKey = env('API_KEY');
|
|
59
114
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
115
|
+
// CORRECT: Use config files
|
|
116
|
+
// config/services.php
|
|
117
|
+
'stripe' => ['key' => env('STRIPE_KEY')],
|
|
118
|
+
|
|
119
|
+
// In code:
|
|
120
|
+
$apiKey = config('services.stripe.key');
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### CSRF & Authentication
|
|
124
|
+
|
|
125
|
+
```php
|
|
126
|
+
// Blade forms — CSRF automatic
|
|
127
|
+
<form method="POST">
|
|
128
|
+
@csrf
|
|
129
|
+
...
|
|
130
|
+
</form>
|
|
131
|
+
|
|
132
|
+
// API routes — use Sanctum middleware
|
|
133
|
+
Route::middleware('auth:sanctum')->group(function () {
|
|
134
|
+
Route::apiResource('users', UserController::class);
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### File Upload Validation
|
|
139
|
+
|
|
140
|
+
```php
|
|
141
|
+
public function rules(): array
|
|
142
|
+
{
|
|
143
|
+
return [
|
|
144
|
+
'avatar' => [
|
|
145
|
+
'required',
|
|
146
|
+
'image',
|
|
147
|
+
'mimes:jpg,jpeg,png,webp',
|
|
148
|
+
'max:2048', // 2MB
|
|
149
|
+
'dimensions:max_width=2000,max_height=2000',
|
|
150
|
+
],
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Octane Security Considerations
|
|
156
|
+
|
|
157
|
+
```php
|
|
158
|
+
// WRONG: Static state leaks user data between requests
|
|
159
|
+
class AuthService {
|
|
160
|
+
private static ?User $currentUser = null; // LEAKS!
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// WRONG: Superglobals are stale in Octane
|
|
164
|
+
$token = $_SERVER['HTTP_AUTHORIZATION'];
|
|
165
|
+
|
|
166
|
+
// CORRECT: Use Request object
|
|
167
|
+
$token = $request->bearerToken();
|
|
168
|
+
$user = $request->user();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Security Checklist
|
|
70
172
|
|
|
71
|
-
|
|
173
|
+
- [ ] All queries use Eloquent or Query Builder (no raw SQL with user input)
|
|
174
|
+
- [ ] All Blade output uses `{{ }}` (auto-escaped)
|
|
175
|
+
- [ ] CSRF protection on all forms (`@csrf`)
|
|
176
|
+
- [ ] Passwords use `Hash::make()` / `Hash::check()`
|
|
177
|
+
- [ ] File uploads validated (type, size, dimensions)
|
|
178
|
+
- [ ] API routes protected with Sanctum middleware
|
|
179
|
+
- [ ] Models define `$fillable` (no `$guarded = []`)
|
|
180
|
+
- [ ] Queries scoped by authenticated user (unless admin)
|
|
181
|
+
- [ ] No `env()` calls outside config files
|
|
182
|
+
- [ ] No static state in services (Octane-safe)
|
|
183
|
+
- [ ] No superglobals (`$_GET`, `$_POST`, `$_SESSION`)
|
|
184
|
+
- [ ] Sensitive headers set (X-Content-Type-Options, X-Frame-Options, CSP)
|
|
185
|
+
- [ ] Rate limiting on authentication endpoints
|
|
186
|
+
|
|
187
|
+
## Sensitive Patterns (FORBIDDEN)
|
|
72
188
|
|
|
73
189
|
| Pattern | Risk |
|
|
74
190
|
|---------|------|
|
|
75
|
-
|
|
|
76
|
-
| `
|
|
191
|
+
| `DB::raw()` with user input | SQL Injection |
|
|
192
|
+
| `{!! $userInput !!}` | XSS |
|
|
77
193
|
| `md5()` / `sha1()` for passwords | Weak hashing |
|
|
78
|
-
|
|
|
79
|
-
| `
|
|
80
|
-
|
|
|
194
|
+
| Dynamic code execution | RCE |
|
|
195
|
+
| `unserialize()` on user data | Object injection |
|
|
196
|
+
| `$request->all()` without `$fillable` | Mass assignment |
|
|
197
|
+
| `env()` in runtime code | Null after config cache |
|
|
198
|
+
| Static user state in services | Data leaks in Octane |
|
package/stacks/php/stack.json
CHANGED
|
@@ -27,13 +27,20 @@
|
|
|
27
27
|
"name": "Laravel 12 + Octane (RoadRunner) + Inertia.js",
|
|
28
28
|
"icon": "🚀",
|
|
29
29
|
"detectFiles": ["artisan", "rr.yaml"],
|
|
30
|
-
"default": true
|
|
30
|
+
"default": true,
|
|
31
|
+
"skills": [
|
|
32
|
+
"laravel-patterns",
|
|
33
|
+
"laravel-octane"
|
|
34
|
+
]
|
|
31
35
|
},
|
|
32
36
|
{
|
|
33
37
|
"id": "laravel",
|
|
34
38
|
"name": "Laravel 12 (standard)",
|
|
35
39
|
"icon": "🏗️",
|
|
36
|
-
"detectFiles": ["artisan", "bootstrap/app.php"]
|
|
40
|
+
"detectFiles": ["artisan", "bootstrap/app.php"],
|
|
41
|
+
"skills": [
|
|
42
|
+
"laravel-patterns"
|
|
43
|
+
]
|
|
37
44
|
}
|
|
38
45
|
],
|
|
39
46
|
"databases": [
|
|
@@ -46,12 +53,20 @@
|
|
|
46
53
|
"id": "react-inertia",
|
|
47
54
|
"name": "ReactJS 19 + Inertia.js + TailwindCSS 4",
|
|
48
55
|
"icon": "⚛️",
|
|
49
|
-
"default": true
|
|
56
|
+
"default": true,
|
|
57
|
+
"frameworks": ["laravel", "laravel-octane"]
|
|
50
58
|
},
|
|
51
59
|
{
|
|
52
60
|
"id": "blade",
|
|
53
61
|
"name": "Blade + TailwindCSS 4",
|
|
54
|
-
"icon": "🖼️"
|
|
62
|
+
"icon": "🖼️",
|
|
63
|
+
"frameworks": ["laravel", "laravel-octane"]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "livewire",
|
|
67
|
+
"name": "Livewire + Alpine.js",
|
|
68
|
+
"icon": "⚡",
|
|
69
|
+
"frameworks": ["laravel", "laravel-octane"]
|
|
55
70
|
},
|
|
56
71
|
{
|
|
57
72
|
"id": "none",
|
|
@@ -68,8 +83,6 @@
|
|
|
68
83
|
"phpstan-analysis",
|
|
69
84
|
"composer-workflow",
|
|
70
85
|
"security-scan-php",
|
|
71
|
-
"laravel-patterns",
|
|
72
|
-
"laravel-octane",
|
|
73
86
|
"api-design"
|
|
74
87
|
],
|
|
75
88
|
"requirements": [
|