@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.
Files changed (104) hide show
  1. package/dist/cli.js +7310 -2471
  2. package/dist/core.js +116 -10
  3. package/package.json +1 -1
  4. package/src/gdskills/bundled/install-manifest.json +349 -2
  5. package/src/gdskills/bundled/rules/core/model-selection.mdc +18 -0
  6. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
  7. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
  8. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
  9. package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
  10. package/src/gdskills/bundled/skills/review/review-jev-comments/SKILL.md +184 -0
  11. package/src/gdskills/bundled/skills/review/review-jev-contract/SKILL.md +193 -0
  12. package/src/gdskills/bundled/skills/review/review-jev-docs/SKILL.md +189 -0
  13. package/src/gdskills/bundled/skills/review/review-jev-risk/SKILL.md +190 -0
  14. package/src/gdskills/bundled/skills/review/review-jev-scenarios/SKILL.md +187 -0
  15. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +88 -15
  16. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +4 -4
  17. package/src/gdskills/bundled/stacks/c-cpp/agent-refs.json +4 -0
  18. package/src/gdskills/bundled/stacks/c-cpp/governance/eval.json +1777 -0
  19. package/src/gdskills/bundled/stacks/c-cpp/governance/scout.json +31 -0
  20. package/src/gdskills/bundled/stacks/c-cpp/pack.json +42 -0
  21. package/src/gdskills/bundled/stacks/c-cpp/rules/coding-style.mdc +80 -0
  22. package/src/gdskills/bundled/stacks/c-cpp/rules/patterns.mdc +87 -0
  23. package/src/gdskills/bundled/stacks/c-cpp/rules/security.mdc +90 -0
  24. package/src/gdskills/bundled/stacks/c-cpp/rules/testing.mdc +83 -0
  25. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/SKILL.md +153 -0
  26. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/evals.json +74 -0
  27. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/SKILL.md +132 -0
  28. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/evals.json +73 -0
  29. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/SKILL.md +151 -0
  30. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/evals.json +74 -0
  31. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/SKILL.md +152 -0
  32. package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/evals.json +74 -0
  33. package/src/gdskills/bundled/stacks/ci-github-gitlab/agent-refs.json +4 -0
  34. package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/eval.json +1295 -0
  35. package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/scout.json +26 -0
  36. package/src/gdskills/bundled/stacks/ci-github-gitlab/pack.json +41 -0
  37. package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/patterns.mdc +77 -0
  38. package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/security.mdc +144 -0
  39. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/SKILL.md +121 -0
  40. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/evals.json +73 -0
  41. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/SKILL.md +139 -0
  42. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/evals.json +73 -0
  43. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/SKILL.md +147 -0
  44. package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/evals.json +74 -0
  45. package/src/gdskills/bundled/stacks/docker-k8s-terraform/agent-refs.json +4 -0
  46. package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/eval.json +865 -0
  47. package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/scout.json +16 -0
  48. package/src/gdskills/bundled/stacks/docker-k8s-terraform/pack.json +46 -0
  49. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/coding-style.mdc +74 -0
  50. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/patterns.mdc +81 -0
  51. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/security.mdc +146 -0
  52. package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/testing.mdc +61 -0
  53. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/SKILL.md +151 -0
  54. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/evals.json +74 -0
  55. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/SKILL.md +135 -0
  56. package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/evals.json +76 -0
  57. package/src/gdskills/bundled/stacks/php-laravel/agent-refs.json +4 -0
  58. package/src/gdskills/bundled/stacks/php-laravel/governance/eval.json +1829 -0
  59. package/src/gdskills/bundled/stacks/php-laravel/governance/scout.json +33 -0
  60. package/src/gdskills/bundled/stacks/php-laravel/pack.json +41 -0
  61. package/src/gdskills/bundled/stacks/php-laravel/rules/coding-style.mdc +82 -0
  62. package/src/gdskills/bundled/stacks/php-laravel/rules/patterns.mdc +80 -0
  63. package/src/gdskills/bundled/stacks/php-laravel/rules/security.mdc +80 -0
  64. package/src/gdskills/bundled/stacks/php-laravel/rules/testing.mdc +82 -0
  65. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/SKILL.md +143 -0
  66. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/evals.json +74 -0
  67. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/SKILL.md +126 -0
  68. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/evals.json +76 -0
  69. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/SKILL.md +140 -0
  70. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/evals.json +75 -0
  71. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/SKILL.md +124 -0
  72. package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/evals.json +74 -0
  73. package/src/gdskills/bundled/stacks/ruby-rails/agent-refs.json +4 -0
  74. package/src/gdskills/bundled/stacks/ruby-rails/governance/eval.json +1673 -0
  75. package/src/gdskills/bundled/stacks/ruby-rails/governance/scout.json +33 -0
  76. package/src/gdskills/bundled/stacks/ruby-rails/pack.json +42 -0
  77. package/src/gdskills/bundled/stacks/ruby-rails/rules/coding-style.mdc +69 -0
  78. package/src/gdskills/bundled/stacks/ruby-rails/rules/patterns.mdc +93 -0
  79. package/src/gdskills/bundled/stacks/ruby-rails/rules/security.mdc +90 -0
  80. package/src/gdskills/bundled/stacks/ruby-rails/rules/testing.mdc +89 -0
  81. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/SKILL.md +143 -0
  82. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/evals.json +73 -0
  83. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/SKILL.md +134 -0
  84. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/evals.json +71 -0
  85. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/SKILL.md +141 -0
  86. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/evals.json +72 -0
  87. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/SKILL.md +125 -0
  88. package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/evals.json +72 -0
  89. package/src/gdskills/bundled/stacks/sql-db/agent-refs.json +4 -0
  90. package/src/gdskills/bundled/stacks/sql-db/governance/eval.json +1829 -0
  91. package/src/gdskills/bundled/stacks/sql-db/governance/scout.json +30 -0
  92. package/src/gdskills/bundled/stacks/sql-db/pack.json +40 -0
  93. package/src/gdskills/bundled/stacks/sql-db/rules/coding-style.mdc +69 -0
  94. package/src/gdskills/bundled/stacks/sql-db/rules/patterns.mdc +134 -0
  95. package/src/gdskills/bundled/stacks/sql-db/rules/security.mdc +74 -0
  96. package/src/gdskills/bundled/stacks/sql-db/rules/testing.mdc +83 -0
  97. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/SKILL.md +147 -0
  98. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/evals.json +72 -0
  99. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/SKILL.md +132 -0
  100. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/evals.json +73 -0
  101. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/SKILL.md +153 -0
  102. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/evals.json +77 -0
  103. package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/SKILL.md +129 -0
  104. 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
+ }