@mrciphersmith/keryx 0.3.1 → 0.3.3
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/dist/cli.js +7310 -2471
- package/dist/core.js +116 -10
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +349 -2
- package/src/gdskills/bundled/rules/core/model-selection.mdc +18 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-jev-comments/SKILL.md +184 -0
- package/src/gdskills/bundled/skills/review/review-jev-contract/SKILL.md +193 -0
- package/src/gdskills/bundled/skills/review/review-jev-docs/SKILL.md +189 -0
- package/src/gdskills/bundled/skills/review/review-jev-risk/SKILL.md +190 -0
- package/src/gdskills/bundled/skills/review/review-jev-scenarios/SKILL.md +187 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +88 -15
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +4 -4
- package/src/gdskills/bundled/stacks/c-cpp/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/scout.json +31 -0
- package/src/gdskills/bundled/stacks/c-cpp/pack.json +42 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/patterns.mdc +87 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/SKILL.md +152 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/eval.json +1295 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/scout.json +26 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/pack.json +41 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/patterns.mdc +77 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/security.mdc +144 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/eval.json +865 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/scout.json +16 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/pack.json +46 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/coding-style.mdc +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/patterns.mdc +81 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/security.mdc +146 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/testing.mdc +61 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/php-laravel/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/php-laravel/pack.json +41 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/coding-style.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/patterns.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/security.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/SKILL.md +126 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/SKILL.md +140 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ruby-rails/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/eval.json +1673 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/ruby-rails/pack.json +42 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/patterns.mdc +93 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/evals.json +71 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/SKILL.md +125 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/scout.json +30 -0
- package/src/gdskills/bundled/stacks/sql-db/pack.json +40 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/patterns.mdc +134 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/security.mdc +74 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/evals.json +73 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"query": "Use when implementing or extending a feature in a PHP 8.2+ / Laravel application -- controllers, Form Request validation, Eloquent models and relationships, migrations, route-model binding, middleware, queued jobs, and service container bindings.",
|
|
4
|
+
"decision": "fork",
|
|
5
|
+
"topMatch": "php-laravel/php-laravel-testing",
|
|
6
|
+
"recordedAt": "2026-09-25T15:27:29.539Z",
|
|
7
|
+
"skillName": "php-laravel-implementation",
|
|
8
|
+
"justification": "Nearest match php-laravel/php-laravel-testing (0.47) is this pack's own testing skill -- writes Pest/PHPUnit tests, never touches production controllers/models/migrations. ruby-rails-implementation (0.26) and nodejs-implementation (0.24) implement features for a different language/framework entirely (Ruby/Rails, Node/Express), sharing only generic implementation vocabulary, not Laravel's Eloquent/Form-Request/service-container API surface. No existing skill covers implementing a PHP/Laravel feature, so this is a genuine create."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"query": "Use when a Laravel application's test suite needs writing, extending, or fixing -- Pest or PHPUnit feature/unit tests, model factories, RefreshDatabase, HTTP testing helpers, and Queue/Mail/Event/Http fakes.",
|
|
12
|
+
"decision": "fork",
|
|
13
|
+
"topMatch": "ruby-rails/ruby-rails-testing",
|
|
14
|
+
"recordedAt": "2026-09-25T15:27:40.315Z",
|
|
15
|
+
"skillName": "php-laravel-testing",
|
|
16
|
+
"justification": "Nearest match ruby-rails-testing (0.35) covers RSpec/Minitest for a different language/framework (Ruby/Rails), not Pest/PHPUnit or Laravel's Queue/Mail/Event/Http fakes. go-testing (0.24) and react-testing (0.24) are for unrelated stacks (Go's testing package, Jest/RTL). No existing skill covers Laravel's own test tooling (Pest, factories, RefreshDatabase, framework fakes), so this is a genuine create."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"query": "Use when composer install/update fails, PHP fatal errors block a Laravel app from booting, phpstan/larastan reports a failing analysis, or a previously-passing test suite is now failing -- resolves dependency conflicts, autoload issues, config/service-provider errors, and static-analysis findings with the smallest root-cause fix.",
|
|
20
|
+
"decision": "fork",
|
|
21
|
+
"topMatch": "ruby-rails/ruby-rails-build-fix",
|
|
22
|
+
"recordedAt": "2026-09-25T15:28:05.422Z",
|
|
23
|
+
"skillName": "php-laravel-build-fix",
|
|
24
|
+
"justification": "Nearest match ruby-rails-build-fix (0.39) resolves Bundler/Gemfile conflicts and Rails boot errors -- not composer.json/composer.lock conflicts, PSR-4 autoload mismatches, or phpstan/Larastan findings, none of which apply to a Ruby/Bundler toolchain. python-build-fix (0.25) and nodejs-build-fix (0.21) resolve pip/npm/tsc failures for unrelated languages. No existing skill knows Composer's dependency resolution, PSR-4 autoloading, or PHP static-analysis tooling, so this is a genuine create."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"query": "Use when auditing a PHP/Laravel diff before merge for Eloquent and Blade pitfalls -- $fillable/$guarded mass-assignment gaps, missing eager-loaded relationships causing N+1 queries, string-interpolated DB::raw() SQL, unescaped {!! !!} Blade output, a route dropped from VerifyCsrfToken coverage, and a queued job with no idempotency guard. Read-only, no edits.",
|
|
28
|
+
"decision": "create",
|
|
29
|
+
"topMatch": "ruby-rails/ruby-rails-code-review",
|
|
30
|
+
"recordedAt": "2026-09-25T17:49:43.838Z",
|
|
31
|
+
"skillName": "php-laravel-code-review"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "php-laravel",
|
|
3
|
+
"family": "framework",
|
|
4
|
+
"modules": ["php-laravel-rules", "php-laravel-skills"],
|
|
5
|
+
"detectionMarkers": ["php", "laravel"],
|
|
6
|
+
"provenance": {
|
|
7
|
+
"origin": "authored",
|
|
8
|
+
"sourceRef": "flow 337, Wave 4 batch 5"
|
|
9
|
+
},
|
|
10
|
+
"stability": "experimental",
|
|
11
|
+
"skills": {
|
|
12
|
+
"implement": ["php-laravel-implementation"],
|
|
13
|
+
"test": ["php-laravel-testing"],
|
|
14
|
+
"review": ["php-laravel-code-review"],
|
|
15
|
+
"build-fix": ["php-laravel-build-fix"],
|
|
16
|
+
"migrate": []
|
|
17
|
+
},
|
|
18
|
+
"agentProfile": {
|
|
19
|
+
"displayName": "PHP / Laravel",
|
|
20
|
+
"auditFocus": [
|
|
21
|
+
"an Eloquent create/update fed straight from $request->all() instead of validated() or an explicit $fillable/$guarded allowlist -- mass-assignment risk",
|
|
22
|
+
"raw SQL string interpolation (DB::select/DB::statement with concatenated values) instead of parameter bindings or the query builder/Eloquent",
|
|
23
|
+
"a relationship accessed inside a loop with no eager load (with()/load()) on the collection it iterates -- N+1 queries",
|
|
24
|
+
"Blade {!! !!} unescaped output rendering a value that traces back to user input",
|
|
25
|
+
"a state-changing route (POST/PUT/PATCH/DELETE) missing CSRF protection or excluded from the VerifyCsrfToken middleware without a stated reason",
|
|
26
|
+
"a queue job with no idempotency handling (unique(), a dedupe key, or a check before side-effecting work) on a connection that can redeliver or retry it -- true of most production queue configurations"
|
|
27
|
+
],
|
|
28
|
+
"buildCommands": [
|
|
29
|
+
"composer install",
|
|
30
|
+
"php artisan config:clear (when config/env-dependent errors appear)",
|
|
31
|
+
"./vendor/bin/phpstan analyse (when the project has a phpstan/larastan config)",
|
|
32
|
+
"php artisan test (or ./vendor/bin/pest / ./vendor/bin/phpunit)"
|
|
33
|
+
],
|
|
34
|
+
"fixGuardrails": [
|
|
35
|
+
"Never silence a static-analysis finding with a blanket @phpstan-ignore or @psalm-suppress instead of fixing the root cause.",
|
|
36
|
+
"Never widen $fillable to a blanket guard-nothing allowlist, or swap $guarded to [] to make a mass-assignment error disappear.",
|
|
37
|
+
"Never disable CSRF verification for a route or add it to VerifyCsrfToken's except list just to make a failing request succeed.",
|
|
38
|
+
"Never delete or skip a failing test to reach a green build."
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.php"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# PHP / Laravel coding style
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic style rules to modern PHP
|
|
11
|
+
(8.2+) and Laravel (11/12/13) idiom. Applies only to `*.php` files —
|
|
12
|
+
everything not PHP/Laravel-specific still comes from the common rules
|
|
13
|
+
this file `extends`.
|
|
14
|
+
|
|
15
|
+
## Naming and structure
|
|
16
|
+
|
|
17
|
+
- Classes, interfaces, enums, and traits are `StudlyCase`; methods and
|
|
18
|
+
properties are `camelCase`; constants and enum cases are
|
|
19
|
+
`SCREAMING_SNAKE_CASE` unless the enum case is meant to read as a label.
|
|
20
|
+
- Follow Laravel's default directory conventions
|
|
21
|
+
(`app/Models`, `app/Http/Controllers`, `app/Http/Requests`,
|
|
22
|
+
`app/Providers`, `app/Jobs`, `database/migrations`) rather than
|
|
23
|
+
inventing a parallel structure for the same kind of class.
|
|
24
|
+
- A controller method stays thin: validate (via a Form Request), delegate
|
|
25
|
+
to a model/action/service, return a response — push business rules into
|
|
26
|
+
a model method, an action class, or a service, not the controller.
|
|
27
|
+
- Type-hint every parameter, property, and return value that can be
|
|
28
|
+
typed; PHP 8.2+ intersection types and `never`/`static` return types are
|
|
29
|
+
fair game where they genuinely describe the contract.
|
|
30
|
+
|
|
31
|
+
## PHP 8.2+ language features
|
|
32
|
+
|
|
33
|
+
- `readonly` properties for value objects and anything constructed once
|
|
34
|
+
and never mutated (a DTO, an immutable config value) instead of a
|
|
35
|
+
mutable property guarded only by convention.
|
|
36
|
+
- Native `enum` (backed by `string`/`int` when the value needs to persist
|
|
37
|
+
to a column or API payload) instead of class constants for a fixed set
|
|
38
|
+
of related values.
|
|
39
|
+
- First-class callable syntax (`$this->method(...)`, `Str::slug(...)`)
|
|
40
|
+
instead of a string/array callable (`[$this, 'method']`) or an
|
|
41
|
+
unnecessary closure wrapper when passing a method as a callback.
|
|
42
|
+
- Constructor property promotion (`public function __construct(private
|
|
43
|
+
readonly OrderRepository $orders) {}`) for simple DTOs and services
|
|
44
|
+
instead of repeating each property as a separate declaration plus
|
|
45
|
+
assignment.
|
|
46
|
+
|
|
47
|
+
## Eloquent models
|
|
48
|
+
|
|
49
|
+
- Declare `protected $fillable` (an explicit allowlist) or `protected
|
|
50
|
+
$guarded` on every model that is ever mass-assigned; do not leave a
|
|
51
|
+
model without either and rely on `Model::unguard()` outside of a
|
|
52
|
+
seeder/test context.
|
|
53
|
+
- Cast attributes with the model's `casts()` method (or the `$casts`
|
|
54
|
+
property on codebases not yet migrated to it) rather than hand-parsing
|
|
55
|
+
dates/JSON/enums in accessors.
|
|
56
|
+
- Put a relationship's return type on the method
|
|
57
|
+
(`: HasMany`, `: BelongsTo`, ...) so IDEs and static analysis can follow
|
|
58
|
+
it.
|
|
59
|
+
|
|
60
|
+
## Controllers, routes, and requests
|
|
61
|
+
|
|
62
|
+
- Route parameters use route-model binding
|
|
63
|
+
(`Route::get('/users/{user}', ...)` resolving to a typed `User $user`
|
|
64
|
+
parameter) instead of manually calling `User::findOrFail($id)` in every
|
|
65
|
+
controller method that already receives the bound model.
|
|
66
|
+
- Validate every request that accepts user input through a Form Request
|
|
67
|
+
class (`php artisan make:request`) or `$request->validate([...])`, not
|
|
68
|
+
by reading raw `$request->all()`/`$request->input()` values straight
|
|
69
|
+
into model or database calls.
|
|
70
|
+
- Register route-specific middleware with `->middleware(...)` on the
|
|
71
|
+
route/group; keep global middleware in `bootstrap/app.php`
|
|
72
|
+
(or `app/Http/Kernel.php` on codebases still on that structure) rather
|
|
73
|
+
than duplicating a global concern per-route.
|
|
74
|
+
|
|
75
|
+
## Formatting
|
|
76
|
+
|
|
77
|
+
- Follow PSR-12 spacing/brace conventions; run the project's configured
|
|
78
|
+
formatter (`./vendor/bin/pint` is Laravel's default) instead of
|
|
79
|
+
hand-formatting around one that is already configured.
|
|
80
|
+
- One class per file, filename matching the class name, matching the
|
|
81
|
+
namespace to the directory per PSR-4 — do not add a second top-level
|
|
82
|
+
class/interface/enum to an existing file.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.php"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# PHP / Laravel patterns
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic design guidance to idiomatic
|
|
11
|
+
Laravel application design and its common anti-patterns. Applies only to
|
|
12
|
+
`*.php` files.
|
|
13
|
+
|
|
14
|
+
## Layered responsibility
|
|
15
|
+
|
|
16
|
+
- Controllers orchestrate; they do not contain business rules. Push
|
|
17
|
+
non-trivial logic into an Eloquent model method, an invokable action
|
|
18
|
+
class (`app/Actions/...`), or a service class injected through the
|
|
19
|
+
constructor.
|
|
20
|
+
- A Form Request class owns validation rules and authorization for one
|
|
21
|
+
request shape (`rules()`, `authorize()`); do not duplicate the same
|
|
22
|
+
validation array across multiple controller methods when a shared Form
|
|
23
|
+
Request would do.
|
|
24
|
+
- Eloquent model classes own their own data shape (casts, relationships,
|
|
25
|
+
scopes, accessors); keep cross-model orchestration (a multi-model
|
|
26
|
+
transaction, a workflow spanning several models) in a service or action
|
|
27
|
+
class, not stuffed into one model's methods.
|
|
28
|
+
|
|
29
|
+
## Eloquent relationships and queries
|
|
30
|
+
|
|
31
|
+
- Eager-load a relationship (`with()`, or `load()` after the fact) before
|
|
32
|
+
iterating a collection and accessing that relationship inside the loop;
|
|
33
|
+
a relationship accessed per-row with no eager load is an N+1 query, not
|
|
34
|
+
"just how Eloquent works."
|
|
35
|
+
- For a result set too large to hold in memory, use `chunk()`/`chunkById()`
|
|
36
|
+
or `cursor()`/`lazy()` instead of `all()`/an unbounded `get()`;
|
|
37
|
+
`chunkById()` (not plain `chunk()`) when the loop body updates or
|
|
38
|
+
deletes rows, since plain `chunk()`'s offset pagination skips rows as
|
|
39
|
+
the underlying set shifts.
|
|
40
|
+
- Use query scopes (`scopeActive`, `scopePublished`) for a filter reused
|
|
41
|
+
across more than one call site instead of repeating the same `where()`
|
|
42
|
+
chain.
|
|
43
|
+
|
|
44
|
+
## Dependency injection and the service container
|
|
45
|
+
|
|
46
|
+
- Type-hint a constructor/method dependency and let the container resolve
|
|
47
|
+
it instead of calling `app(SomeClass::class)`/`resolve(...)` inline
|
|
48
|
+
inside business logic — reserve container-pulling for genuinely
|
|
49
|
+
dynamic resolution (a class name only known at runtime).
|
|
50
|
+
- Bind an interface to its implementation in a service provider's
|
|
51
|
+
`register()` when a class needs to be swappable (a payment gateway, a
|
|
52
|
+
notification channel); do not introduce an interface with exactly one
|
|
53
|
+
implementation and no swapping need "for testability" alone when
|
|
54
|
+
Laravel's own container-based mocking (`$this->mock(...)` in tests)
|
|
55
|
+
already covers that.
|
|
56
|
+
|
|
57
|
+
## Jobs, events, and idempotency
|
|
58
|
+
|
|
59
|
+
- A queued job that can be delivered more than once (most production
|
|
60
|
+
queue connections are at-least-once) either implements `ShouldBeUnique`/`uniqueId()`, or
|
|
61
|
+
checks/records its own side effect before repeating it — do not assume
|
|
62
|
+
a job runs exactly once.
|
|
63
|
+
- Prefer a domain event (`event(new OrderShipped($order))`) with
|
|
64
|
+
dedicated listeners over stacking unrelated side effects directly in
|
|
65
|
+
the code path that triggers them, when more than one unrelated concern
|
|
66
|
+
needs to react to the same occurrence.
|
|
67
|
+
|
|
68
|
+
## Anti-patterns to flag
|
|
69
|
+
|
|
70
|
+
- A "fat controller" that validates, queries, transforms, and formats the
|
|
71
|
+
response all inline — split by responsibility as above.
|
|
72
|
+
- A model with `protected $guarded = []` used outside of a factory/seeder
|
|
73
|
+
context — this disables mass-assignment protection entirely and should
|
|
74
|
+
be treated as a defect, not a convenience default.
|
|
75
|
+
- Business logic duplicated between a web controller and an API
|
|
76
|
+
controller for the same underlying action — extract the shared action/
|
|
77
|
+
service both call.
|
|
78
|
+
- A trait used to share mutable state between unrelated models instead of
|
|
79
|
+
genuinely shared, stateless behavior — prefer composition (an injected
|
|
80
|
+
collaborator) when the "shared behavior" is really shared state.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.php"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# PHP / Laravel security
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic security rules to PHP/Laravel-
|
|
11
|
+
specific, OWASP-relevant risks and the framework API to use instead.
|
|
12
|
+
Applies only to `*.php` files.
|
|
13
|
+
|
|
14
|
+
## Mass assignment
|
|
15
|
+
|
|
16
|
+
- Every Eloquent model that is ever constructed from request data
|
|
17
|
+
declares `$fillable` (an explicit allowlist) or `$guarded`; an empty
|
|
18
|
+
`$guarded = []` mass-assigns every column, attacker-controlled fields
|
|
19
|
+
(`is_admin`, `role`, `user_id` on someone else's record) included.
|
|
20
|
+
- Build a create/update from `$request->validated()` (the Form Request's
|
|
21
|
+
validated subset), not raw `$request->all()`/`$request->input()` — even
|
|
22
|
+
with `$fillable` set, validating first catches type/shape problems
|
|
23
|
+
`$fillable` alone does not.
|
|
24
|
+
|
|
25
|
+
## SQL and data access
|
|
26
|
+
|
|
27
|
+
- Use Eloquent/the query builder's parameter binding
|
|
28
|
+
(`where('email', $email)`, `DB::select('... where id = ?', [$id])`) for
|
|
29
|
+
every value that varies with input; never interpolate a variable
|
|
30
|
+
directly into a raw SQL string (`DB::select("... where id = $id")`,
|
|
31
|
+
`DB::statement("... = '{$value}'")`) — that is SQL injection regardless
|
|
32
|
+
of how "internal" the input looks.
|
|
33
|
+
- `DB::raw()`/`whereRaw()`/`selectRaw()` still take bindings as a second
|
|
34
|
+
argument for any variable part of the expression — pass the value
|
|
35
|
+
there, not by interpolating it into the raw string.
|
|
36
|
+
|
|
37
|
+
## Output encoding (Blade/XSS)
|
|
38
|
+
|
|
39
|
+
- Blade's `{{ $value }}` escapes through `htmlspecialchars` by default;
|
|
40
|
+
use it for anything that can contain user-influenced content.
|
|
41
|
+
- `{!! $value !!}` performs no escaping — reserve it for content the
|
|
42
|
+
application itself fully controls and trusts (a sanitized rich-text
|
|
43
|
+
field explicitly passed through a purifier, not raw user input) and
|
|
44
|
+
say so at the point of use when it is used at all.
|
|
45
|
+
|
|
46
|
+
## CSRF and state-changing requests
|
|
47
|
+
|
|
48
|
+
- Every state-changing route (POST/PUT/PATCH/DELETE) reached from a
|
|
49
|
+
browser session relies on Laravel's `VerifyCsrfToken`/
|
|
50
|
+
`ValidateCsrfToken` middleware (the default for the `web` middleware
|
|
51
|
+
group) and includes `@csrf` in the form or the `X-CSRF-TOKEN`/
|
|
52
|
+
`X-XSRF-TOKEN` header for an AJAX call.
|
|
53
|
+
- Adding a route to the CSRF-exempt list is a deliberate, narrow decision
|
|
54
|
+
for a genuinely stateless endpoint (a signed webhook, an API token
|
|
55
|
+
route already outside the `web` group) — never a workaround for a
|
|
56
|
+
request that is failing CSRF verification for an unrelated reason.
|
|
57
|
+
|
|
58
|
+
## Secrets and configuration
|
|
59
|
+
|
|
60
|
+
- Read secrets from `.env`/`config/*.php` via the `config()` helper; never
|
|
61
|
+
commit a real credential, API key, or signing secret into a versioned
|
|
62
|
+
file (`.env.example` gets placeholder values only).
|
|
63
|
+
- Laravel's `APP_KEY` (used for encryption and signed URLs) is generated
|
|
64
|
+
per environment (`php artisan key:generate`) and never shared between
|
|
65
|
+
environments or committed to version control.
|
|
66
|
+
|
|
67
|
+
## Queues and background work
|
|
68
|
+
|
|
69
|
+
- A queue job handling anything security- or money-relevant is written
|
|
70
|
+
assuming its connection can redeliver it (true of most production
|
|
71
|
+
configurations): guard the side effect with
|
|
72
|
+
`ShouldBeUnique`, a `uniqueId()`, or an idempotency check recorded
|
|
73
|
+
before the effect runs, not an assumption that retries "shouldn't
|
|
74
|
+
normally happen."
|
|
75
|
+
|
|
76
|
+
## Dependency hygiene
|
|
77
|
+
|
|
78
|
+
- Run `composer audit` when introducing or updating dependencies, or when
|
|
79
|
+
asked to check for known vulnerabilities in the project's Composer
|
|
80
|
+
packages.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.php"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# PHP / Laravel testing
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic testing rules to Laravel's
|
|
11
|
+
testing stack — Pest (the current default for new Laravel apps) or
|
|
12
|
+
PHPUnit, plus Laravel's HTTP/database testing helpers. Applies only to
|
|
13
|
+
`*.php` files.
|
|
14
|
+
|
|
15
|
+
## Layout and naming
|
|
16
|
+
|
|
17
|
+
- Tests live under `tests/Feature` (anything touching the framework —
|
|
18
|
+
HTTP, database, queue, auth) or `tests/Unit` (a class tested in
|
|
19
|
+
isolation, no framework boot needed); match whichever the project
|
|
20
|
+
already uses for a given kind of test rather than defaulting one way.
|
|
21
|
+
- Pest: a test file is a plain script of `test('...', function () {...})`/
|
|
22
|
+
`it('...', function () {...})` calls, optionally grouped with
|
|
23
|
+
`describe(...)`. PHPUnit: a test class extends `Tests\TestCase`, with
|
|
24
|
+
each test a `public function test_...(): void` method or a method
|
|
25
|
+
annotated `#[Test]`.
|
|
26
|
+
- Name a test for the behavior it verifies ("rejects an order with no
|
|
27
|
+
line items"), not the method under test alone ("test_store").
|
|
28
|
+
|
|
29
|
+
## Database and factories
|
|
30
|
+
|
|
31
|
+
- Use model factories (`app/Models/*` + `database/factories/*Factory.php`,
|
|
32
|
+
`User::factory()->create([...])`) to build test data instead of
|
|
33
|
+
hand-inserting rows; override only the fields the test cares about.
|
|
34
|
+
- Apply `RefreshDatabase` (or `DatabaseTransactions`, on a project already
|
|
35
|
+
using it) on any test that touches the database, so each test starts
|
|
36
|
+
from a known, isolated state — do not rely on execution order between
|
|
37
|
+
tests for shared fixture data.
|
|
38
|
+
|
|
39
|
+
## HTTP and feature tests
|
|
40
|
+
|
|
41
|
+
- Exercise routes through Laravel's HTTP testing helpers
|
|
42
|
+
(`$this->get(...)`, `$this->postJson(...)`, `actingAs($user)`) and
|
|
43
|
+
assert on the response (`assertStatus`, `assertJson`, `assertSee`,
|
|
44
|
+
`assertRedirect`) rather than calling controller methods directly —
|
|
45
|
+
a feature test should exercise the same path a real request takes
|
|
46
|
+
(routing, middleware, validation included).
|
|
47
|
+
- Assert authorization/validation failures explicitly
|
|
48
|
+
(`assertForbidden()`, `assertInvalid(['field'])`) instead of only
|
|
49
|
+
checking the happy path.
|
|
50
|
+
|
|
51
|
+
## Fakes over real side effects
|
|
52
|
+
|
|
53
|
+
- Use Laravel's fakes (`Queue::fake()`, `Mail::fake()`, `Event::fake()`,
|
|
54
|
+
`Storage::fake('disk')`, `Http::fake([...])`) to assert a job was
|
|
55
|
+
dispatched, a mail was queued, or an HTTP call was made — never let a
|
|
56
|
+
test actually send an email, hit a real queue, or make a real outbound
|
|
57
|
+
HTTP request.
|
|
58
|
+
- After `Queue::fake()`, assert with `Queue::assertPushed(JobClass::class,
|
|
59
|
+
fn ($job) => ...)` to check the job's own state, not just that some job
|
|
60
|
+
of that class was pushed.
|
|
61
|
+
|
|
62
|
+
## Assertions and determinism
|
|
63
|
+
|
|
64
|
+
- Never synchronize with `sleep()`/a fixed delay to wait for a queued job
|
|
65
|
+
or async side effect in a test — use `Queue::fake()` and assert what
|
|
66
|
+
was dispatched, or run the job synchronously in the test
|
|
67
|
+
(`ShouldQueue` jobs can be dispatched and handled inline via
|
|
68
|
+
`Bus::fake()`/direct `handle()` invocation) instead of waiting on a
|
|
69
|
+
real worker.
|
|
70
|
+
- Prefer Pest's `expect($value)->toBe(...)` (or PHPUnit's
|
|
71
|
+
`assertSame`) over a loose equality check when the type matters (`===`
|
|
72
|
+
semantics), especially for IDs, enum cases, and money values.
|
|
73
|
+
|
|
74
|
+
## Static analysis and running
|
|
75
|
+
|
|
76
|
+
- Run the project's configured static analysis (`./vendor/bin/phpstan
|
|
77
|
+
analyse`, typically via Larastan on a Laravel app) alongside the test
|
|
78
|
+
suite when configured — a green test suite with new static-analysis
|
|
79
|
+
errors is not a finished change.
|
|
80
|
+
- Run `php artisan test` (Pest or PHPUnit, whichever the project uses) or
|
|
81
|
+
`./vendor/bin/pest`/`./vendor/bin/phpunit` directly; do not skip a slow
|
|
82
|
+
test file to get a faster local loop without saying so in the report.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: php-laravel-build-fix
|
|
3
|
+
description: "Use when composer install/update fails, PHP fatal errors block a Laravel app from booting, phpstan/larastan reports a failing analysis, or a previously-passing test suite is now failing -- resolves dependency conflicts, autoload issues, config/service-provider errors, and static-analysis findings with the smallest root-cause fix. Not for an npm/frontend build failure or a Python/pytest failure (use that stack's own build-fix skill)."
|
|
4
|
+
triggers:
|
|
5
|
+
- "composer install is failing"
|
|
6
|
+
- "this Laravel app throws a fatal error on boot"
|
|
7
|
+
- "phpstan is failing on this file"
|
|
8
|
+
- "fix this class not found autoload error"
|
|
9
|
+
- "the service provider isn't registering correctly"
|
|
10
|
+
- "php artisan test is failing after this change"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: build-fix
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# PHP / Laravel build fix
|
|
20
|
+
|
|
21
|
+
Resolve a `composer install`/`update` failure, a PHP fatal error blocking
|
|
22
|
+
a Laravel app from booting, a `phpstan`/Larastan analysis failure, an
|
|
23
|
+
autoload/class-not-found error, a misconfigured service provider, or a
|
|
24
|
+
newly-failing test suite — with the smallest change that fixes the actual
|
|
25
|
+
root cause. `rules/coding-style.mdc` and `rules/security.mdc` govern what
|
|
26
|
+
a "correct" fix looks like; this skill never reaches for a suppression
|
|
27
|
+
instead of a fix.
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### Step 1: Reproduce and classify
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
composer install
|
|
35
|
+
php artisan config:clear && php artisan cache:clear
|
|
36
|
+
./vendor/bin/phpstan analyse # if configured (Larastan)
|
|
37
|
+
php artisan test
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Read the exact error text and classify it:
|
|
41
|
+
|
|
42
|
+
- **Dependency conflict** (`composer install`/`update` reports a version
|
|
43
|
+
constraint that cannot be satisfied, or a platform requirement
|
|
44
|
+
mismatch against the installed PHP version).
|
|
45
|
+
- **Autoload/class-not-found** (`Class "App\..." not found`, usually a
|
|
46
|
+
PSR-4 namespace/path mismatch, or `composer dump-autoload` needed after
|
|
47
|
+
a new class).
|
|
48
|
+
- **Boot-time fatal** (a service provider throwing during `register()`/
|
|
49
|
+
`boot()`, a missing `.env` value a config file requires, a stale cached
|
|
50
|
+
config/route/view referencing something that moved).
|
|
51
|
+
- **Static-analysis finding** (`phpstan`/Larastan reporting a real type
|
|
52
|
+
mismatch, an undefined property, or a missing return type).
|
|
53
|
+
- **Test failure** introduced by the change under investigation (not a
|
|
54
|
+
pre-existing flaky test — confirm by re-running in isolation).
|
|
55
|
+
|
|
56
|
+
### Step 2: Fix by category
|
|
57
|
+
|
|
58
|
+
**Dependency conflict:** run `composer why <package>`/`composer why-not
|
|
59
|
+
<package> <version>` before bumping anything by hand; resolve the actual
|
|
60
|
+
constraint mismatch (a package requiring a PHP version, or two packages
|
|
61
|
+
requiring incompatible versions of a shared dependency) rather than
|
|
62
|
+
force-installing with `--ignore-platform-reqs` or blindly widening a
|
|
63
|
+
`composer.json` constraint.
|
|
64
|
+
|
|
65
|
+
**Autoload/class-not-found:** check the class's namespace matches its
|
|
66
|
+
file path under the PSR-4 mapping in `composer.json`; run `composer
|
|
67
|
+
dump-autoload` after adding a new class if the error persists with a
|
|
68
|
+
correct namespace/path. Do not "fix" this by adding a manual `require`
|
|
69
|
+
for an autoloaded class.
|
|
70
|
+
|
|
71
|
+
**Boot-time fatal:** read the actual exception and trace it to the
|
|
72
|
+
service provider/config file it originates from; run `php artisan
|
|
73
|
+
config:clear`/`route:clear`/`view:clear` first when the error looks stale
|
|
74
|
+
(references something already renamed/removed) before changing code. Fix
|
|
75
|
+
a missing required `.env` value by documenting it in `.env.example`
|
|
76
|
+
rather than hard-coding a fallback secret in code.
|
|
77
|
+
|
|
78
|
+
**Static-analysis finding:** fix the type mismatch, missing return type,
|
|
79
|
+
or undefined-property access the finding names. Never add
|
|
80
|
+
`@phpstan-ignore-next-line`/`@psalm-suppress`/a blanket
|
|
81
|
+
`ignoreErrors` entry in `phpstan.neon` to silence a finding without
|
|
82
|
+
addressing what it found.
|
|
83
|
+
|
|
84
|
+
**Test failure:** read the actual assertion failure; fix the source
|
|
85
|
+
change that broke the contract the test verifies, unless the test itself
|
|
86
|
+
is asserting the wrong thing for a deliberate behavior change (say so in
|
|
87
|
+
the report rather than silently loosening the assertion).
|
|
88
|
+
|
|
89
|
+
### Step 3: Verify
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
composer install
|
|
93
|
+
./vendor/bin/phpstan analyse # if configured
|
|
94
|
+
php artisan test
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
All must exit 0 before reporting done.
|
|
98
|
+
|
|
99
|
+
### Step 4: Report
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
Fixed: composer.json version constraint mismatch (guzzlehttp/guzzle)
|
|
103
|
+
- Root cause: a new dependency required guzzle ^7.8, composer.json pinned ^6.0
|
|
104
|
+
- composer install / phpstan analyse / php artisan test all pass
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
State the root cause in one sentence, not just "fixed the error."
|
|
108
|
+
|
|
109
|
+
## Rules
|
|
110
|
+
|
|
111
|
+
- Find and fix the smallest change that addresses the actual root cause —
|
|
112
|
+
never widen a fix beyond what the failure requires.
|
|
113
|
+
- NEVER add `@phpstan-ignore-next-line`, `@psalm-suppress`, or a blanket
|
|
114
|
+
`ignoreErrors` entry to silence a static-analysis finding instead of
|
|
115
|
+
fixing what it found.
|
|
116
|
+
- NEVER widen `$fillable` to a catch-all allowlist, or set `$guarded = []`
|
|
117
|
+
on a model, just to make a mass-assignment error disappear.
|
|
118
|
+
- NEVER install with `--ignore-platform-reqs` to route around a real
|
|
119
|
+
version conflict without confirming it is an intentional, documented
|
|
120
|
+
override.
|
|
121
|
+
- NEVER delete or skip a failing test to reach a green build.
|
|
122
|
+
|
|
123
|
+
## Red Flags
|
|
124
|
+
|
|
125
|
+
| Rationalization | Why it is wrong |
|
|
126
|
+
|---|---|
|
|
127
|
+
| "I'll add `@phpstan-ignore-next-line` here so the analysis passes" | Silences the finding without fixing the type mismatch/undefined access it caught; fix the actual code instead |
|
|
128
|
+
| "I'll just run composer with `--ignore-platform-reqs`" | Installs a dependency the running PHP version does not actually satisfy, deferring the real failure to runtime instead of fixing the constraint |
|
|
129
|
+
| "This model's mass-assignment error goes away if I set `$guarded = []`" | Disables mass-assignment protection entirely for every attribute, trading a build error for a security defect |
|
|
130
|
+
| "This test is flaky after my change, I'll skip it for now" | Hides a real regression instead of fixing the change that broke the contract the test verifies |
|
|
131
|
+
|
|
132
|
+
## Verification
|
|
133
|
+
|
|
134
|
+
Do not report the fix done until all of the following hold:
|
|
135
|
+
|
|
136
|
+
- `composer install`, the project's static analysis (if configured), and
|
|
137
|
+
`php artisan test` all exit 0.
|
|
138
|
+
- The change is the smallest one that addresses the stated root cause —
|
|
139
|
+
no unrelated files touched.
|
|
140
|
+
- No `@phpstan-ignore-next-line`/`@psalm-suppress`/`ignoreErrors` entry
|
|
141
|
+
was added to silence a finding.
|
|
142
|
+
- The report states the root cause in one sentence, not just "build now
|
|
143
|
+
passes."
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"composer install is failing after I added a new package",
|
|
5
|
+
"Staging 500s on every request since we swapped in the new Redis session driver",
|
|
6
|
+
"phpstan is reporting a failure on this file, can you fix it",
|
|
7
|
+
"Composer dump-autoload runs clean but PHP still can't find App\\Services\\Billing\\InvoiceBuilder at runtime",
|
|
8
|
+
"I split AppServiceProvider into three smaller providers and now the queue connection singleton isn't bound anymore",
|
|
9
|
+
"The whole test suite went red with 'could not find driver' right after I bumped the sqlite package version",
|
|
10
|
+
"Our build is broken because of a dependency version conflict",
|
|
11
|
+
"Larastan is flagging an undefined property on this model"
|
|
12
|
+
],
|
|
13
|
+
"negative": [
|
|
14
|
+
"Implement a new endpoint for cancelling an order",
|
|
15
|
+
"Write Pest tests for the new cancellation endpoint",
|
|
16
|
+
"Review this diff for mass assignment before merging",
|
|
17
|
+
"npm run build is failing after a package upgrade in the frontend app",
|
|
18
|
+
"This Rails app raises a NoMethodError on boot after a gem upgrade",
|
|
19
|
+
"Fix this failing pytest suite after a dependency update"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"scenarios": [
|
|
23
|
+
{
|
|
24
|
+
"id": "no-phpstan-suppress",
|
|
25
|
+
"prompt": "phpstan is reporting 'Cannot call method items() on App\\Models\\Order|null' on this line: $order->items()->create([...]). How should I fix it?",
|
|
26
|
+
"strictness": "high",
|
|
27
|
+
"expected_behavior": [
|
|
28
|
+
{
|
|
29
|
+
"grader": "judge",
|
|
30
|
+
"rubric": "A correct answer fixes the actual nullability the finding names -- confirming $order can't be null before calling items() on it (e.g. via findOrFail() instead of find(), an explicit null check that returns/throws, or narrowing the parameter/property type) -- rather than silencing the finding with @phpstan-ignore-next-line or a phpstan.neon ignoreErrors entry.",
|
|
31
|
+
"pass_criteria": [
|
|
32
|
+
"Names a concrete fix that removes the actual null possibility (e.g. findOrFail() instead of find(), an explicit null guard with an early return/abort, or a non-nullable type on the parameter), not just 'handle the null case'.",
|
|
33
|
+
"The fix is shown at the point where $order becomes possibly-null (how it was obtained), not just wrapped around the items() call."
|
|
34
|
+
],
|
|
35
|
+
"fail_criteria": [
|
|
36
|
+
"Recommends adding @phpstan-ignore-next-line, @phpstan-ignore, or a phpstan.neon ignoreErrors entry for this line instead of fixing the underlying nullability. Mentioning phpstan-ignore only to warn against it is not a failure."
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"calibration": {
|
|
41
|
+
"known_right": "The finding is real: whatever produced $order can return null (likely Order::find($id) rather than findOrFail($id)), and the code calls ->items()->create([...]) on it without checking. Fix it at the source -- if $order comes from a route/method that should always have a valid order, use Order::findOrFail($id) so a missing order throws a 404 instead of silently producing null; if null is a genuinely valid case here, add an explicit guard (if (! $order) { abort(404); } or return early) before the items() call. Either way phpstan's finding goes away because the type is actually narrowed at that point, not because the checker was told to stop looking.",
|
|
42
|
+
"known_wrong": "Just add a @phpstan-ignore-next-line comment above that line -- the code works fine at runtime in practice since $order is basically never actually null there, and this is the fastest way to get the analysis passing again without restructuring how the order is fetched.",
|
|
43
|
+
"vague": "Make sure the nullability is handled properly before calling items() on it.",
|
|
44
|
+
"subtle_wrong": "Add an inline null-coalescing guard right at the call site: ($order?->items())?->create([...]); -- that satisfies phpstan by narrowing the type at the point of the call, and if $order happens to be null the whole expression just silently evaluates to null instead of throwing, so nothing breaks."
|
|
45
|
+
},
|
|
46
|
+
"anti_patterns": ["@phpstan-ignore"]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "no-guarded-empty-workaround",
|
|
50
|
+
"prompt": "I'm getting a MassAssignmentException when creating a Product from validated request data -- Product::create($request->validated()) throws because 'category_id' isn't fillable. What's the right fix?",
|
|
51
|
+
"strictness": "high",
|
|
52
|
+
"expected_behavior": [
|
|
53
|
+
{
|
|
54
|
+
"grader": "judge",
|
|
55
|
+
"rubric": "A correct answer adds category_id (and only the columns that genuinely need to be mass-assignable) to the Product model's $fillable array, rather than removing mass-assignment protection entirely by setting $guarded = [] or widening $fillable to every column on the table.",
|
|
56
|
+
"pass_criteria": [
|
|
57
|
+
"Adds category_id specifically to $fillable (or an equally scoped fix), shown concretely as a code change to the model.",
|
|
58
|
+
"Keeps the allowlist scoped to only the columns that should be mass-assignable, not every column on the table."
|
|
59
|
+
],
|
|
60
|
+
"fail_criteria": [
|
|
61
|
+
"Recommends setting $guarded = [] on the Product model, or replacing $fillable with a full list of every table column, to make the exception stop occurring."
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"calibration": {
|
|
66
|
+
"known_right": "The Product model's $fillable array is missing category_id -- that's exactly what Eloquent's mass-assignment guard is supposed to catch. Fix it by adding the specific column: protected $fillable = ['name', 'price', 'category_id', ...]; (whatever the model already had, plus category_id). That keeps the allowlist scoped to the columns that genuinely need to be set through mass assignment, rather than opening the model up to every column on the table.",
|
|
67
|
+
"known_wrong": "Just set $guarded = []; on the Product model -- that disables the mass-assignment check entirely so Product::create($request->validated()) will work no matter which columns are present, and you won't run into this exception again for any future field either.",
|
|
68
|
+
"vague": "Update the model so category_id is allowed to be mass-assigned.",
|
|
69
|
+
"subtle_wrong": "Replace the model's $fillable array with a list of every column on the products table (id, name, price, category_id, sku, stock, created_at, updated_at, ...) generated from the migration -- that way category_id is covered along with anything else the table might need going forward, without having to keep coming back to update $fillable every time a new field gets added."
|
|
70
|
+
},
|
|
71
|
+
"anti_patterns": ["$guarded = []"]
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|