autoworkflow 3.1.5 → 3.5.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/.claude/commands/analyze.md +19 -0
- package/.claude/commands/audit.md +26 -0
- package/.claude/commands/build.md +39 -0
- package/.claude/commands/commit.md +25 -0
- package/.claude/commands/fix.md +23 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/suggest.md +23 -0
- package/.claude/commands/verify.md +18 -0
- package/.claude/hooks/post-bash-router.sh +20 -0
- package/.claude/hooks/post-commit.sh +140 -0
- package/.claude/hooks/pre-edit.sh +129 -0
- package/.claude/hooks/session-check.sh +79 -0
- package/.claude/settings.json +40 -6
- package/.claude/settings.local.json +3 -1
- package/.claude/skills/actix.md +337 -0
- package/.claude/skills/alembic.md +504 -0
- package/.claude/skills/angular.md +237 -0
- package/.claude/skills/api-design.md +187 -0
- package/.claude/skills/aspnet-core.md +377 -0
- package/.claude/skills/astro.md +245 -0
- package/.claude/skills/auth-clerk.md +327 -0
- package/.claude/skills/auth-firebase.md +367 -0
- package/.claude/skills/auth-nextauth.md +359 -0
- package/.claude/skills/auth-supabase.md +368 -0
- package/.claude/skills/axum.md +386 -0
- package/.claude/skills/blazor.md +456 -0
- package/.claude/skills/chi.md +348 -0
- package/.claude/skills/code-review.md +133 -0
- package/.claude/skills/csharp.md +296 -0
- package/.claude/skills/css-modules.md +325 -0
- package/.claude/skills/cypress.md +343 -0
- package/.claude/skills/debugging.md +133 -0
- package/.claude/skills/diesel.md +392 -0
- package/.claude/skills/django.md +301 -0
- package/.claude/skills/docker.md +319 -0
- package/.claude/skills/doctrine.md +473 -0
- package/.claude/skills/documentation.md +182 -0
- package/.claude/skills/dotnet.md +409 -0
- package/.claude/skills/drizzle.md +293 -0
- package/.claude/skills/echo.md +321 -0
- package/.claude/skills/eloquent.md +256 -0
- package/.claude/skills/emotion.md +426 -0
- package/.claude/skills/entity-framework.md +370 -0
- package/.claude/skills/express.md +316 -0
- package/.claude/skills/fastapi.md +329 -0
- package/.claude/skills/fastify.md +299 -0
- package/.claude/skills/fiber.md +315 -0
- package/.claude/skills/flask.md +322 -0
- package/.claude/skills/gin.md +342 -0
- package/.claude/skills/git.md +116 -0
- package/.claude/skills/github-actions.md +353 -0
- package/.claude/skills/go.md +377 -0
- package/.claude/skills/gorm.md +409 -0
- package/.claude/skills/graphql.md +478 -0
- package/.claude/skills/hibernate.md +379 -0
- package/.claude/skills/hono.md +306 -0
- package/.claude/skills/java.md +400 -0
- package/.claude/skills/jest.md +313 -0
- package/.claude/skills/jpa.md +282 -0
- package/.claude/skills/kotlin.md +347 -0
- package/.claude/skills/kubernetes.md +363 -0
- package/.claude/skills/laravel.md +414 -0
- package/.claude/skills/mcp-browser.md +320 -0
- package/.claude/skills/mcp-database.md +219 -0
- package/.claude/skills/mcp-fetch.md +241 -0
- package/.claude/skills/mcp-filesystem.md +204 -0
- package/.claude/skills/mcp-github.md +217 -0
- package/.claude/skills/mcp-memory.md +240 -0
- package/.claude/skills/mcp-search.md +218 -0
- package/.claude/skills/mcp-slack.md +262 -0
- package/.claude/skills/micronaut.md +388 -0
- package/.claude/skills/mongodb.md +319 -0
- package/.claude/skills/mongoose.md +355 -0
- package/.claude/skills/mysql.md +281 -0
- package/.claude/skills/nestjs.md +335 -0
- package/.claude/skills/nextjs-app-router.md +260 -0
- package/.claude/skills/nextjs-pages.md +172 -0
- package/.claude/skills/nuxt.md +202 -0
- package/.claude/skills/openapi.md +489 -0
- package/.claude/skills/performance.md +199 -0
- package/.claude/skills/php.md +398 -0
- package/.claude/skills/playwright.md +371 -0
- package/.claude/skills/postgresql.md +257 -0
- package/.claude/skills/prisma.md +293 -0
- package/.claude/skills/pydantic.md +304 -0
- package/.claude/skills/pytest.md +313 -0
- package/.claude/skills/python.md +272 -0
- package/.claude/skills/quarkus.md +377 -0
- package/.claude/skills/react.md +230 -0
- package/.claude/skills/redis.md +391 -0
- package/.claude/skills/refactoring.md +143 -0
- package/.claude/skills/remix.md +246 -0
- package/.claude/skills/rest-api.md +490 -0
- package/.claude/skills/rocket.md +366 -0
- package/.claude/skills/rust.md +341 -0
- package/.claude/skills/sass.md +380 -0
- package/.claude/skills/sea-orm.md +382 -0
- package/.claude/skills/security.md +167 -0
- package/.claude/skills/sequelize.md +395 -0
- package/.claude/skills/spring-boot.md +416 -0
- package/.claude/skills/sqlalchemy.md +269 -0
- package/.claude/skills/sqlx-rust.md +408 -0
- package/.claude/skills/state-jotai.md +346 -0
- package/.claude/skills/state-mobx.md +353 -0
- package/.claude/skills/state-pinia.md +431 -0
- package/.claude/skills/state-redux.md +337 -0
- package/.claude/skills/state-tanstack-query.md +434 -0
- package/.claude/skills/state-zustand.md +340 -0
- package/.claude/skills/styled-components.md +403 -0
- package/.claude/skills/svelte.md +238 -0
- package/.claude/skills/sveltekit.md +207 -0
- package/.claude/skills/symfony.md +437 -0
- package/.claude/skills/tailwind.md +279 -0
- package/.claude/skills/terraform.md +394 -0
- package/.claude/skills/testing-library.md +371 -0
- package/.claude/skills/trpc.md +426 -0
- package/.claude/skills/typeorm.md +368 -0
- package/.claude/skills/vitest.md +330 -0
- package/.claude/skills/vue.md +202 -0
- package/.claude/skills/warp.md +365 -0
- package/README.md +135 -52
- package/package.json +1 -1
- package/system/triggers.md +152 -11
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
# PHP Skill (PHP 8.2+)
|
|
2
|
+
|
|
3
|
+
## Constructor Property Promotion
|
|
4
|
+
\`\`\`php
|
|
5
|
+
<?php
|
|
6
|
+
|
|
7
|
+
declare(strict_types=1);
|
|
8
|
+
|
|
9
|
+
// Modern PHP 8+ syntax
|
|
10
|
+
readonly class User
|
|
11
|
+
{
|
|
12
|
+
public function __construct(
|
|
13
|
+
public string $id,
|
|
14
|
+
public string $email,
|
|
15
|
+
public string $name,
|
|
16
|
+
public bool $isActive = true,
|
|
17
|
+
public ?DateTimeImmutable $createdAt = null,
|
|
18
|
+
) {
|
|
19
|
+
$this->createdAt ??= new DateTimeImmutable();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Service with dependency injection
|
|
24
|
+
final readonly class UserService
|
|
25
|
+
{
|
|
26
|
+
public function __construct(
|
|
27
|
+
private UserRepositoryInterface $repository,
|
|
28
|
+
private PasswordHasherInterface $hasher,
|
|
29
|
+
private LoggerInterface $logger,
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
public function findById(string $id): ?User
|
|
33
|
+
{
|
|
34
|
+
return $this->repository->find($id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
## Enums
|
|
40
|
+
\`\`\`php
|
|
41
|
+
<?php
|
|
42
|
+
|
|
43
|
+
// Backed enum
|
|
44
|
+
enum UserStatus: string
|
|
45
|
+
{
|
|
46
|
+
case Active = 'active';
|
|
47
|
+
case Pending = 'pending';
|
|
48
|
+
case Suspended = 'suspended';
|
|
49
|
+
case Deleted = 'deleted';
|
|
50
|
+
|
|
51
|
+
public function label(): string
|
|
52
|
+
{
|
|
53
|
+
return match($this) {
|
|
54
|
+
self::Active => 'Active User',
|
|
55
|
+
self::Pending => 'Awaiting Verification',
|
|
56
|
+
self::Suspended => 'Account Suspended',
|
|
57
|
+
self::Deleted => 'Account Deleted',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public function canLogin(): bool
|
|
62
|
+
{
|
|
63
|
+
return $this === self::Active;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Usage
|
|
68
|
+
$status = UserStatus::Active;
|
|
69
|
+
$status->value; // 'active'
|
|
70
|
+
$status->label(); // 'Active User'
|
|
71
|
+
|
|
72
|
+
// From string
|
|
73
|
+
$status = UserStatus::from('active'); // Throws if invalid
|
|
74
|
+
$status = UserStatus::tryFrom('invalid'); // Returns null
|
|
75
|
+
\`\`\`
|
|
76
|
+
|
|
77
|
+
## Attributes
|
|
78
|
+
\`\`\`php
|
|
79
|
+
<?php
|
|
80
|
+
|
|
81
|
+
use Attribute;
|
|
82
|
+
|
|
83
|
+
// Define attribute
|
|
84
|
+
#[Attribute(Attribute::TARGET_PROPERTY)]
|
|
85
|
+
final readonly class Validate
|
|
86
|
+
{
|
|
87
|
+
public function __construct(
|
|
88
|
+
public string $rule,
|
|
89
|
+
public ?string $message = null,
|
|
90
|
+
) {}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[Attribute(Attribute::TARGET_METHOD)]
|
|
94
|
+
final readonly class Route
|
|
95
|
+
{
|
|
96
|
+
public function __construct(
|
|
97
|
+
public string $path,
|
|
98
|
+
public string $method = 'GET',
|
|
99
|
+
public array $middleware = [],
|
|
100
|
+
) {}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Usage
|
|
104
|
+
final class UserController
|
|
105
|
+
{
|
|
106
|
+
#[Route('/users/{id}', 'GET')]
|
|
107
|
+
public function show(string $id): JsonResponse
|
|
108
|
+
{
|
|
109
|
+
// ...
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[Route('/users', 'POST', middleware: ['auth'])]
|
|
113
|
+
public function store(Request $request): JsonResponse
|
|
114
|
+
{
|
|
115
|
+
// ...
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// DTO with validation attributes
|
|
120
|
+
final readonly class CreateUserRequest
|
|
121
|
+
{
|
|
122
|
+
public function __construct(
|
|
123
|
+
#[Validate('email', 'Invalid email format')]
|
|
124
|
+
public string $email,
|
|
125
|
+
|
|
126
|
+
#[Validate('min:2|max:100')]
|
|
127
|
+
public string $name,
|
|
128
|
+
|
|
129
|
+
#[Validate('min:8')]
|
|
130
|
+
public string $password,
|
|
131
|
+
) {}
|
|
132
|
+
}
|
|
133
|
+
\`\`\`
|
|
134
|
+
|
|
135
|
+
## Match Expressions
|
|
136
|
+
\`\`\`php
|
|
137
|
+
<?php
|
|
138
|
+
|
|
139
|
+
// Match expression (exhaustive, returns value)
|
|
140
|
+
$message = match($status) {
|
|
141
|
+
UserStatus::Active => 'Welcome back!',
|
|
142
|
+
UserStatus::Pending => 'Please verify your email',
|
|
143
|
+
UserStatus::Suspended => 'Account suspended',
|
|
144
|
+
UserStatus::Deleted => 'Account not found',
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// With conditions
|
|
148
|
+
$discount = match(true) {
|
|
149
|
+
$total >= 1000 => 0.20,
|
|
150
|
+
$total >= 500 => 0.15,
|
|
151
|
+
$total >= 100 => 0.10,
|
|
152
|
+
default => 0,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Multiple conditions
|
|
156
|
+
$category = match($code) {
|
|
157
|
+
200, 201, 204 => 'success',
|
|
158
|
+
400, 401, 403, 404 => 'client_error',
|
|
159
|
+
500, 502, 503 => 'server_error',
|
|
160
|
+
default => 'unknown',
|
|
161
|
+
};
|
|
162
|
+
\`\`\`
|
|
163
|
+
|
|
164
|
+
## Null Safety and Error Handling
|
|
165
|
+
\`\`\`php
|
|
166
|
+
<?php
|
|
167
|
+
|
|
168
|
+
// Nullsafe operator
|
|
169
|
+
$country = $user?->address?->country?->name;
|
|
170
|
+
|
|
171
|
+
// Null coalescing
|
|
172
|
+
$name = $user->name ?? 'Anonymous';
|
|
173
|
+
$name ??= 'Default'; // Assignment
|
|
174
|
+
|
|
175
|
+
// Named arguments
|
|
176
|
+
$user = new User(
|
|
177
|
+
id: Uuid::uuid4()->toString(),
|
|
178
|
+
email: $email,
|
|
179
|
+
name: $name,
|
|
180
|
+
isActive: false,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Result pattern for expected failures
|
|
184
|
+
final readonly class Result
|
|
185
|
+
{
|
|
186
|
+
private function __construct(
|
|
187
|
+
public bool $success,
|
|
188
|
+
public mixed $value = null,
|
|
189
|
+
public ?Error $error = null,
|
|
190
|
+
) {}
|
|
191
|
+
|
|
192
|
+
public static function success(mixed $value): self
|
|
193
|
+
{
|
|
194
|
+
return new self(success: true, value: $value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public static function failure(Error $error): self
|
|
198
|
+
{
|
|
199
|
+
return new self(success: false, error: $error);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
public function isSuccess(): bool
|
|
203
|
+
{
|
|
204
|
+
return $this->success;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public function getOrThrow(): mixed
|
|
208
|
+
{
|
|
209
|
+
if (!$this->success) {
|
|
210
|
+
throw new RuntimeException($this->error->message);
|
|
211
|
+
}
|
|
212
|
+
return $this->value;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Usage
|
|
217
|
+
public function createUser(CreateUserRequest $request): Result
|
|
218
|
+
{
|
|
219
|
+
if ($this->repository->existsByEmail($request->email)) {
|
|
220
|
+
return Result::failure(new Error('email_taken', 'Email already exists'));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
$user = new User(
|
|
224
|
+
id: Uuid::uuid4()->toString(),
|
|
225
|
+
email: $request->email,
|
|
226
|
+
name: $request->name,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
$this->repository->save($user);
|
|
230
|
+
|
|
231
|
+
return Result::success($user);
|
|
232
|
+
}
|
|
233
|
+
\`\`\`
|
|
234
|
+
|
|
235
|
+
## Collections
|
|
236
|
+
\`\`\`php
|
|
237
|
+
<?php
|
|
238
|
+
|
|
239
|
+
// Array functions with arrow functions
|
|
240
|
+
$emails = array_map(fn(User $u) => $u->email, $users);
|
|
241
|
+
|
|
242
|
+
$active = array_filter($users, fn(User $u) => $u->isActive);
|
|
243
|
+
|
|
244
|
+
$total = array_reduce(
|
|
245
|
+
$items,
|
|
246
|
+
fn(float $sum, Item $item) => $sum + $item->price,
|
|
247
|
+
0.0
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Using Collection class (Laravel or custom)
|
|
251
|
+
$users = collect($users)
|
|
252
|
+
->filter(fn(User $u) => $u->isActive)
|
|
253
|
+
->map(fn(User $u) => new UserDto($u))
|
|
254
|
+
->sortByDesc(fn(UserDto $u) => $u->createdAt)
|
|
255
|
+
->values()
|
|
256
|
+
->all();
|
|
257
|
+
|
|
258
|
+
// Generator for large datasets
|
|
259
|
+
function processUsers(iterable $users): Generator
|
|
260
|
+
{
|
|
261
|
+
foreach ($users as $user) {
|
|
262
|
+
yield processUser($user);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
\`\`\`
|
|
266
|
+
|
|
267
|
+
## Interfaces and Traits
|
|
268
|
+
\`\`\`php
|
|
269
|
+
<?php
|
|
270
|
+
|
|
271
|
+
// Interface
|
|
272
|
+
interface UserRepositoryInterface
|
|
273
|
+
{
|
|
274
|
+
public function find(string $id): ?User;
|
|
275
|
+
public function findByEmail(string $email): ?User;
|
|
276
|
+
public function save(User $user): void;
|
|
277
|
+
public function delete(string $id): void;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Trait for common functionality
|
|
281
|
+
trait Timestamps
|
|
282
|
+
{
|
|
283
|
+
public ?DateTimeImmutable $createdAt = null;
|
|
284
|
+
public ?DateTimeImmutable $updatedAt = null;
|
|
285
|
+
|
|
286
|
+
public function touch(): void
|
|
287
|
+
{
|
|
288
|
+
$this->updatedAt = new DateTimeImmutable();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public function setCreatedAt(): void
|
|
292
|
+
{
|
|
293
|
+
$this->createdAt ??= new DateTimeImmutable();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Usage
|
|
298
|
+
class Post
|
|
299
|
+
{
|
|
300
|
+
use Timestamps;
|
|
301
|
+
|
|
302
|
+
public function __construct(
|
|
303
|
+
public string $id,
|
|
304
|
+
public string $title,
|
|
305
|
+
public string $content,
|
|
306
|
+
) {
|
|
307
|
+
$this->setCreatedAt();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
\`\`\`
|
|
311
|
+
|
|
312
|
+
## Testing with PHPUnit
|
|
313
|
+
\`\`\`php
|
|
314
|
+
<?php
|
|
315
|
+
|
|
316
|
+
use PHPUnit\\Framework\\TestCase;
|
|
317
|
+
use PHPUnit\\Framework\\Attributes\\Test;
|
|
318
|
+
use PHPUnit\\Framework\\Attributes\\DataProvider;
|
|
319
|
+
|
|
320
|
+
final class UserServiceTest extends TestCase
|
|
321
|
+
{
|
|
322
|
+
private UserRepositoryInterface $repository;
|
|
323
|
+
private UserService $service;
|
|
324
|
+
|
|
325
|
+
protected function setUp(): void
|
|
326
|
+
{
|
|
327
|
+
$this->repository = $this->createMock(UserRepositoryInterface::class);
|
|
328
|
+
$this->service = new UserService($this->repository);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
#[Test]
|
|
332
|
+
public function findsUserById(): void
|
|
333
|
+
{
|
|
334
|
+
$user = new User(id: '1', email: 'test@example.com', name: 'Test');
|
|
335
|
+
|
|
336
|
+
$this->repository
|
|
337
|
+
->expects($this->once())
|
|
338
|
+
->method('find')
|
|
339
|
+
->with('1')
|
|
340
|
+
->willReturn($user);
|
|
341
|
+
|
|
342
|
+
$result = $this->service->findById('1');
|
|
343
|
+
|
|
344
|
+
$this->assertSame($user, $result);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#[Test]
|
|
348
|
+
public function returnsNullWhenUserNotFound(): void
|
|
349
|
+
{
|
|
350
|
+
$this->repository
|
|
351
|
+
->method('find')
|
|
352
|
+
->willReturn(null);
|
|
353
|
+
|
|
354
|
+
$result = $this->service->findById('999');
|
|
355
|
+
|
|
356
|
+
$this->assertNull($result);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#[Test]
|
|
360
|
+
#[DataProvider('invalidEmailProvider')]
|
|
361
|
+
public function rejectsInvalidEmails(string $email): void
|
|
362
|
+
{
|
|
363
|
+
$this->expectException(ValidationException::class);
|
|
364
|
+
|
|
365
|
+
$this->service->create(new CreateUserRequest(
|
|
366
|
+
email: $email,
|
|
367
|
+
name: 'Test',
|
|
368
|
+
password: 'password123',
|
|
369
|
+
));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
public static function invalidEmailProvider(): array
|
|
373
|
+
{
|
|
374
|
+
return [
|
|
375
|
+
'empty' => [''],
|
|
376
|
+
'no at sign' => ['invalid'],
|
|
377
|
+
'no domain' => ['test@'],
|
|
378
|
+
];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
\`\`\`
|
|
382
|
+
|
|
383
|
+
## ✅ DO
|
|
384
|
+
- Use \`declare(strict_types=1)\` in all files
|
|
385
|
+
- Use \`readonly\` classes for immutable data
|
|
386
|
+
- Use constructor property promotion
|
|
387
|
+
- Use enums for fixed sets of values
|
|
388
|
+
- Use match expressions instead of switch
|
|
389
|
+
- Use attributes for metadata
|
|
390
|
+
- Follow PSR-12 coding standards
|
|
391
|
+
|
|
392
|
+
## ❌ DON'T
|
|
393
|
+
- Don't use \`@var\` annotations when types are declared
|
|
394
|
+
- Don't use arrays for typed data (use DTOs/value objects)
|
|
395
|
+
- Don't use \`mixed\` when specific types are possible
|
|
396
|
+
- Don't suppress errors with \`@\`
|
|
397
|
+
- Don't use globals or static state
|
|
398
|
+
- Don't catch \`Exception\` without rethrowing or logging
|