start-vibing-stacks 2.2.0 → 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/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/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
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# React 19+ Standards (with Inertia.js)
|
|
2
|
+
|
|
3
|
+
## Version Requirements
|
|
4
|
+
|
|
5
|
+
- **ReactJS >= 19** — MANDATORY
|
|
6
|
+
- **TailwindCSS >= 4** — MANDATORY
|
|
7
|
+
- **Inertia.js >= 2** — MANDATORY
|
|
8
|
+
|
|
9
|
+
## Translation Pattern (via Inertia shared props)
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import __ from '@/Utils/translate';
|
|
13
|
+
|
|
14
|
+
// CORRECT: Translations as CONST at the top, BEFORE hooks
|
|
15
|
+
const LABELS = {
|
|
16
|
+
title: __('dashboard.title'),
|
|
17
|
+
save: __('common.save'),
|
|
18
|
+
cancel: __('common.cancel'),
|
|
19
|
+
errorRequired: __('errors.field_required'),
|
|
20
|
+
welcome: __('dashboard.welcome', { name: 'User' }),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default function Dashboard() {
|
|
24
|
+
const [data, setData] = useState(null);
|
|
25
|
+
|
|
26
|
+
return <h1>{LABELS.title}</h1>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// WRONG: __() inside JSX (Hook violation — usePage() is called internally)
|
|
30
|
+
return <h1>{__('dashboard.title')}</h1>; // NEVER
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Rules:**
|
|
34
|
+
- Translations in `CONST` variables before state hooks
|
|
35
|
+
- New strings must be added to `lang/en/*.php` AND `lang/pt/*.php`
|
|
36
|
+
- Error strings centralized in `lang/*/errors.php`
|
|
37
|
+
- Use replacements for dynamic values: `__('key', { name: value })`
|
|
38
|
+
|
|
39
|
+
## Debug Logging
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
const ENABLE_DASHBOARD_DEBUG = false;
|
|
43
|
+
|
|
44
|
+
const debugLog = (...args: unknown[]) => {
|
|
45
|
+
if (ENABLE_DASHBOARD_DEBUG) console.log('[Dashboard]', ...args);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default function Dashboard() {
|
|
49
|
+
debugLog('Rendering with data:', data);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Rule:** Never leave raw `console.log`. Always use controlled debug pattern.
|
|
54
|
+
|
|
55
|
+
## TailwindCSS Class Organization
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// CORRECT: Classes as CONST — clean JSX
|
|
59
|
+
const STYLES = {
|
|
60
|
+
container: 'flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm',
|
|
61
|
+
title: 'text-2xl font-bold text-gray-900',
|
|
62
|
+
button: 'px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition',
|
|
63
|
+
grid: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default function Dashboard() {
|
|
67
|
+
return (
|
|
68
|
+
<div className={STYLES.container}>
|
|
69
|
+
<h1 className={STYLES.title}>{LABELS.title}</h1>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// WRONG: Inline class soup
|
|
75
|
+
<div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm"> // NEVER
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## SVG Icons
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// CORRECT: Separate files, import with ?react
|
|
82
|
+
import CheckIcon from '@/Icons/CheckIcon.svg?react';
|
|
83
|
+
import { CheckIcon, AlertIcon } from '@/Icons';
|
|
84
|
+
|
|
85
|
+
// WRONG: Inline SVG (bloats JSX)
|
|
86
|
+
<svg viewBox="0 0 24 24">...</svg> // NEVER
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Structure:**
|
|
90
|
+
```
|
|
91
|
+
resources/js/Icons/
|
|
92
|
+
├── index.js # Barrel export
|
|
93
|
+
├── CheckIcon.svg
|
|
94
|
+
├── AlertIcon.svg
|
|
95
|
+
└── SpinnerIcon.svg
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Inertia.js Hooks & Navigation
|
|
99
|
+
|
|
100
|
+
### Accessing Shared Props
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { usePage } from '@inertiajs/react';
|
|
104
|
+
|
|
105
|
+
export default function Header() {
|
|
106
|
+
const { auth, locale, flash } = usePage().props;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<nav>
|
|
110
|
+
<span>{auth.user?.name}</span>
|
|
111
|
+
{flash.success && <Alert>{flash.success}</Alert>}
|
|
112
|
+
</nav>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Forms with useForm
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { useForm } from '@inertiajs/react';
|
|
121
|
+
|
|
122
|
+
export default function CreateOrder() {
|
|
123
|
+
const { data, setData, post, processing, errors } = useForm({
|
|
124
|
+
product_id: '',
|
|
125
|
+
quantity: 1,
|
|
126
|
+
notes: '',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const handleSubmit = (e) => {
|
|
130
|
+
e.preventDefault();
|
|
131
|
+
post(route('orders.store'));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<form onSubmit={handleSubmit}>
|
|
136
|
+
<Input
|
|
137
|
+
value={data.product_id}
|
|
138
|
+
onChange={(e) => setData('product_id', e.target.value)}
|
|
139
|
+
error={errors.product_id}
|
|
140
|
+
/>
|
|
141
|
+
<Button type="submit" loading={processing}>
|
|
142
|
+
{processing ? <LoadingSpinner /> : LABELS.save}
|
|
143
|
+
</Button>
|
|
144
|
+
</form>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Rules:**
|
|
150
|
+
- Use `useForm` for ALL form submissions (handles CSRF, errors, loading)
|
|
151
|
+
- `processing` boolean for button loading states
|
|
152
|
+
- `errors` object maps to Form Request validation errors
|
|
153
|
+
- Never use `fetch()` or `axios` for form submissions
|
|
154
|
+
|
|
155
|
+
### Navigation
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { Link, router } from '@inertiajs/react';
|
|
159
|
+
|
|
160
|
+
// SPA links (no full page reload)
|
|
161
|
+
<Link href={route('orders.index')}>Orders</Link>
|
|
162
|
+
|
|
163
|
+
// Programmatic navigation
|
|
164
|
+
router.visit(route('dashboard'));
|
|
165
|
+
|
|
166
|
+
// Partial reload (only refresh specific props)
|
|
167
|
+
router.reload({ only: ['stats', 'recentOrders'] });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Loading States
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// CORRECT: Always show loading feedback
|
|
174
|
+
export default function DataTable() {
|
|
175
|
+
const [loading, setLoading] = useState(true);
|
|
176
|
+
|
|
177
|
+
if (loading) {
|
|
178
|
+
return <SectionLoader />;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return <Table data={data} />;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Button loading (from useForm)
|
|
185
|
+
<Button onClick={handleSave} disabled={processing}>
|
|
186
|
+
{processing ? <LoadingSpinner /> : LABELS.save}
|
|
187
|
+
</Button>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Rule:** Every data-heavy section needs a loading state.
|
|
191
|
+
|
|
192
|
+
## Modal Data Flow
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
interface EditModalProps {
|
|
196
|
+
item: Item;
|
|
197
|
+
isOpen: boolean;
|
|
198
|
+
onClose: () => void;
|
|
199
|
+
onUpdated: () => void;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function EditModal({ item, isOpen, onClose, onUpdated }: EditModalProps) {
|
|
203
|
+
const { data, setData, put, processing } = useForm({
|
|
204
|
+
name: item.name,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const handleSave = (e) => {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
put(route('items.update', item.id), {
|
|
210
|
+
onSuccess: () => {
|
|
211
|
+
onUpdated();
|
|
212
|
+
onClose();
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Parent: use router.reload for refresh after modal mutation
|
|
219
|
+
<EditModal
|
|
220
|
+
item={selectedItem}
|
|
221
|
+
isOpen={showModal}
|
|
222
|
+
onClose={() => setShowModal(false)}
|
|
223
|
+
onUpdated={() => router.reload({ only: ['items'] })}
|
|
224
|
+
/>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Third-Party Libraries (Charts)
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
// CORRECT: Let React handle re-rendering
|
|
231
|
+
{chartData && (
|
|
232
|
+
<ApexChart
|
|
233
|
+
key={JSON.stringify(chartData)}
|
|
234
|
+
options={chartOptions}
|
|
235
|
+
series={chartData}
|
|
236
|
+
type="area"
|
|
237
|
+
/>
|
|
238
|
+
)}
|
|
239
|
+
|
|
240
|
+
// WRONG: Manual DOM manipulation
|
|
241
|
+
const chartRef = useRef(null);
|
|
242
|
+
chartRef.current.updateSeries(newData); // NEVER
|
|
243
|
+
|
|
244
|
+
// Memoize expensive computations
|
|
245
|
+
const processedData = useMemo(() => {
|
|
246
|
+
return heavyTransform(rawData);
|
|
247
|
+
}, [rawData]);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Rules:**
|
|
251
|
+
- No `useRef` for updating third-party components
|
|
252
|
+
- Conditional rendering: `data && <Component />`
|
|
253
|
+
- `useMemo` for expensive computations
|
|
254
|
+
- Loading states before rendering charts
|
|
255
|
+
|
|
256
|
+
## Forbidden Patterns
|
|
257
|
+
|
|
258
|
+
| Pattern | Reason | Use Instead |
|
|
259
|
+
|---------|--------|-------------|
|
|
260
|
+
| `fetch()` / `axios` for pages | Bypasses Inertia | `Inertia::render()` props |
|
|
261
|
+
| `__()` inside JSX | Hook violation | CONST at top |
|
|
262
|
+
| Inline SVGs | Bloats components | SVG files + `?react` |
|
|
263
|
+
| `<a href>` for internal links | Full reload | `<Link href>` |
|
|
264
|
+
| `window.location` | Full reload | `router.visit()` |
|
|
265
|
+
| Raw `console.log` | Uncontrolled | Debug constant pattern |
|
|
266
|
+
| Inline Tailwind soup | Unreadable | STYLES const object |
|
|
267
|
+
| `axios.post()` for forms | No CSRF/errors | `useForm().post()` |
|
|
@@ -1,92 +1,170 @@
|
|
|
1
1
|
# Laravel Octane (RoadRunner) Patterns
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## How Octane Works
|
|
4
4
|
|
|
5
|
-
Octane runs your app in a **long-lived process
|
|
5
|
+
Octane runs your app in a **long-lived worker process**. The application boots ONCE and handles many requests without restarting. This is fundamentally different from traditional PHP where each request boots a fresh process.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Consequence:** Any state stored in static properties, globals, or singletons persists across requests and can leak between users.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
// Dependency Injection over resolve()
|
|
11
|
-
public function __construct(
|
|
12
|
-
private readonly UserService $userService,
|
|
13
|
-
) {}
|
|
9
|
+
## Critical Rules
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
### DO: Dependency Injection
|
|
12
|
+
|
|
13
|
+
```php
|
|
14
|
+
// CORRECT: Constructor injection (resolved fresh per request scope)
|
|
15
|
+
class OrderController extends Controller
|
|
17
16
|
{
|
|
18
|
-
|
|
17
|
+
public function __construct(
|
|
18
|
+
private readonly OrderService $orderService,
|
|
19
|
+
private readonly CacheManager $cache,
|
|
20
|
+
) {}
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
// CORRECT: Method injection in controller actions
|
|
24
|
+
public function store(StoreOrderRequest $request, PaymentGateway $gateway): JsonResponse
|
|
25
|
+
{
|
|
26
|
+
$result = $gateway->charge($request->validated());
|
|
27
|
+
return response()->json($result, 201);
|
|
28
|
+
}
|
|
25
29
|
```
|
|
26
30
|
|
|
27
|
-
###
|
|
31
|
+
### DO: Use the Request Object
|
|
28
32
|
|
|
29
33
|
```php
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
public function store(Request $request): JsonResponse
|
|
35
|
+
{
|
|
36
|
+
$name = $request->input('name');
|
|
37
|
+
$token = $request->bearerToken();
|
|
38
|
+
$ip = $request->ip();
|
|
39
|
+
$user = $request->user();
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
// Global variables
|
|
36
|
-
global $user; // ❌
|
|
37
|
-
|
|
38
|
-
// Modifying config at runtime
|
|
39
|
-
config(['app.name' => 'New']); // ❌ Affects ALL requests
|
|
40
|
-
|
|
41
|
-
// die() or exit()
|
|
42
|
-
die('error'); // ❌ Kills the worker
|
|
43
|
-
|
|
44
|
-
// Superglobals
|
|
45
|
-
$_GET['id']; // ❌ Stale between requests
|
|
46
|
-
$_POST['name']; // ❌
|
|
47
|
-
$_SESSION['user']; // ❌
|
|
48
41
|
```
|
|
49
42
|
|
|
50
|
-
###
|
|
43
|
+
### DO: Persistent DB Connections with Flush
|
|
51
44
|
|
|
52
45
|
```php
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
// config/database.php
|
|
47
|
+
'mysql' => [
|
|
48
|
+
'options' => [
|
|
49
|
+
PDO::ATTR_PERSISTENT => true,
|
|
50
|
+
],
|
|
51
|
+
],
|
|
52
|
+
|
|
53
|
+
// Octane automatically flushes connections between requests
|
|
54
|
+
// via Octane::prepare() — no manual work needed
|
|
61
55
|
```
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
### Memoization in Workers
|
|
57
|
+
### DO: Instance-scoped Memoization
|
|
66
58
|
|
|
67
59
|
```php
|
|
68
60
|
// ✅ Scoped to instance, cleared per request
|
|
69
61
|
class ProcessLeadsJob implements ShouldQueue
|
|
70
62
|
{
|
|
71
63
|
private array $lookupCache = [];
|
|
72
|
-
|
|
64
|
+
|
|
73
65
|
public function handle(): void
|
|
74
66
|
{
|
|
75
67
|
foreach ($this->leads as $lead) {
|
|
76
|
-
$result = $this->lookupCache[$lead->key]
|
|
68
|
+
$result = $this->lookupCache[$lead->key]
|
|
77
69
|
??= $this->expensiveLookup($lead->key);
|
|
78
70
|
}
|
|
79
|
-
// Cache is
|
|
71
|
+
// Cache is garbage collected when job instance is destroyed
|
|
80
72
|
}
|
|
81
73
|
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## NEVER Do These in Octane
|
|
77
|
+
|
|
78
|
+
### No Static State
|
|
79
|
+
|
|
80
|
+
```php
|
|
81
|
+
// WRONG: Static properties persist across ALL requests
|
|
82
|
+
class AuthService {
|
|
83
|
+
private static ?User $currentUser = null; // LEAKS between users!
|
|
84
|
+
private static array $cache = []; // Grows forever!
|
|
85
|
+
}
|
|
82
86
|
|
|
83
|
-
//
|
|
84
|
-
|
|
87
|
+
// CORRECT: Instance properties (scoped to request)
|
|
88
|
+
class AuthService {
|
|
89
|
+
private ?User $currentUser = null;
|
|
90
|
+
private array $cache = [];
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### No Global Variables
|
|
95
|
+
|
|
96
|
+
```php
|
|
97
|
+
// WRONG
|
|
98
|
+
global $config;
|
|
99
|
+
$GLOBALS['user'] = $user;
|
|
100
|
+
|
|
101
|
+
// CORRECT: Use DI or config()
|
|
102
|
+
$value = config('app.name');
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### No Runtime Config Mutation
|
|
106
|
+
|
|
107
|
+
```php
|
|
108
|
+
// WRONG: Affects ALL concurrent requests in the worker
|
|
109
|
+
config(['app.timezone' => 'America/Sao_Paulo']);
|
|
110
|
+
config(['services.stripe.key' => $newKey]);
|
|
111
|
+
|
|
112
|
+
// CORRECT: Pass values explicitly
|
|
113
|
+
$this->service->processWithTimezone('America/Sao_Paulo');
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### No Process Termination
|
|
117
|
+
|
|
118
|
+
```php
|
|
119
|
+
// WRONG: Kills the entire worker process
|
|
120
|
+
die('Something went wrong');
|
|
121
|
+
exit(1);
|
|
122
|
+
dd($variable);
|
|
123
|
+
|
|
124
|
+
// CORRECT: Throw exceptions (handled by Laravel)
|
|
125
|
+
throw new RuntimeException('Something went wrong');
|
|
126
|
+
abort(500, 'Internal error');
|
|
127
|
+
|
|
128
|
+
// For debugging: use dump() without die
|
|
129
|
+
dump($variable); // Outputs without killing worker
|
|
130
|
+
Log::debug('Debug info', ['var' => $variable]);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### No Superglobals
|
|
134
|
+
|
|
135
|
+
```php
|
|
136
|
+
// WRONG: Stale data from previous requests
|
|
137
|
+
$_GET['id'];
|
|
138
|
+
$_POST['name'];
|
|
139
|
+
$_SESSION['user'];
|
|
140
|
+
$_SERVER['HTTP_AUTHORIZATION'];
|
|
141
|
+
$_COOKIE['token'];
|
|
142
|
+
|
|
143
|
+
// CORRECT: Use Request object
|
|
144
|
+
$request->input('id');
|
|
145
|
+
$request->post('name');
|
|
146
|
+
$request->session()->get('user');
|
|
147
|
+
$request->header('Authorization');
|
|
148
|
+
$request->cookie('token');
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### No Singleton Abuse
|
|
152
|
+
|
|
153
|
+
```php
|
|
154
|
+
// WRONG: Singleton state leaks
|
|
155
|
+
app()->singleton('cart', fn () => new Cart());
|
|
156
|
+
// The same Cart instance serves ALL users!
|
|
157
|
+
|
|
158
|
+
// CORRECT: Use scoped bindings
|
|
159
|
+
app()->scoped('cart', fn () => new Cart());
|
|
160
|
+
// Fresh instance per request, cleared between requests
|
|
85
161
|
```
|
|
86
162
|
|
|
87
163
|
## RoadRunner Configuration (rr.yaml)
|
|
88
164
|
|
|
89
165
|
```yaml
|
|
166
|
+
version: '3'
|
|
167
|
+
|
|
90
168
|
server:
|
|
91
169
|
command: "php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=8000"
|
|
92
170
|
|
|
@@ -94,17 +172,41 @@ http:
|
|
|
94
172
|
address: 0.0.0.0:8000
|
|
95
173
|
pool:
|
|
96
174
|
num_workers: 4
|
|
97
|
-
max_jobs: 500
|
|
175
|
+
max_jobs: 500
|
|
98
176
|
supervisor:
|
|
99
|
-
max_worker_memory: 128
|
|
177
|
+
max_worker_memory: 128
|
|
100
178
|
|
|
101
179
|
logs:
|
|
102
180
|
level: info
|
|
181
|
+
encoding: json
|
|
103
182
|
```
|
|
104
183
|
|
|
184
|
+
### Worker Tuning
|
|
185
|
+
|
|
186
|
+
| Setting | Default | Description |
|
|
187
|
+
|---------|---------|-------------|
|
|
188
|
+
| `num_workers` | CPU cores | Number of worker processes |
|
|
189
|
+
| `max_jobs` | 500 | Restart worker after N requests (memory safety) |
|
|
190
|
+
| `max_worker_memory` | 128 MB | Kill worker if exceeds memory |
|
|
191
|
+
|
|
192
|
+
**Rule:** Always set `max_jobs` to prevent memory leaks from accumulating.
|
|
193
|
+
|
|
105
194
|
## Database Safety
|
|
106
195
|
|
|
107
196
|
- **NEVER** execute `db:wipe`, `migrate:fresh`, `migrate:refresh`, `db:reset`
|
|
108
197
|
- **ALWAYS** use incremental migrations (`make:migration`)
|
|
109
|
-
- If migration fails
|
|
198
|
+
- If migration fails, fix the file or create a new one
|
|
110
199
|
- Assume **all environments contain critical data**
|
|
200
|
+
|
|
201
|
+
## Octane Checklist
|
|
202
|
+
|
|
203
|
+
- [ ] No `static` properties on service classes
|
|
204
|
+
- [ ] No global variables or `$GLOBALS`
|
|
205
|
+
- [ ] No `config()` mutations at runtime
|
|
206
|
+
- [ ] No `die()`, `exit()`, or `dd()` in production code
|
|
207
|
+
- [ ] No superglobals (`$_GET`, `$_POST`, `$_SESSION`, `$_SERVER`)
|
|
208
|
+
- [ ] No `app()->singleton()` for request-scoped data (use `scoped`)
|
|
209
|
+
- [ ] All services use constructor DI (not `app()` or `resolve()`)
|
|
210
|
+
- [ ] Memoization scoped to instance, not static
|
|
211
|
+
- [ ] `max_jobs` configured in `rr.yaml` for memory safety
|
|
212
|
+
- [ ] Persistent DB connections enabled with Octane flush
|