Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 103 additions & 3 deletions resources/boost/skills/models/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
name: models
description: Eloquent model conventions covering mass assignment, casts, relationships, section headers, and activity logging. Every model must follow these structural rules.
description: Eloquent model conventions for mass assignment, casts, relationship naming, activity logging, and mandatory model tests (CRUD + relations).
compatible_agents:
- architect
- implement
Expand All@@ -14,7 +14,7 @@ compatible_agents:

- When creating a new Eloquent model in `app/Models/`.
- When refactoring existing models to align with mass-assignment, casting, and logging conventions.
- When reviewing models for consistency in relationships, helpers, and activity logging.
- When reviewing models for consistency in relationships, helpers, activity logging, and model tests.

## When NOT to Apply

Expand All@@ -27,6 +27,7 @@ compatible_agents:
- Database schema and migrations for the model’s table exist or are being designed.
- `spatie/laravel-activitylog` is installed and configured for activity logging.
- "Business models" means models representing core domain records with audit value (for example invoices, orders, payments). Apply `LogsActivity` to these models by default.
- A factory exists (or is created) for the model and any related models used in tests.

## Process

Expand All@@ -48,18 +49,57 @@ compatible_agents:
### 3. Define Relationships and Helpers

- Use typed return types on all relationship methods (`HasMany`, `BelongsTo`, etc.).
- Follow Laravel relationship naming conventions:
- Use singular names for single-record relations (`belongsTo`, `hasOne`, `morphOne`).
- Use plural names for multi-record relations (`hasMany`, `belongsToMany`, `morphMany`).
- Method names must use `camelCase` based on the related model name (for example, `pipelineSteps()` for `PipelineStep`).
- Avoid generic relation names like `steps()`, `runs()`, `items()`, or `attachments()` when they hide model intent.
- Group related sections of the model with comment headers such as:
- `// --- Relationships ---`
- `// --- Status Helpers ---`
- `// --- Activity Log ---`
- Keep domain-specific helper methods focused and clearly named (e.g., `isDraft()`, `isPaid()`).

### 3.1 Required Relationship Renames (Canonical Examples)

All relationship renames follow this convention: method name = `camelCase(RelatedModelName)` with singular/plural matching relation cardinality.

| Model | Old Method | New Method |
| --- | --- | --- |
| Pipeline | `steps()` | `pipelineSteps()` |
| Pipeline | `runs()` | `pipelineRuns()` |
| PipelineStep | `stepRuns()` | `pipelineStepRuns()` |
| PipelineRun | `stepRuns()` | `pipelineStepRuns()` |
| PipelineTemplate | `steps()` | `pipelineSteps()` |
| Inbox | `items()` | `inboxItems()` |
| Inbox | `serviceUsers()` | `inboxServiceUsers()` |
| Inbox | `importConfigs()` | `inboxImportConfigs()` |
| InboxItem | `importConfig()` | `inboxImportConfig()` |
| InboxItem | `sections()` | `inboxItemSections()` |
| ProviderType | `templates()` | `providerTypeTemplates()` |
| Prompt | `attachments()` | `promptAttachments()` |

### 4. Ensure Testability and Factories

- Create a corresponding factory for every model under `database/factories/`.
- Ensure factories cover required attributes and common state variants.
- Prefer explicit factory states for common statuses (`->draft()`, `->paid()`, `->archived()`) to match model helpers.

### 5. Write Mandatory Model Tests (CRUD + All Relations)

- Add a dedicated model test file under `tests/Unit/Models/` (or the project-standard model-test location).
- Use Pest syntax unless the code area is explicitly standardized on class-based PHPUnit.
- Cover all CRUD operations:
- **Create**: persist model with factory and assert DB row exists.
- **Read**: retrieve model and assert expected attributes/casts.
- **Update**: change persisted data and assert DB reflects updates.
- **Delete**: delete model and assert row is missing/soft-deleted as expected.
- Test every relationship method defined on the model:
- Assert relation returns the correct relation class (`HasMany`, `BelongsTo`, etc.).
- Assert related records can be created/attached through the relation.
- Assert retrieval returns expected related models/count.
- Include at least one helper/cast assertion for domain behavior (for example `isDraft()` and enum/date casts).

## Examples

```php
Expand DownExpand Up@@ -87,7 +127,7 @@ class Invoice extends Model

// --- Relationships ---

public function lines(): HasMany
public function invoiceLines(): HasMany
{
return $this->hasMany(InvoiceLine::class);
}
Expand DownExpand Up@@ -116,6 +156,56 @@ class Invoice extends Model
}
```

```php
// tests/Unit/Models/InvoiceTest.php
use App\Enums\Status;
use App\Models\Invoice;
use App\Models\InvoiceLine;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('supports invoice CRUD operations', function () {
// Create
$invoice = Invoice::factory()->create([
'status' => Status::Draft,
'amount' => '100.00',
]);

expect($invoice->exists)->toBeTrue();
$this->assertDatabaseHas('invoices', ['id' => $invoice->id, 'amount' => '100.00']);

// Read + cast/helper checks
$fresh = Invoice::query()->findOrFail($invoice->id);
expect($fresh->status)->toBe(Status::Draft)
->and($fresh->isDraft())->toBeTrue();

// Update
$fresh->update(['amount' => '250.00']);
$this->assertDatabaseHas('invoices', ['id' => $fresh->id, 'amount' => '250.00']);

// Delete (supports soft deletes)
$fresh->delete();
$this->assertSoftDeleted('invoices', ['id' => $fresh->id]);
// For hard-deleting models, use instead:
// $this->assertDatabaseMissing('invoices', ['id' => $fresh->id]);
});

it('defines and resolves invoiceLines relation', function () {
$invoice = Invoice::factory()->create();

// Relation shape
expect($invoice->invoiceLines())->toBeInstanceOf(HasMany::class);

// Relation behavior
InvoiceLine::factory()->count(2)->create(['invoice_id' => $invoice->id]);

expect($invoice->invoiceLines)->toHaveCount(2)
->and($invoice->invoiceLines->first())->toBeInstanceOf(InvoiceLine::class);
});
```

## Checklists

### Execution Checklist
Expand All@@ -126,7 +216,12 @@ class Invoice extends Model
- [ ] `LogsActivity` trait is added where auditing is required.
- [ ] `getActivitylogOptions()` is configured with `logAll()`, `logOnlyDirty()`, and `dontSubmitEmptyLogs()`.
- [ ] All relationship methods have correct typed return types.
- [ ] Relationship method names use `camelCase(RelatedModelName)` with correct singular/plural form.
- [ ] Existing generic relation names are renamed to explicit model-based names (for example, `steps()` -> `pipelineSteps()`).
- [ ] A matching factory exists in `database/factories/`.
- [ ] A model test exists and covers **Create, Read, Update, Delete** behavior.
- [ ] Every relationship method has at least one assertion for relation type and one for relation data retrieval.
- [ ] At least one cast/helper assertion validates domain behavior (for example enum or status helper).
- [ ] Business logic is extracted to Actions or Services instead of living directly in the model.

## Safety / Things to Avoid
Expand All@@ -135,7 +230,10 @@ class Invoice extends Model
- Defining `$casts` as a property instead of a `casts()` method.
- Omitting the `LogsActivity` trait on business models that should be audited.
- Omitting return types on relationship methods.
- Using ambiguous relationship names that do not reflect the related model class.
- Creating a model without a corresponding factory.
- Creating or updating a model without adding/updating CRUD + relation tests.
- Testing only relation existence but not relation behavior (or vice versa).
- Putting complex business logic directly in the model — prefer Actions or Services.
- Defining model shape with `protected array $fillable = ['name'];` and `protected array $casts = ['status' => 'string'];` instead of `$guarded = []` and `casts()`

Expand All@@ -145,3 +243,5 @@ class Invoice extends Model
- [Spatie Activity Log](https://spatie.be/docs/laravel-activitylog/)
- Related: `Enums/SKILL.md` — enums are cast in `casts()`
- Related: `Migrations/SKILL.md` — migrations define the model's schema
- Related: `PestTesting/SKILL.md` — preferred style for model tests
- Related: `PHPUnit/SKILL.md` — class-based alternative where required
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 103 additions & 3 deletions resources/boost/skills/models/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
name: models
description: Eloquent model conventions covering mass assignment, casts, relationships, section headers, and activity logging. Every model must follow these structural rules.
description: Eloquent model conventions for mass assignment, casts, relationship naming, activity logging, and mandatory model tests (CRUD + relations).
compatible_agents:
- architect
- implement
Expand All@@ -14,7 +14,7 @@ compatible_agents:

- When creating a new Eloquent model in `app/Models/`.
- When refactoring existing models to align with mass-assignment, casting, and logging conventions.
- When reviewing models for consistency in relationships, helpers, and activity logging.
- When reviewing models for consistency in relationships, helpers, activity logging, and model tests.

## When NOT to Apply

Expand All@@ -27,6 +27,7 @@ compatible_agents:
- Database schema and migrations for the model’s table exist or are being designed.
- `spatie/laravel-activitylog` is installed and configured for activity logging.
- "Business models" means models representing core domain records with audit value (for example invoices, orders, payments). Apply `LogsActivity` to these models by default.
- A factory exists (or is created) for the model and any related models used in tests.

## Process

Expand All@@ -48,18 +49,57 @@ compatible_agents:
### 3. Define Relationships and Helpers

- Use typed return types on all relationship methods (`HasMany`, `BelongsTo`, etc.).
- Follow Laravel relationship naming conventions:
- Use singular names for single-record relations (`belongsTo`, `hasOne`, `morphOne`).
- Use plural names for multi-record relations (`hasMany`, `belongsToMany`, `morphMany`).
- Method names must use `camelCase` based on the related model name (for example, `pipelineSteps()` for `PipelineStep`).
- Avoid generic relation names like `steps()`, `runs()`, `items()`, or `attachments()` when they hide model intent.
- Group related sections of the model with comment headers such as:
- `// --- Relationships ---`
- `// --- Status Helpers ---`
- `// --- Activity Log ---`
- Keep domain-specific helper methods focused and clearly named (e.g., `isDraft()`, `isPaid()`).

### 3.1 Required Relationship Renames (Canonical Examples)

All relationship renames follow this convention: method name = `camelCase(RelatedModelName)` with singular/plural matching relation cardinality.

| Model | Old Method | New Method |
| --- | --- | --- |
| Pipeline | `steps()` | `pipelineSteps()` |
| Pipeline | `runs()` | `pipelineRuns()` |
| PipelineStep | `stepRuns()` | `pipelineStepRuns()` |
| PipelineRun | `stepRuns()` | `pipelineStepRuns()` |
| PipelineTemplate | `steps()` | `pipelineSteps()` |
| Inbox | `items()` | `inboxItems()` |
| Inbox | `serviceUsers()` | `inboxServiceUsers()` |
| Inbox | `importConfigs()` | `inboxImportConfigs()` |
| InboxItem | `importConfig()` | `inboxImportConfig()` |
| InboxItem | `sections()` | `inboxItemSections()` |
| ProviderType | `templates()` | `providerTypeTemplates()` |
| Prompt | `attachments()` | `promptAttachments()` |

### 4. Ensure Testability and Factories

- Create a corresponding factory for every model under `database/factories/`.
- Ensure factories cover required attributes and common state variants.
- Prefer explicit factory states for common statuses (`->draft()`, `->paid()`, `->archived()`) to match model helpers.

### 5. Write Mandatory Model Tests (CRUD + All Relations)

- Add a dedicated model test file under `tests/Unit/Models/` (or the project-standard model-test location).
- Use Pest syntax unless the code area is explicitly standardized on class-based PHPUnit.
- Cover all CRUD operations:
- **Create**: persist model with factory and assert DB row exists.
- **Read**: retrieve model and assert expected attributes/casts.
- **Update**: change persisted data and assert DB reflects updates.
- **Delete**: delete model and assert row is missing/soft-deleted as expected.
- Test every relationship method defined on the model:
- Assert relation returns the correct relation class (`HasMany`, `BelongsTo`, etc.).
- Assert related records can be created/attached through the relation.
- Assert retrieval returns expected related models/count.
- Include at least one helper/cast assertion for domain behavior (for example `isDraft()` and enum/date casts).

## Examples

```php
Expand DownExpand Up@@ -87,7 +127,7 @@ class Invoice extends Model

// --- Relationships ---

public function lines(): HasMany
public function invoiceLines(): HasMany
{
return $this->hasMany(InvoiceLine::class);
}
Expand DownExpand Up@@ -116,6 +156,56 @@ class Invoice extends Model
}
```

```php
// tests/Unit/Models/InvoiceTest.php
use App\Enums\Status;
use App\Models\Invoice;
use App\Models\InvoiceLine;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('supports invoice CRUD operations', function () {
// Create
$invoice = Invoice::factory()->create([
'status' => Status::Draft,
'amount' => '100.00',
]);

expect($invoice->exists)->toBeTrue();
$this->assertDatabaseHas('invoices', ['id' => $invoice->id, 'amount' => '100.00']);

// Read + cast/helper checks
$fresh = Invoice::query()->findOrFail($invoice->id);
expect($fresh->status)->toBe(Status::Draft)
->and($fresh->isDraft())->toBeTrue();

// Update
$fresh->update(['amount' => '250.00']);
$this->assertDatabaseHas('invoices', ['id' => $fresh->id, 'amount' => '250.00']);

// Delete (supports soft deletes)
$fresh->delete();
$this->assertSoftDeleted('invoices', ['id' => $fresh->id]);
// For hard-deleting models, use instead:
// $this->assertDatabaseMissing('invoices', ['id' => $fresh->id]);
});

it('defines and resolves invoiceLines relation', function () {
$invoice = Invoice::factory()->create();

// Relation shape
expect($invoice->invoiceLines())->toBeInstanceOf(HasMany::class);

// Relation behavior
InvoiceLine::factory()->count(2)->create(['invoice_id' => $invoice->id]);

expect($invoice->invoiceLines)->toHaveCount(2)
->and($invoice->invoiceLines->first())->toBeInstanceOf(InvoiceLine::class);
});
```

## Checklists

### Execution Checklist
Expand All@@ -126,7 +216,12 @@ class Invoice extends Model
- [ ] `LogsActivity` trait is added where auditing is required.
- [ ] `getActivitylogOptions()` is configured with `logAll()`, `logOnlyDirty()`, and `dontSubmitEmptyLogs()`.
- [ ] All relationship methods have correct typed return types.
- [ ] Relationship method names use `camelCase(RelatedModelName)` with correct singular/plural form.
- [ ] Existing generic relation names are renamed to explicit model-based names (for example, `steps()` -> `pipelineSteps()`).
- [ ] A matching factory exists in `database/factories/`.
- [ ] A model test exists and covers **Create, Read, Update, Delete** behavior.
- [ ] Every relationship method has at least one assertion for relation type and one for relation data retrieval.
- [ ] At least one cast/helper assertion validates domain behavior (for example enum or status helper).
- [ ] Business logic is extracted to Actions or Services instead of living directly in the model.

## Safety / Things to Avoid
Expand All@@ -135,7 +230,10 @@ class Invoice extends Model
- Defining `$casts` as a property instead of a `casts()` method.
- Omitting the `LogsActivity` trait on business models that should be audited.
- Omitting return types on relationship methods.
- Using ambiguous relationship names that do not reflect the related model class.
- Creating a model without a corresponding factory.
- Creating or updating a model without adding/updating CRUD + relation tests.
- Testing only relation existence but not relation behavior (or vice versa).
- Putting complex business logic directly in the model — prefer Actions or Services.
- Defining model shape with `protected array $fillable = ['name'];` and `protected array $casts = ['status' => 'string'];` instead of `$guarded = []` and `casts()`

Expand All@@ -145,3 +243,5 @@ class Invoice extends Model
- [Spatie Activity Log](https://spatie.be/docs/laravel-activitylog/)
- Related: `Enums/SKILL.md` — enums are cast in `casts()`
- Related: `Migrations/SKILL.md` — migrations define the model's schema
- Related: `PestTesting/SKILL.md` — preferred style for model tests
- Related: `PHPUnit/SKILL.md` — class-based alternative where required
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 103 additions & 3 deletions resources/boost/skills/models/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
name: models
description: Eloquent model conventions covering mass assignment, casts, relationships, section headers, and activity logging. Every model must follow these structural rules.
description: Eloquent model conventions for mass assignment, casts, relationship naming, activity logging, and mandatory model tests (CRUD + relations).
compatible_agents:
- architect
- implement
Expand All@@ -14,7 +14,7 @@ compatible_agents:

- When creating a new Eloquent model in `app/Models/`.
- When refactoring existing models to align with mass-assignment, casting, and logging conventions.
- When reviewing models for consistency in relationships, helpers, and activity logging.
- When reviewing models for consistency in relationships, helpers, activity logging, and model tests.

## When NOT to Apply

Expand All@@ -27,6 +27,7 @@ compatible_agents:
- Database schema and migrations for the model’s table exist or are being designed.
- `spatie/laravel-activitylog` is installed and configured for activity logging.
- "Business models" means models representing core domain records with audit value (for example invoices, orders, payments). Apply `LogsActivity` to these models by default.
- A factory exists (or is created) for the model and any related models used in tests.

## Process

Expand All@@ -48,18 +49,57 @@ compatible_agents:
### 3. Define Relationships and Helpers

- Use typed return types on all relationship methods (`HasMany`, `BelongsTo`, etc.).
- Follow Laravel relationship naming conventions:
- Use singular names for single-record relations (`belongsTo`, `hasOne`, `morphOne`).
- Use plural names for multi-record relations (`hasMany`, `belongsToMany`, `morphMany`).
- Method names must use `camelCase` based on the related model name (for example, `pipelineSteps()` for `PipelineStep`).
- Avoid generic relation names like `steps()`, `runs()`, `items()`, or `attachments()` when they hide model intent.
- Group related sections of the model with comment headers such as:
- `// --- Relationships ---`
- `// --- Status Helpers ---`
- `// --- Activity Log ---`
- Keep domain-specific helper methods focused and clearly named (e.g., `isDraft()`, `isPaid()`).

### 3.1 Required Relationship Renames (Canonical Examples)

All relationship renames follow this convention: method name = `camelCase(RelatedModelName)` with singular/plural matching relation cardinality.

| Model | Old Method | New Method |
| --- | --- | --- |
| Pipeline | `steps()` | `pipelineSteps()` |
| Pipeline | `runs()` | `pipelineRuns()` |
| PipelineStep | `stepRuns()` | `pipelineStepRuns()` |
| PipelineRun | `stepRuns()` | `pipelineStepRuns()` |
| PipelineTemplate | `steps()` | `pipelineSteps()` |
| Inbox | `items()` | `inboxItems()` |
| Inbox | `serviceUsers()` | `inboxServiceUsers()` |
| Inbox | `importConfigs()` | `inboxImportConfigs()` |
| InboxItem | `importConfig()` | `inboxImportConfig()` |
| InboxItem | `sections()` | `inboxItemSections()` |
| ProviderType | `templates()` | `providerTypeTemplates()` |
| Prompt | `attachments()` | `promptAttachments()` |

### 4. Ensure Testability and Factories

- Create a corresponding factory for every model under `database/factories/`.
- Ensure factories cover required attributes and common state variants.
- Prefer explicit factory states for common statuses (`->draft()`, `->paid()`, `->archived()`) to match model helpers.

### 5. Write Mandatory Model Tests (CRUD + All Relations)

- Add a dedicated model test file under `tests/Unit/Models/` (or the project-standard model-test location).
- Use Pest syntax unless the code area is explicitly standardized on class-based PHPUnit.
- Cover all CRUD operations:
- **Create**: persist model with factory and assert DB row exists.
- **Read**: retrieve model and assert expected attributes/casts.
- **Update**: change persisted data and assert DB reflects updates.
- **Delete**: delete model and assert row is missing/soft-deleted as expected.
- Test every relationship method defined on the model:
- Assert relation returns the correct relation class (`HasMany`, `BelongsTo`, etc.).
- Assert related records can be created/attached through the relation.
- Assert retrieval returns expected related models/count.
- Include at least one helper/cast assertion for domain behavior (for example `isDraft()` and enum/date casts).

## Examples

```php
Expand DownExpand Up@@ -87,7 +127,7 @@ class Invoice extends Model

// --- Relationships ---

public function lines(): HasMany
public function invoiceLines(): HasMany
{
return $this->hasMany(InvoiceLine::class);
}
Expand DownExpand Up@@ -116,6 +156,56 @@ class Invoice extends Model
}
```

```php
// tests/Unit/Models/InvoiceTest.php
use App\Enums\Status;
use App\Models\Invoice;
use App\Models\InvoiceLine;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('supports invoice CRUD operations', function () {
// Create
$invoice = Invoice::factory()->create([
'status' => Status::Draft,
'amount' => '100.00',
]);

expect($invoice->exists)->toBeTrue();
$this->assertDatabaseHas('invoices', ['id' => $invoice->id, 'amount' => '100.00']);

// Read + cast/helper checks
$fresh = Invoice::query()->findOrFail($invoice->id);
expect($fresh->status)->toBe(Status::Draft)
->and($fresh->isDraft())->toBeTrue();

// Update
$fresh->update(['amount' => '250.00']);
$this->assertDatabaseHas('invoices', ['id' => $fresh->id, 'amount' => '250.00']);

// Delete (supports soft deletes)
$fresh->delete();
$this->assertSoftDeleted('invoices', ['id' => $fresh->id]);
// For hard-deleting models, use instead:
// $this->assertDatabaseMissing('invoices', ['id' => $fresh->id]);
});

it('defines and resolves invoiceLines relation', function () {
$invoice = Invoice::factory()->create();

// Relation shape
expect($invoice->invoiceLines())->toBeInstanceOf(HasMany::class);

// Relation behavior
InvoiceLine::factory()->count(2)->create(['invoice_id' => $invoice->id]);

expect($invoice->invoiceLines)->toHaveCount(2)
->and($invoice->invoiceLines->first())->toBeInstanceOf(InvoiceLine::class);
});
```

## Checklists

### Execution Checklist
Expand All@@ -126,7 +216,12 @@ class Invoice extends Model
- [ ] `LogsActivity` trait is added where auditing is required.
- [ ] `getActivitylogOptions()` is configured with `logAll()`, `logOnlyDirty()`, and `dontSubmitEmptyLogs()`.
- [ ] All relationship methods have correct typed return types.
- [ ] Relationship method names use `camelCase(RelatedModelName)` with correct singular/plural form.
- [ ] Existing generic relation names are renamed to explicit model-based names (for example, `steps()` -> `pipelineSteps()`).
- [ ] A matching factory exists in `database/factories/`.
- [ ] A model test exists and covers **Create, Read, Update, Delete** behavior.
- [ ] Every relationship method has at least one assertion for relation type and one for relation data retrieval.
- [ ] At least one cast/helper assertion validates domain behavior (for example enum or status helper).
- [ ] Business logic is extracted to Actions or Services instead of living directly in the model.

## Safety / Things to Avoid
Expand All@@ -135,7 +230,10 @@ class Invoice extends Model
- Defining `$casts` as a property instead of a `casts()` method.
- Omitting the `LogsActivity` trait on business models that should be audited.
- Omitting return types on relationship methods.
- Using ambiguous relationship names that do not reflect the related model class.
- Creating a model without a corresponding factory.
- Creating or updating a model without adding/updating CRUD + relation tests.
- Testing only relation existence but not relation behavior (or vice versa).
- Putting complex business logic directly in the model — prefer Actions or Services.
- Defining model shape with `protected array $fillable = ['name'];` and `protected array $casts = ['status' => 'string'];` instead of `$guarded = []` and `casts()`

Expand All@@ -145,3 +243,5 @@ class Invoice extends Model
- [Spatie Activity Log](https://spatie.be/docs/laravel-activitylog/)
- Related: `Enums/SKILL.md` — enums are cast in `casts()`
- Related: `Migrations/SKILL.md` — migrations define the model's schema
- Related: `PestTesting/SKILL.md` — preferred style for model tests
- Related: `PHPUnit/SKILL.md` — class-based alternative where required
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 103 additions & 3 deletions resources/boost/skills/models/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
name: models
description: Eloquent model conventions covering mass assignment, casts, relationships, section headers, and activity logging. Every model must follow these structural rules.
description: Eloquent model conventions for mass assignment, casts, relationship naming, activity logging, and mandatory model tests (CRUD + relations).
compatible_agents:
- architect
- implement
Expand All@@ -14,7 +14,7 @@ compatible_agents:

- When creating a new Eloquent model in `app/Models/`.
- When refactoring existing models to align with mass-assignment, casting, and logging conventions.
- When reviewing models for consistency in relationships, helpers, and activity logging.
- When reviewing models for consistency in relationships, helpers, activity logging, and model tests.

## When NOT to Apply

Expand All@@ -27,6 +27,7 @@ compatible_agents:
- Database schema and migrations for the model’s table exist or are being designed.
- `spatie/laravel-activitylog` is installed and configured for activity logging.
- "Business models" means models representing core domain records with audit value (for example invoices, orders, payments). Apply `LogsActivity` to these models by default.
- A factory exists (or is created) for the model and any related models used in tests.

## Process

Expand All@@ -48,18 +49,57 @@ compatible_agents:
### 3. Define Relationships and Helpers

- Use typed return types on all relationship methods (`HasMany`, `BelongsTo`, etc.).
- Follow Laravel relationship naming conventions:
- Use singular names for single-record relations (`belongsTo`, `hasOne`, `morphOne`).
- Use plural names for multi-record relations (`hasMany`, `belongsToMany`, `morphMany`).
- Method names must use `camelCase` based on the related model name (for example, `pipelineSteps()` for `PipelineStep`).
- Avoid generic relation names like `steps()`, `runs()`, `items()`, or `attachments()` when they hide model intent.
- Group related sections of the model with comment headers such as:
- `// --- Relationships ---`
- `// --- Status Helpers ---`
- `// --- Activity Log ---`
- Keep domain-specific helper methods focused and clearly named (e.g., `isDraft()`, `isPaid()`).

### 3.1 Required Relationship Renames (Canonical Examples)

All relationship renames follow this convention: method name = `camelCase(RelatedModelName)` with singular/plural matching relation cardinality.

| Model | Old Method | New Method |
| --- | --- | --- |
| Pipeline | `steps()` | `pipelineSteps()` |
| Pipeline | `runs()` | `pipelineRuns()` |
| PipelineStep | `stepRuns()` | `pipelineStepRuns()` |
| PipelineRun | `stepRuns()` | `pipelineStepRuns()` |
| PipelineTemplate | `steps()` | `pipelineSteps()` |
| Inbox | `items()` | `inboxItems()` |
| Inbox | `serviceUsers()` | `inboxServiceUsers()` |
| Inbox | `importConfigs()` | `inboxImportConfigs()` |
| InboxItem | `importConfig()` | `inboxImportConfig()` |
| InboxItem | `sections()` | `inboxItemSections()` |
| ProviderType | `templates()` | `providerTypeTemplates()` |
| Prompt | `attachments()` | `promptAttachments()` |

### 4. Ensure Testability and Factories

- Create a corresponding factory for every model under `database/factories/`.
- Ensure factories cover required attributes and common state variants.
- Prefer explicit factory states for common statuses (`->draft()`, `->paid()`, `->archived()`) to match model helpers.

### 5. Write Mandatory Model Tests (CRUD + All Relations)

- Add a dedicated model test file under `tests/Unit/Models/` (or the project-standard model-test location).
- Use Pest syntax unless the code area is explicitly standardized on class-based PHPUnit.
- Cover all CRUD operations:
- **Create**: persist model with factory and assert DB row exists.
- **Read**: retrieve model and assert expected attributes/casts.
- **Update**: change persisted data and assert DB reflects updates.
- **Delete**: delete model and assert row is missing/soft-deleted as expected.
- Test every relationship method defined on the model:
- Assert relation returns the correct relation class (`HasMany`, `BelongsTo`, etc.).
- Assert related records can be created/attached through the relation.
- Assert retrieval returns expected related models/count.
- Include at least one helper/cast assertion for domain behavior (for example `isDraft()` and enum/date casts).

## Examples

```php
Expand DownExpand Up@@ -87,7 +127,7 @@ class Invoice extends Model

// --- Relationships ---

public function lines(): HasMany
public function invoiceLines(): HasMany
{
return $this->hasMany(InvoiceLine::class);
}
Expand DownExpand Up@@ -116,6 +156,56 @@ class Invoice extends Model
}
```

```php
// tests/Unit/Models/InvoiceTest.php
use App\Enums\Status;
use App\Models\Invoice;
use App\Models\InvoiceLine;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('supports invoice CRUD operations', function () {
// Create
$invoice = Invoice::factory()->create([
'status' => Status::Draft,
'amount' => '100.00',
]);

expect($invoice->exists)->toBeTrue();
$this->assertDatabaseHas('invoices', ['id' => $invoice->id, 'amount' => '100.00']);

// Read + cast/helper checks
$fresh = Invoice::query()->findOrFail($invoice->id);
expect($fresh->status)->toBe(Status::Draft)
->and($fresh->isDraft())->toBeTrue();

// Update
$fresh->update(['amount' => '250.00']);
$this->assertDatabaseHas('invoices', ['id' => $fresh->id, 'amount' => '250.00']);

// Delete (supports soft deletes)
$fresh->delete();
$this->assertSoftDeleted('invoices', ['id' => $fresh->id]);
// For hard-deleting models, use instead:
// $this->assertDatabaseMissing('invoices', ['id' => $fresh->id]);
});

it('defines and resolves invoiceLines relation', function () {
$invoice = Invoice::factory()->create();

// Relation shape
expect($invoice->invoiceLines())->toBeInstanceOf(HasMany::class);

// Relation behavior
InvoiceLine::factory()->count(2)->create(['invoice_id' => $invoice->id]);

expect($invoice->invoiceLines)->toHaveCount(2)
->and($invoice->invoiceLines->first())->toBeInstanceOf(InvoiceLine::class);
});
```

## Checklists

### Execution Checklist
Expand All@@ -126,7 +216,12 @@ class Invoice extends Model
- [ ] `LogsActivity` trait is added where auditing is required.
- [ ] `getActivitylogOptions()` is configured with `logAll()`, `logOnlyDirty()`, and `dontSubmitEmptyLogs()`.
- [ ] All relationship methods have correct typed return types.
- [ ] Relationship method names use `camelCase(RelatedModelName)` with correct singular/plural form.
- [ ] Existing generic relation names are renamed to explicit model-based names (for example, `steps()` -> `pipelineSteps()`).
- [ ] A matching factory exists in `database/factories/`.
- [ ] A model test exists and covers **Create, Read, Update, Delete** behavior.
- [ ] Every relationship method has at least one assertion for relation type and one for relation data retrieval.
- [ ] At least one cast/helper assertion validates domain behavior (for example enum or status helper).
- [ ] Business logic is extracted to Actions or Services instead of living directly in the model.

## Safety / Things to Avoid
Expand All@@ -135,7 +230,10 @@ class Invoice extends Model
- Defining `$casts` as a property instead of a `casts()` method.
- Omitting the `LogsActivity` trait on business models that should be audited.
- Omitting return types on relationship methods.
- Using ambiguous relationship names that do not reflect the related model class.
- Creating a model without a corresponding factory.
- Creating or updating a model without adding/updating CRUD + relation tests.
- Testing only relation existence but not relation behavior (or vice versa).
- Putting complex business logic directly in the model — prefer Actions or Services.
- Defining model shape with `protected array $fillable = ['name'];` and `protected array $casts = ['status' => 'string'];` instead of `$guarded = []` and `casts()`

Expand All@@ -145,3 +243,5 @@ class Invoice extends Model
- [Spatie Activity Log](https://spatie.be/docs/laravel-activitylog/)
- Related: `Enums/SKILL.md` — enums are cast in `casts()`
- Related: `Migrations/SKILL.md` — migrations define the model's schema
- Related: `PestTesting/SKILL.md` — preferred style for model tests
- Related: `PHPUnit/SKILL.md` — class-based alternative where required
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 103 additions & 3 deletions resources/boost/skills/models/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
name: models
description: Eloquent model conventions covering mass assignment, casts, relationships, section headers, and activity logging. Every model must follow these structural rules.
description: Eloquent model conventions for mass assignment, casts, relationship naming, activity logging, and mandatory model tests (CRUD + relations).
compatible_agents:
- architect
- implement
Expand All@@ -14,7 +14,7 @@ compatible_agents:

- When creating a new Eloquent model in `app/Models/`.
- When refactoring existing models to align with mass-assignment, casting, and logging conventions.
- When reviewing models for consistency in relationships, helpers, and activity logging.
- When reviewing models for consistency in relationships, helpers, activity logging, and model tests.

## When NOT to Apply

Expand All@@ -27,6 +27,7 @@ compatible_agents:
- Database schema and migrations for the model’s table exist or are being designed.
- `spatie/laravel-activitylog` is installed and configured for activity logging.
- "Business models" means models representing core domain records with audit value (for example invoices, orders, payments). Apply `LogsActivity` to these models by default.
- A factory exists (or is created) for the model and any related models used in tests.

## Process

Expand All@@ -48,18 +49,57 @@ compatible_agents:
### 3. Define Relationships and Helpers

- Use typed return types on all relationship methods (`HasMany`, `BelongsTo`, etc.).
- Follow Laravel relationship naming conventions:
- Use singular names for single-record relations (`belongsTo`, `hasOne`, `morphOne`).
- Use plural names for multi-record relations (`hasMany`, `belongsToMany`, `morphMany`).
- Method names must use `camelCase` based on the related model name (for example, `pipelineSteps()` for `PipelineStep`).
- Avoid generic relation names like `steps()`, `runs()`, `items()`, or `attachments()` when they hide model intent.
- Group related sections of the model with comment headers such as:
- `// --- Relationships ---`
- `// --- Status Helpers ---`
- `// --- Activity Log ---`
- Keep domain-specific helper methods focused and clearly named (e.g., `isDraft()`, `isPaid()`).

### 3.1 Required Relationship Renames (Canonical Examples)

All relationship renames follow this convention: method name = `camelCase(RelatedModelName)` with singular/plural matching relation cardinality.

| Model | Old Method | New Method |
| --- | --- | --- |
| Pipeline | `steps()` | `pipelineSteps()` |
| Pipeline | `runs()` | `pipelineRuns()` |
| PipelineStep | `stepRuns()` | `pipelineStepRuns()` |
| PipelineRun | `stepRuns()` | `pipelineStepRuns()` |
| PipelineTemplate | `steps()` | `pipelineSteps()` |
| Inbox | `items()` | `inboxItems()` |
| Inbox | `serviceUsers()` | `inboxServiceUsers()` |
| Inbox | `importConfigs()` | `inboxImportConfigs()` |
| InboxItem | `importConfig()` | `inboxImportConfig()` |
| InboxItem | `sections()` | `inboxItemSections()` |
| ProviderType | `templates()` | `providerTypeTemplates()` |
| Prompt | `attachments()` | `promptAttachments()` |

### 4. Ensure Testability and Factories

- Create a corresponding factory for every model under `database/factories/`.
- Ensure factories cover required attributes and common state variants.
- Prefer explicit factory states for common statuses (`->draft()`, `->paid()`, `->archived()`) to match model helpers.

### 5. Write Mandatory Model Tests (CRUD + All Relations)

- Add a dedicated model test file under `tests/Unit/Models/` (or the project-standard model-test location).
- Use Pest syntax unless the code area is explicitly standardized on class-based PHPUnit.
- Cover all CRUD operations:
- **Create**: persist model with factory and assert DB row exists.
- **Read**: retrieve model and assert expected attributes/casts.
- **Update**: change persisted data and assert DB reflects updates.
- **Delete**: delete model and assert row is missing/soft-deleted as expected.
- Test every relationship method defined on the model:
- Assert relation returns the correct relation class (`HasMany`, `BelongsTo`, etc.).
- Assert related records can be created/attached through the relation.
- Assert retrieval returns expected related models/count.
- Include at least one helper/cast assertion for domain behavior (for example `isDraft()` and enum/date casts).

## Examples

```php
Expand DownExpand Up@@ -87,7 +127,7 @@ class Invoice extends Model

// --- Relationships ---

public function lines(): HasMany
public function invoiceLines(): HasMany
{
return $this->hasMany(InvoiceLine::class);
}
Expand DownExpand Up@@ -116,6 +156,56 @@ class Invoice extends Model
}
```

```php
// tests/Unit/Models/InvoiceTest.php
use App\Enums\Status;
use App\Models\Invoice;
use App\Models\InvoiceLine;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('supports invoice CRUD operations', function () {
// Create
$invoice = Invoice::factory()->create([
'status' => Status::Draft,
'amount' => '100.00',
]);

expect($invoice->exists)->toBeTrue();
$this->assertDatabaseHas('invoices', ['id' => $invoice->id, 'amount' => '100.00']);

// Read + cast/helper checks
$fresh = Invoice::query()->findOrFail($invoice->id);
expect($fresh->status)->toBe(Status::Draft)
->and($fresh->isDraft())->toBeTrue();

// Update
$fresh->update(['amount' => '250.00']);
$this->assertDatabaseHas('invoices', ['id' => $fresh->id, 'amount' => '250.00']);

// Delete (supports soft deletes)
$fresh->delete();
$this->assertSoftDeleted('invoices', ['id' => $fresh->id]);
// For hard-deleting models, use instead:
// $this->assertDatabaseMissing('invoices', ['id' => $fresh->id]);
});

it('defines and resolves invoiceLines relation', function () {
$invoice = Invoice::factory()->create();

// Relation shape
expect($invoice->invoiceLines())->toBeInstanceOf(HasMany::class);

// Relation behavior
InvoiceLine::factory()->count(2)->create(['invoice_id' => $invoice->id]);

expect($invoice->invoiceLines)->toHaveCount(2)
->and($invoice->invoiceLines->first())->toBeInstanceOf(InvoiceLine::class);
});
```

## Checklists

### Execution Checklist
Expand All@@ -126,7 +216,12 @@ class Invoice extends Model
- [ ] `LogsActivity` trait is added where auditing is required.
- [ ] `getActivitylogOptions()` is configured with `logAll()`, `logOnlyDirty()`, and `dontSubmitEmptyLogs()`.
- [ ] All relationship methods have correct typed return types.
- [ ] Relationship method names use `camelCase(RelatedModelName)` with correct singular/plural form.
- [ ] Existing generic relation names are renamed to explicit model-based names (for example, `steps()` -> `pipelineSteps()`).
- [ ] A matching factory exists in `database/factories/`.
- [ ] A model test exists and covers **Create, Read, Update, Delete** behavior.
- [ ] Every relationship method has at least one assertion for relation type and one for relation data retrieval.
- [ ] At least one cast/helper assertion validates domain behavior (for example enum or status helper).
- [ ] Business logic is extracted to Actions or Services instead of living directly in the model.

## Safety / Things to Avoid
Expand All@@ -135,7 +230,10 @@ class Invoice extends Model
- Defining `$casts` as a property instead of a `casts()` method.
- Omitting the `LogsActivity` trait on business models that should be audited.
- Omitting return types on relationship methods.
- Using ambiguous relationship names that do not reflect the related model class.
- Creating a model without a corresponding factory.
- Creating or updating a model without adding/updating CRUD + relation tests.
- Testing only relation existence but not relation behavior (or vice versa).
- Putting complex business logic directly in the model — prefer Actions or Services.
- Defining model shape with `protected array $fillable = ['name'];` and `protected array $casts = ['status' => 'string'];` instead of `$guarded = []` and `casts()`

Expand All@@ -145,3 +243,5 @@ class Invoice extends Model
- [Spatie Activity Log](https://spatie.be/docs/laravel-activitylog/)
- Related: `Enums/SKILL.md` — enums are cast in `casts()`
- Related: `Migrations/SKILL.md` — migrations define the model's schema
- Related: `PestTesting/SKILL.md` — preferred style for model tests
- Related: `PHPUnit/SKILL.md` — class-based alternative where required
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 103 additions & 3 deletions resources/boost/skills/models/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
name: models
description: Eloquent model conventions covering mass assignment, casts, relationships, section headers, and activity logging. Every model must follow these structural rules.
description: Eloquent model conventions for mass assignment, casts, relationship naming, activity logging, and mandatory model tests (CRUD + relations).
compatible_agents:
- architect
- implement
Expand All@@ -14,7 +14,7 @@ compatible_agents:

- When creating a new Eloquent model in `app/Models/`.
- When refactoring existing models to align with mass-assignment, casting, and logging conventions.
- When reviewing models for consistency in relationships, helpers, and activity logging.
- When reviewing models for consistency in relationships, helpers, activity logging, and model tests.

## When NOT to Apply

Expand All@@ -27,6 +27,7 @@ compatible_agents:
- Database schema and migrations for the model’s table exist or are being designed.
- `spatie/laravel-activitylog` is installed and configured for activity logging.
- "Business models" means models representing core domain records with audit value (for example invoices, orders, payments). Apply `LogsActivity` to these models by default.
- A factory exists (or is created) for the model and any related models used in tests.

## Process

Expand All@@ -48,18 +49,57 @@ compatible_agents:
### 3. Define Relationships and Helpers

- Use typed return types on all relationship methods (`HasMany`, `BelongsTo`, etc.).
- Follow Laravel relationship naming conventions:
- Use singular names for single-record relations (`belongsTo`, `hasOne`, `morphOne`).
- Use plural names for multi-record relations (`hasMany`, `belongsToMany`, `morphMany`).
- Method names must use `camelCase` based on the related model name (for example, `pipelineSteps()` for `PipelineStep`).
- Avoid generic relation names like `steps()`, `runs()`, `items()`, or `attachments()` when they hide model intent.
- Group related sections of the model with comment headers such as:
- `// --- Relationships ---`
- `// --- Status Helpers ---`
- `// --- Activity Log ---`
- Keep domain-specific helper methods focused and clearly named (e.g., `isDraft()`, `isPaid()`).

### 3.1 Required Relationship Renames (Canonical Examples)

All relationship renames follow this convention: method name = `camelCase(RelatedModelName)` with singular/plural matching relation cardinality.

| Model | Old Method | New Method |
| --- | --- | --- |
| Pipeline | `steps()` | `pipelineSteps()` |
| Pipeline | `runs()` | `pipelineRuns()` |
| PipelineStep | `stepRuns()` | `pipelineStepRuns()` |
| PipelineRun | `stepRuns()` | `pipelineStepRuns()` |
| PipelineTemplate | `steps()` | `pipelineSteps()` |
| Inbox | `items()` | `inboxItems()` |
| Inbox | `serviceUsers()` | `inboxServiceUsers()` |
| Inbox | `importConfigs()` | `inboxImportConfigs()` |
| InboxItem | `importConfig()` | `inboxImportConfig()` |
| InboxItem | `sections()` | `inboxItemSections()` |
| ProviderType | `templates()` | `providerTypeTemplates()` |
| Prompt | `attachments()` | `promptAttachments()` |

### 4. Ensure Testability and Factories

- Create a corresponding factory for every model under `database/factories/`.
- Ensure factories cover required attributes and common state variants.
- Prefer explicit factory states for common statuses (`->draft()`, `->paid()`, `->archived()`) to match model helpers.

### 5. Write Mandatory Model Tests (CRUD + All Relations)

- Add a dedicated model test file under `tests/Unit/Models/` (or the project-standard model-test location).
- Use Pest syntax unless the code area is explicitly standardized on class-based PHPUnit.
- Cover all CRUD operations:
- **Create**: persist model with factory and assert DB row exists.
- **Read**: retrieve model and assert expected attributes/casts.
- **Update**: change persisted data and assert DB reflects updates.
- **Delete**: delete model and assert row is missing/soft-deleted as expected.
- Test every relationship method defined on the model:
- Assert relation returns the correct relation class (`HasMany`, `BelongsTo`, etc.).
- Assert related records can be created/attached through the relation.
- Assert retrieval returns expected related models/count.
- Include at least one helper/cast assertion for domain behavior (for example `isDraft()` and enum/date casts).

## Examples

```php
Expand DownExpand Up@@ -87,7 +127,7 @@ class Invoice extends Model

// --- Relationships ---

public function lines(): HasMany
public function invoiceLines(): HasMany
{
return $this->hasMany(InvoiceLine::class);
}
Expand DownExpand Up@@ -116,6 +156,56 @@ class Invoice extends Model
}
```

```php
// tests/Unit/Models/InvoiceTest.php
use App\Enums\Status;
use App\Models\Invoice;
use App\Models\InvoiceLine;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('supports invoice CRUD operations', function () {
// Create
$invoice = Invoice::factory()->create([
'status' => Status::Draft,
'amount' => '100.00',
]);

expect($invoice->exists)->toBeTrue();
$this->assertDatabaseHas('invoices', ['id' => $invoice->id, 'amount' => '100.00']);

// Read + cast/helper checks
$fresh = Invoice::query()->findOrFail($invoice->id);
expect($fresh->status)->toBe(Status::Draft)
->and($fresh->isDraft())->toBeTrue();

// Update
$fresh->update(['amount' => '250.00']);
$this->assertDatabaseHas('invoices', ['id' => $fresh->id, 'amount' => '250.00']);

// Delete (supports soft deletes)
$fresh->delete();
$this->assertSoftDeleted('invoices', ['id' => $fresh->id]);
// For hard-deleting models, use instead:
// $this->assertDatabaseMissing('invoices', ['id' => $fresh->id]);
});

it('defines and resolves invoiceLines relation', function () {
$invoice = Invoice::factory()->create();

// Relation shape
expect($invoice->invoiceLines())->toBeInstanceOf(HasMany::class);

// Relation behavior
InvoiceLine::factory()->count(2)->create(['invoice_id' => $invoice->id]);

expect($invoice->invoiceLines)->toHaveCount(2)
->and($invoice->invoiceLines->first())->toBeInstanceOf(InvoiceLine::class);
});
```

## Checklists

### Execution Checklist
Expand All@@ -126,7 +216,12 @@ class Invoice extends Model
- [ ] `LogsActivity` trait is added where auditing is required.
- [ ] `getActivitylogOptions()` is configured with `logAll()`, `logOnlyDirty()`, and `dontSubmitEmptyLogs()`.
- [ ] All relationship methods have correct typed return types.
- [ ] Relationship method names use `camelCase(RelatedModelName)` with correct singular/plural form.
- [ ] Existing generic relation names are renamed to explicit model-based names (for example, `steps()` -> `pipelineSteps()`).
- [ ] A matching factory exists in `database/factories/`.
- [ ] A model test exists and covers **Create, Read, Update, Delete** behavior.
- [ ] Every relationship method has at least one assertion for relation type and one for relation data retrieval.
- [ ] At least one cast/helper assertion validates domain behavior (for example enum or status helper).
- [ ] Business logic is extracted to Actions or Services instead of living directly in the model.

## Safety / Things to Avoid
Expand All@@ -135,7 +230,10 @@ class Invoice extends Model
- Defining `$casts` as a property instead of a `casts()` method.
- Omitting the `LogsActivity` trait on business models that should be audited.
- Omitting return types on relationship methods.
- Using ambiguous relationship names that do not reflect the related model class.
- Creating a model without a corresponding factory.
- Creating or updating a model without adding/updating CRUD + relation tests.
- Testing only relation existence but not relation behavior (or vice versa).
- Putting complex business logic directly in the model — prefer Actions or Services.
- Defining model shape with `protected array $fillable = ['name'];` and `protected array $casts = ['status' => 'string'];` instead of `$guarded = []` and `casts()`

Expand All@@ -145,3 +243,5 @@ class Invoice extends Model
- [Spatie Activity Log](https://spatie.be/docs/laravel-activitylog/)
- Related: `Enums/SKILL.md` — enums are cast in `casts()`
- Related: `Migrations/SKILL.md` — migrations define the model's schema
- Related: `PestTesting/SKILL.md` — preferred style for model tests
- Related: `PHPUnit/SKILL.md` — class-based alternative where required
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 103 additions & 3 deletions resources/boost/skills/models/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
name: models
description: Eloquent model conventions covering mass assignment, casts, relationships, section headers, and activity logging. Every model must follow these structural rules.
description: Eloquent model conventions for mass assignment, casts, relationship naming, activity logging, and mandatory model tests (CRUD + relations).
compatible_agents:
- architect
- implement
Expand All@@ -14,7 +14,7 @@ compatible_agents:

- When creating a new Eloquent model in `app/Models/`.
- When refactoring existing models to align with mass-assignment, casting, and logging conventions.
- When reviewing models for consistency in relationships, helpers, and activity logging.
- When reviewing models for consistency in relationships, helpers, activity logging, and model tests.

## When NOT to Apply

Expand All@@ -27,6 +27,7 @@ compatible_agents:
- Database schema and migrations for the model’s table exist or are being designed.
- `spatie/laravel-activitylog` is installed and configured for activity logging.
- "Business models" means models representing core domain records with audit value (for example invoices, orders, payments). Apply `LogsActivity` to these models by default.
- A factory exists (or is created) for the model and any related models used in tests.

## Process

Expand All@@ -48,18 +49,57 @@ compatible_agents:
### 3. Define Relationships and Helpers

- Use typed return types on all relationship methods (`HasMany`, `BelongsTo`, etc.).
- Follow Laravel relationship naming conventions:
- Use singular names for single-record relations (`belongsTo`, `hasOne`, `morphOne`).
- Use plural names for multi-record relations (`hasMany`, `belongsToMany`, `morphMany`).
- Method names must use `camelCase` based on the related model name (for example, `pipelineSteps()` for `PipelineStep`).
- Avoid generic relation names like `steps()`, `runs()`, `items()`, or `attachments()` when they hide model intent.
- Group related sections of the model with comment headers such as:
- `// --- Relationships ---`
- `// --- Status Helpers ---`
- `// --- Activity Log ---`
- Keep domain-specific helper methods focused and clearly named (e.g., `isDraft()`, `isPaid()`).

### 3.1 Required Relationship Renames (Canonical Examples)

All relationship renames follow this convention: method name = `camelCase(RelatedModelName)` with singular/plural matching relation cardinality.

| Model | Old Method | New Method |
| --- | --- | --- |
| Pipeline | `steps()` | `pipelineSteps()` |
| Pipeline | `runs()` | `pipelineRuns()` |
| PipelineStep | `stepRuns()` | `pipelineStepRuns()` |
| PipelineRun | `stepRuns()` | `pipelineStepRuns()` |
| PipelineTemplate | `steps()` | `pipelineSteps()` |
| Inbox | `items()` | `inboxItems()` |
| Inbox | `serviceUsers()` | `inboxServiceUsers()` |
| Inbox | `importConfigs()` | `inboxImportConfigs()` |
| InboxItem | `importConfig()` | `inboxImportConfig()` |
| InboxItem | `sections()` | `inboxItemSections()` |
| ProviderType | `templates()` | `providerTypeTemplates()` |
| Prompt | `attachments()` | `promptAttachments()` |

### 4. Ensure Testability and Factories

- Create a corresponding factory for every model under `database/factories/`.
- Ensure factories cover required attributes and common state variants.
- Prefer explicit factory states for common statuses (`->draft()`, `->paid()`, `->archived()`) to match model helpers.

### 5. Write Mandatory Model Tests (CRUD + All Relations)

- Add a dedicated model test file under `tests/Unit/Models/` (or the project-standard model-test location).
- Use Pest syntax unless the code area is explicitly standardized on class-based PHPUnit.
- Cover all CRUD operations:
- **Create**: persist model with factory and assert DB row exists.
- **Read**: retrieve model and assert expected attributes/casts.
- **Update**: change persisted data and assert DB reflects updates.
- **Delete**: delete model and assert row is missing/soft-deleted as expected.
- Test every relationship method defined on the model:
- Assert relation returns the correct relation class (`HasMany`, `BelongsTo`, etc.).
- Assert related records can be created/attached through the relation.
- Assert retrieval returns expected related models/count.
- Include at least one helper/cast assertion for domain behavior (for example `isDraft()` and enum/date casts).

## Examples

```php
Expand DownExpand Up@@ -87,7 +127,7 @@ class Invoice extends Model

// --- Relationships ---

public function lines(): HasMany
public function invoiceLines(): HasMany
{
return $this->hasMany(InvoiceLine::class);
}
Expand DownExpand Up@@ -116,6 +156,56 @@ class Invoice extends Model
}
```

```php
// tests/Unit/Models/InvoiceTest.php
use App\Enums\Status;
use App\Models\Invoice;
use App\Models\InvoiceLine;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('supports invoice CRUD operations', function () {
// Create
$invoice = Invoice::factory()->create([
'status' => Status::Draft,
'amount' => '100.00',
]);

expect($invoice->exists)->toBeTrue();
$this->assertDatabaseHas('invoices', ['id' => $invoice->id, 'amount' => '100.00']);

// Read + cast/helper checks
$fresh = Invoice::query()->findOrFail($invoice->id);
expect($fresh->status)->toBe(Status::Draft)
->and($fresh->isDraft())->toBeTrue();

// Update
$fresh->update(['amount' => '250.00']);
$this->assertDatabaseHas('invoices', ['id' => $fresh->id, 'amount' => '250.00']);

// Delete (supports soft deletes)
$fresh->delete();
$this->assertSoftDeleted('invoices', ['id' => $fresh->id]);
// For hard-deleting models, use instead:
// $this->assertDatabaseMissing('invoices', ['id' => $fresh->id]);
});

it('defines and resolves invoiceLines relation', function () {
$invoice = Invoice::factory()->create();

// Relation shape
expect($invoice->invoiceLines())->toBeInstanceOf(HasMany::class);

// Relation behavior
InvoiceLine::factory()->count(2)->create(['invoice_id' => $invoice->id]);

expect($invoice->invoiceLines)->toHaveCount(2)
->and($invoice->invoiceLines->first())->toBeInstanceOf(InvoiceLine::class);
});
```

## Checklists

### Execution Checklist
Expand All@@ -126,7 +216,12 @@ class Invoice extends Model
- [ ] `LogsActivity` trait is added where auditing is required.
- [ ] `getActivitylogOptions()` is configured with `logAll()`, `logOnlyDirty()`, and `dontSubmitEmptyLogs()`.
- [ ] All relationship methods have correct typed return types.
- [ ] Relationship method names use `camelCase(RelatedModelName)` with correct singular/plural form.
- [ ] Existing generic relation names are renamed to explicit model-based names (for example, `steps()` -> `pipelineSteps()`).
- [ ] A matching factory exists in `database/factories/`.
- [ ] A model test exists and covers **Create, Read, Update, Delete** behavior.
- [ ] Every relationship method has at least one assertion for relation type and one for relation data retrieval.
- [ ] At least one cast/helper assertion validates domain behavior (for example enum or status helper).
- [ ] Business logic is extracted to Actions or Services instead of living directly in the model.

## Safety / Things to Avoid
Expand All@@ -135,7 +230,10 @@ class Invoice extends Model
- Defining `$casts` as a property instead of a `casts()` method.
- Omitting the `LogsActivity` trait on business models that should be audited.
- Omitting return types on relationship methods.
- Using ambiguous relationship names that do not reflect the related model class.
- Creating a model without a corresponding factory.
- Creating or updating a model without adding/updating CRUD + relation tests.
- Testing only relation existence but not relation behavior (or vice versa).
- Putting complex business logic directly in the model — prefer Actions or Services.
- Defining model shape with `protected array $fillable = ['name'];` and `protected array $casts = ['status' => 'string'];` instead of `$guarded = []` and `casts()`

Expand All@@ -145,3 +243,5 @@ class Invoice extends Model
- [Spatie Activity Log](https://spatie.be/docs/laravel-activitylog/)
- Related: `Enums/SKILL.md` — enums are cast in `casts()`
- Related: `Migrations/SKILL.md` — migrations define the model's schema
- Related: `PestTesting/SKILL.md` — preferred style for model tests
- Related: `PHPUnit/SKILL.md` — class-based alternative where required
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 103 additions & 3 deletions resources/boost/skills/models/SKILL.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
name: models
description: Eloquent model conventions covering mass assignment, casts, relationships, section headers, and activity logging. Every model must follow these structural rules.
description: Eloquent model conventions for mass assignment, casts, relationship naming, activity logging, and mandatory model tests (CRUD + relations).
compatible_agents:
- architect
- implement
Expand All@@ -14,7 +14,7 @@ compatible_agents:

- When creating a new Eloquent model in `app/Models/`.
- When refactoring existing models to align with mass-assignment, casting, and logging conventions.
- When reviewing models for consistency in relationships, helpers, and activity logging.
- When reviewing models for consistency in relationships, helpers, activity logging, and model tests.

## When NOT to Apply

Expand All@@ -27,6 +27,7 @@ compatible_agents:
- Database schema and migrations for the model’s table exist or are being designed.
- `spatie/laravel-activitylog` is installed and configured for activity logging.
- "Business models" means models representing core domain records with audit value (for example invoices, orders, payments). Apply `LogsActivity` to these models by default.
- A factory exists (or is created) for the model and any related models used in tests.

## Process

Expand All@@ -48,18 +49,57 @@ compatible_agents:
### 3. Define Relationships and Helpers

- Use typed return types on all relationship methods (`HasMany`, `BelongsTo`, etc.).
- Follow Laravel relationship naming conventions:
- Use singular names for single-record relations (`belongsTo`, `hasOne`, `morphOne`).
- Use plural names for multi-record relations (`hasMany`, `belongsToMany`, `morphMany`).
- Method names must use `camelCase` based on the related model name (for example, `pipelineSteps()` for `PipelineStep`).
- Avoid generic relation names like `steps()`, `runs()`, `items()`, or `attachments()` when they hide model intent.
- Group related sections of the model with comment headers such as:
- `// --- Relationships ---`
- `// --- Status Helpers ---`
- `// --- Activity Log ---`
- Keep domain-specific helper methods focused and clearly named (e.g., `isDraft()`, `isPaid()`).

### 3.1 Required Relationship Renames (Canonical Examples)

All relationship renames follow this convention: method name = `camelCase(RelatedModelName)` with singular/plural matching relation cardinality.

| Model | Old Method | New Method |
| --- | --- | --- |
| Pipeline | `steps()` | `pipelineSteps()` |
| Pipeline | `runs()` | `pipelineRuns()` |
| PipelineStep | `stepRuns()` | `pipelineStepRuns()` |
| PipelineRun | `stepRuns()` | `pipelineStepRuns()` |
| PipelineTemplate | `steps()` | `pipelineSteps()` |
| Inbox | `items()` | `inboxItems()` |
| Inbox | `serviceUsers()` | `inboxServiceUsers()` |
| Inbox | `importConfigs()` | `inboxImportConfigs()` |
| InboxItem | `importConfig()` | `inboxImportConfig()` |
| InboxItem | `sections()` | `inboxItemSections()` |
| ProviderType | `templates()` | `providerTypeTemplates()` |
| Prompt | `attachments()` | `promptAttachments()` |

### 4. Ensure Testability and Factories

- Create a corresponding factory for every model under `database/factories/`.
- Ensure factories cover required attributes and common state variants.
- Prefer explicit factory states for common statuses (`->draft()`, `->paid()`, `->archived()`) to match model helpers.

### 5. Write Mandatory Model Tests (CRUD + All Relations)

- Add a dedicated model test file under `tests/Unit/Models/` (or the project-standard model-test location).
- Use Pest syntax unless the code area is explicitly standardized on class-based PHPUnit.
- Cover all CRUD operations:
- **Create**: persist model with factory and assert DB row exists.
- **Read**: retrieve model and assert expected attributes/casts.
- **Update**: change persisted data and assert DB reflects updates.
- **Delete**: delete model and assert row is missing/soft-deleted as expected.
- Test every relationship method defined on the model:
- Assert relation returns the correct relation class (`HasMany`, `BelongsTo`, etc.).
- Assert related records can be created/attached through the relation.
- Assert retrieval returns expected related models/count.
- Include at least one helper/cast assertion for domain behavior (for example `isDraft()` and enum/date casts).

## Examples

```php
Expand DownExpand Up@@ -87,7 +127,7 @@ class Invoice extends Model

// --- Relationships ---

public function lines(): HasMany
public function invoiceLines(): HasMany
{
return $this->hasMany(InvoiceLine::class);
}
Expand DownExpand Up@@ -116,6 +156,56 @@ class Invoice extends Model
}
```

```php
// tests/Unit/Models/InvoiceTest.php
use App\Enums\Status;
use App\Models\Invoice;
use App\Models\InvoiceLine;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('supports invoice CRUD operations', function () {
// Create
$invoice = Invoice::factory()->create([
'status' => Status::Draft,
'amount' => '100.00',
]);

expect($invoice->exists)->toBeTrue();
$this->assertDatabaseHas('invoices', ['id' => $invoice->id, 'amount' => '100.00']);

// Read + cast/helper checks
$fresh = Invoice::query()->findOrFail($invoice->id);
expect($fresh->status)->toBe(Status::Draft)
->and($fresh->isDraft())->toBeTrue();

// Update
$fresh->update(['amount' => '250.00']);
$this->assertDatabaseHas('invoices', ['id' => $fresh->id, 'amount' => '250.00']);

// Delete (supports soft deletes)
$fresh->delete();
$this->assertSoftDeleted('invoices', ['id' => $fresh->id]);
// For hard-deleting models, use instead:
// $this->assertDatabaseMissing('invoices', ['id' => $fresh->id]);
});

it('defines and resolves invoiceLines relation', function () {
$invoice = Invoice::factory()->create();

// Relation shape
expect($invoice->invoiceLines())->toBeInstanceOf(HasMany::class);

// Relation behavior
InvoiceLine::factory()->count(2)->create(['invoice_id' => $invoice->id]);

expect($invoice->invoiceLines)->toHaveCount(2)
->and($invoice->invoiceLines->first())->toBeInstanceOf(InvoiceLine::class);
});
```

## Checklists

### Execution Checklist
Expand All@@ -126,7 +216,12 @@ class Invoice extends Model
- [ ] `LogsActivity` trait is added where auditing is required.
- [ ] `getActivitylogOptions()` is configured with `logAll()`, `logOnlyDirty()`, and `dontSubmitEmptyLogs()`.
- [ ] All relationship methods have correct typed return types.
- [ ] Relationship method names use `camelCase(RelatedModelName)` with correct singular/plural form.
- [ ] Existing generic relation names are renamed to explicit model-based names (for example, `steps()` -> `pipelineSteps()`).
- [ ] A matching factory exists in `database/factories/`.
- [ ] A model test exists and covers **Create, Read, Update, Delete** behavior.
- [ ] Every relationship method has at least one assertion for relation type and one for relation data retrieval.
- [ ] At least one cast/helper assertion validates domain behavior (for example enum or status helper).
- [ ] Business logic is extracted to Actions or Services instead of living directly in the model.

## Safety / Things to Avoid
Expand All@@ -135,7 +230,10 @@ class Invoice extends Model
- Defining `$casts` as a property instead of a `casts()` method.
- Omitting the `LogsActivity` trait on business models that should be audited.
- Omitting return types on relationship methods.
- Using ambiguous relationship names that do not reflect the related model class.
- Creating a model without a corresponding factory.
- Creating or updating a model without adding/updating CRUD + relation tests.
- Testing only relation existence but not relation behavior (or vice versa).
- Putting complex business logic directly in the model — prefer Actions or Services.
- Defining model shape with `protected array $fillable = ['name'];` and `protected array $casts = ['status' => 'string'];` instead of `$guarded = []` and `casts()`

Expand All@@ -145,3 +243,5 @@ class Invoice extends Model
- [Spatie Activity Log](https://spatie.be/docs/laravel-activitylog/)
- Related: `Enums/SKILL.md` — enums are cast in `casts()`
- Related: `Migrations/SKILL.md` — migrations define the model's schema
- Related: `PestTesting/SKILL.md` — preferred style for model tests
- Related: `PHPUnit/SKILL.md` — class-based alternative where required