diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 1d1024d..d5b5d94 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -20,15 +20,15 @@ jobs:
strategy:
fail-fast: true
matrix:
- os: [ubuntu-latest, windows-latest]
+ os: [ubuntu-latest]
php: [8.4, 8.3]
laravel: [12.*, 11.*]
stability: [prefer-lowest, prefer-stable]
include:
- laravel: 12.*
- testbench: 10.*
+ testbench: ^10.6
- laravel: 11.*
- testbench: 9.*
+ testbench: ^9.14
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
@@ -57,7 +57,12 @@ jobs:
run: composer show -D
- name: Execute tests
- run: vendor/bin/pest --ci
+ run: |
+ if [ "${{ matrix.stability }}" == "prefer-lowest" ]; then
+ vendor/bin/pest --ci --exclude-group=redis,arch,functional
+ else
+ vendor/bin/pest --ci --exclude-group=redis
+ fi
test-with-redis:
runs-on: ubuntu-latest
@@ -70,9 +75,9 @@ jobs:
stability: [prefer-stable]
include:
- laravel: 12.*
- testbench: 10.*
+ testbench: ^10.6
- laravel: 11.*
- testbench: 9.*
+ testbench: ^9.14
name: Redis Integration - P${{ matrix.php }} - L${{ matrix.laravel }}
@@ -118,10 +123,26 @@ jobs:
- name: Wait for Redis
run: |
- timeout 30 bash -c 'until redis-cli -h 127.0.0.1 -p 6379 ping; do sleep 1; done'
+ php -r "
+ \$redis = new Redis();
+ \$start = time();
+ while (time() - \$start < 30) {
+ try {
+ if (\$redis->connect('127.0.0.1', 6379, 1)) {
+ echo 'Redis is ready\n';
+ exit(0);
+ }
+ } catch (Exception \$e) {
+ sleep(1);
+ }
+ }
+ echo 'Timeout waiting for Redis\n';
+ exit(1);
+ "
- name: Execute integration tests
- run: vendor/bin/pest --ci
+ run: vendor/bin/pest --ci --group=redis
env:
REDIS_HOST: 127.0.0.1
REDIS_PORT: 6379
+ REDIS_AVAILABLE: true
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..5fef094
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,862 @@
+
+=== foundation rules ===
+
+# Laravel Boost Guidelines
+
+The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications.
+
+## Foundational Context
+This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
+
+- php - 8.4.14
+- laravel/framework (LARAVEL) - v12
+- laravel/prompts (PROMPTS) - v0
+- statamic/cms (STATAMIC) - v5
+- laravel/mcp (MCP) - v0
+- laravel/pint (PINT) - v1
+- laravel/sail (SAIL) - v1
+- phpunit/phpunit (PHPUNIT) - v11
+
+## Conventions
+- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming.
+- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
+- Check for existing components to reuse before writing a new one.
+
+## Verification Scripts
+- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important.
+
+## Application Structure & Architecture
+- Stick to existing directory structure - don't create new base folders without approval.
+- Do not change the application's dependencies without approval.
+
+## Frontend Bundling
+- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them.
+
+## Replies
+- Be concise in your explanations - focus on what's important rather than explaining obvious details.
+
+## Documentation Files
+- You must only create documentation files if explicitly requested by the user.
+
+
+=== boost rules ===
+
+## Laravel Boost
+- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them.
+
+## Artisan
+- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters.
+
+## URLs
+- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port.
+
+## Tinker / Debugging
+- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly.
+- Use the `database-query` tool when you only need to read from the database.
+
+## Reading Browser Logs With the `browser-logs` Tool
+- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost.
+- Only recent browser logs will be useful - ignore old logs.
+
+## Searching Documentation (Critically Important)
+- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages.
+- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc.
+- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches.
+- Search the documentation before making code changes to ensure we are taking the correct approach.
+- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`.
+- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`.
+
+### Available Search Syntax
+- You can and should pass multiple queries at once. The most relevant results will be returned first.
+
+1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'
+2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit"
+3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order
+4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit"
+5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms
+
+
+=== php rules ===
+
+## PHP
+
+- Always use curly braces for control structures, even if it has one line.
+
+### Constructors
+- Use PHP 8 constructor property promotion in `__construct()`.
+ - public function __construct(public GitHub $github) { }
+- Do not allow empty `__construct()` methods with zero parameters.
+
+### Type Declarations
+- Always use explicit return type declarations for methods and functions.
+- Use appropriate PHP type hints for method parameters.
+
+
+protected function isAccessible(User $user, ?string $path = null): bool
+{
+ ...
+}
+
+
+## Comments
+- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on.
+
+## PHPDoc Blocks
+- Add useful array shape type definitions for arrays when appropriate.
+
+## Enums
+- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.
+
+
+=== herd rules ===
+
+## Laravel Herd
+
+- The application is served by Laravel Herd and will be available at: https?://[kebab-case-project-dir].test. Use the `get-absolute-url` tool to generate URLs for the user to ensure valid URLs.
+- You must not run any commands to make the site available via HTTP(s). It is _always_ available through Laravel Herd.
+
+
+=== laravel/core rules ===
+
+## Do Things the Laravel Way
+
+- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
+- If you're creating a generic PHP class, use `php artisan make:class`.
+- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
+
+### Database
+- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins.
+- Use Eloquent models and relationships before suggesting raw database queries
+- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them.
+- Generate code that prevents N+1 query problems by using eager loading.
+- Use Laravel's query builder for very complex database operations.
+
+### Model Creation
+- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`.
+
+### APIs & Eloquent Resources
+- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
+
+### Controllers & Validation
+- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages.
+- Check sibling Form Requests to see if the application uses array or string based validation rules.
+
+### Queues
+- Use queued jobs for time-consuming operations with the `ShouldQueue` interface.
+
+### Authentication & Authorization
+- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).
+
+### URL Generation
+- When generating links to other pages, prefer named routes and the `route()` function.
+
+### Configuration
+- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`.
+
+### Testing
+- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
+- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
+- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
+
+### Vite Error
+- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`.
+
+
+=== laravel/v12 rules ===
+
+## Laravel 12
+
+- Use the `search-docs` tool to get version specific documentation.
+- Since Laravel 11, Laravel has a new streamlined file structure which this project uses.
+
+### Laravel 12 Structure
+- No middleware files in `app/Http/Middleware/`.
+- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files.
+- `bootstrap/providers.php` contains application specific service providers.
+- **No app\Console\Kernel.php** - use `bootstrap/app.php` or `routes/console.php` for console configuration.
+- **Commands auto-register** - files in `app/Console/Commands/` are automatically available and do not require manual registration.
+
+### Database
+- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost.
+- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`.
+
+### Models
+- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.
+
+
+=== pint/core rules ===
+
+## Laravel Pint Code Formatter
+
+- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
+- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues.
+
+
+=== phpunit/core rules ===
+
+## PHPUnit Core
+
+- This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `php artisan make:test --phpunit {name}` to create a new test.
+- If you see a test using "Pest", convert it to PHPUnit.
+- Every time a test has been updated, run that singular test.
+- When the tests relating to your feature are passing, ask the user if they would like to also run the entire test suite to make sure everything is still passing.
+- Tests should test all of the happy paths, failure paths, and weird paths.
+- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files, these are core to the application.
+
+### Running Tests
+- Run the minimal number of tests, using an appropriate filter, before finalizing.
+- To run all tests: `php artisan test`.
+- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`.
+- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file).
+
+
+
+=== phpeek documentation rules ===
+
+## PHPeek Documentation System
+
+This application automatically imports and displays documentation from GitHub releases. Understanding this system is critical for working with documentation packages.
+
+### Core Philosophy
+
+**Major Version Management**
+- Store ONE entry per major version (v1, v2, v3) - not individual releases
+- Automatically keep only the latest release within each major version
+- When importing version 1.2.1 after 1.2.0, the system updates the existing v1 entry
+- URLs use major version: `/docs/{package}/v1`, `/docs/{package}/v2`
+
+**Version Comparison Logic**
+- System uses PHP's `version_compare()` to determine if new version is newer
+- Only updates if new version is strictly newer than existing
+- Skips import if existing version is same or newer
+- Example: 1.2.1 > 1.2.0 → updates | 1.2.0 <= 1.2.0 → skips
+
+### Files NOT Used on Website
+
+**README.md - GitHub Only**
+- README.md is NEVER displayed on the PHPeek website
+- README.md is only for GitHub repository display
+- If documentation exists in a `/docs` folder, README.md is completely ignored
+- Do NOT reference README.md in documentation guides or examples
+
+**Files Used on Website**
+- All `.md` files in the `/docs` folder
+- All image/asset files in `/docs` directory
+- `_index.md` files for directory landing pages (optional but recommended)
+
+### Documentation Directory Structure
+
+**Required Structure**
+```
+docs/
+├── _index.md # Optional: Main docs landing page
+├── introduction.md # Recommended: Getting started
+├── installation.md # Recommended: Setup instructions
+├── quickstart.md # Recommended: Quick examples
+├── basic-usage/ # Feature directories
+│ ├── _index.md # Optional: Section landing page
+│ ├── feature-one.md
+│ └── feature-two.md
+└── advanced-usage/
+ ├── _index.md
+ ├── advanced-feature.md
+ └── expert-topics.md
+```
+
+**Directory Naming Rules**
+- Use lowercase with hyphens: `basic-usage/`, `advanced-features/`
+- Keep names short but descriptive: `api-reference/`, `platform-support/`
+- Avoid deep nesting (max 2-3 levels recommended)
+
+### Metadata Requirements (Frontmatter)
+
+**Required Fields** (extracted automatically)
+```yaml
+---
+title: "Page Title" # REQUIRED - Used in navigation, page header, SEO
+description: "Brief summary" # REQUIRED - Used in navigation, meta tags, SEO
+weight: 99 # OPTIONAL - Lower numbers appear first (default: 99)
+hidden: false # OPTIONAL - Set true to hide from navigation
+---
+```
+
+**How Metadata Is Used**
+
+**Title Field**
+- **Navigation Sidebar**: Displays as clickable link text
+- **Page Header**: Shown as `
` on the page (if no `# heading` in content)
+- **Browser Tab**: Used in `` tag
+- **SEO**: Used in meta tags for search engines
+- **Breadcrumbs**: Used in navigation breadcrumbs (future feature)
+
+**Description Field**
+- **Navigation Tooltip**: May be shown on hover (future feature)
+- **Meta Description**: Used in `` tag
+- **Search Results**: Displayed in search engine results
+- **Social Sharing**: Used when sharing on social media
+- **SEO**: Critical for search ranking and click-through rate
+
+**Weight Field**
+- **Navigation Order**: Controls order in sidebar
+- **Not visible to users**: Only affects sort order
+- **Directory-wide**: Weight applies within current directory only
+- **Index pages**: Use weight to position directory sections
+
+**Hidden Field**
+- **Navigation**: If `true`, page won't appear in sidebar
+- **Direct Access**: Page is still accessible via direct URL
+- **Use Cases**: Draft pages, admin docs, deprecated content
+- **Default**: `false` - all pages are visible
+
+**Complete Metadata Example**
+```yaml
+---
+title: "CPU Usage Calculation"
+description: "Deep dive into calculating CPU usage percentages from raw time counters"
+weight: 25
+hidden: false
+---
+
+# CPU Usage Calculation
+
+This guide explains how to calculate CPU usage percentages...
+```
+
+**Title Best Practices**
+- Use Title Case: "Getting Started", "API Reference", "Error Handling"
+- Keep under 50 characters for optimal display
+- Be specific: "CPU Metrics" not just "Metrics"
+- Avoid redundancy: Don't repeat package name in every title
+- Match content: Title should reflect page purpose
+- Unique titles: No duplicate titles in navigation
+
+**Description Best Practices**
+- One clear sentence summarizing the page
+- 60-160 characters for optimal SEO (120 characters ideal)
+- Focus on user value: what they'll learn or accomplish
+- Action-oriented: "Get", "Learn", "Understand", "Monitor"
+- Avoid generic phrases: "This page describes..."
+- Include key terms for SEO
+
+**Good Description Examples**
+```yaml
+# ✅ Specific and action-oriented
+description: "Get raw CPU time counters and per-core metrics from the system"
+
+# ✅ Clear user value
+description: "Master the Result pattern for explicit error handling without exceptions"
+
+# ✅ Concise with key terms
+description: "Monitor resource usage for individual processes or process groups"
+
+# ❌ Too generic
+description: "This page describes CPU metrics and how to use them"
+
+# ❌ Too long (>160 chars)
+description: "This comprehensive guide will walk you through everything you need to know about CPU metrics including how to get them, what they mean, and how to interpret the results in various scenarios"
+
+# ❌ No user value
+description: "CPU metrics documentation page"
+```
+
+**Weight System Deep Dive**
+- Default weight: 99 (if not specified)
+- Lower numbers appear first: 1, 2, 3... 99
+- Same weight = alphabetical sort by title
+- Weight scope: Only within current directory
+- Recommended ranges:
+ - 1-10: Critical pages (introduction, installation, quickstart)
+ - 11-30: Common features (basic usage guides)
+ - 31-70: Advanced features (complex topics)
+ - 71-99: Reference material (API docs, appendices)
+
+**Weight Organization Example**
+```yaml
+# docs/introduction.md
+weight: 1 # First page - overview
+
+# docs/installation.md
+weight: 2 # Setup instructions
+
+# docs/quickstart.md
+weight: 3 # Quick examples
+
+# docs/basic-usage/cpu-metrics.md
+weight: 10 # First basic feature
+
+# docs/basic-usage/memory-metrics.md
+weight: 11 # Second basic feature
+
+# docs/advanced-usage/custom-implementations.md
+weight: 50 # Advanced topic
+
+# docs/api-reference.md
+weight: 90 # Reference material at end
+```
+
+**Hidden Pages Use Cases**
+```yaml
+# Draft content not ready for users
+---
+title: "Kubernetes Integration"
+description: "Deploy metrics collection in Kubernetes clusters"
+hidden: true # Still being written
+---
+
+# Deprecated content kept for reference
+---
+title: "Legacy API v1"
+description: "Old API documentation (deprecated)"
+hidden: true # Don't show in navigation
+---
+
+# Internal documentation
+---
+title: "Development Setup"
+description: "Local development environment setup for contributors"
+hidden: true # Only for maintainers
+---
+```
+
+### Index Files (_index.md)
+
+**Purpose**
+- Creates landing pages for directory sections
+- Provides overview of section contents
+- Optional but recommended for better UX
+
+**When to Use _index.md**
+- ✅ For major sections with 3+ child pages
+- ✅ When you want custom intro text for a section
+- ✅ For directories that need explanation (e.g., "Architecture")
+- ❌ Not required for simple directories
+- ❌ System auto-creates virtual indexes if missing
+
+**Example _index.md**
+```markdown
+---
+title: "Basic Usage"
+description: "Essential features for getting started with the package"
+weight: 1
+---
+
+# Basic Usage
+
+This section covers the fundamental features you'll use daily:
+
+- CPU and memory monitoring
+- Disk usage tracking
+- Network statistics
+- System uptime
+
+Start with the "System Overview" guide for a quick introduction.
+```
+
+### Metadata Quality Levels
+
+The system assigns quality levels based on metadata completeness:
+
+**Complete** ✅
+- All files have `title` and `description`
+- All directories have `_index.md` files
+- Consistent weight ordering
+- No missing or empty metadata
+
+**Partial** ⚠️
+- Most files have metadata but some missing
+- Some directories lack `_index.md` (system creates virtual ones)
+- Generally functional but could be improved
+
+**Minimal** ⚠️
+- Many files missing metadata
+- Multiple directories without indexes
+- May have auto-generated titles (from filenames)
+- Navigation still works but UX is degraded
+
+### Navigation Building Logic
+
+**Hierarchical Tree Structure**
+1. System scans `/docs` folder recursively
+2. Extracts frontmatter from each `.md` file
+3. Groups files by directory
+4. Sorts by weight (ascending), then title (alphabetically)
+5. Builds nested tree structure for navigation
+6. Creates virtual `_index.md` for missing directory indexes
+
+**Path-Based Routing**
+- File path determines URL
+- `docs/introduction.md` → `/docs/{package}/v1/introduction`
+- `docs/basic-usage/cpu-metrics.md` → `/docs/{package}/v1/basic-usage/cpu-metrics`
+- Remove `.md` extension from URLs
+- Preserve directory structure in URLs
+
+**Current Page Highlighting**
+- Navigation component tracks `current_path`
+- Compares file path to current URL
+- Applies active styles to matching navigation item
+
+### Links and URLs
+
+**Internal Documentation Links**
+- Use relative paths to link between documentation pages
+- Path is relative to current file location
+- Remove `.md` extension from link targets
+- System automatically converts to proper URLs
+
+**Link Syntax Examples**
+```markdown
+# Link to sibling file in same directory
+[Installation Guide](installation)
+
+# Link to file in parent directory
+[Back to Introduction](../introduction)
+
+# Link to file in subdirectory
+[CPU Metrics](basic-usage/cpu-metrics)
+
+# Link to file in different subdirectory
+[Platform Comparison](../platform-support/comparison)
+
+# Link with anchor to heading
+[Error Handling](advanced-usage/error-handling#result-pattern)
+```
+
+**How Link Resolution Works**
+1. Markdown: `[CPU Guide](basic-usage/cpu-metrics)`
+2. Resolves to file: `docs/basic-usage/cpu-metrics.md`
+3. Renders as URL: `/docs/{package}/v1/basic-usage/cpu-metrics`
+4. Navigation highlights current page
+
+**External Links**
+```markdown
+# External links use full URLs
+[GitHub Repository](https://github.com/owner/repo)
+[Official Website](https://example.com)
+
+# Always include https:// for external links
+✅ [Example](https://example.com)
+❌ [Example](example.com)
+```
+
+**Link Best Practices**
+- ✅ Use descriptive link text: `[View API Reference](api-reference)`
+- ❌ Avoid generic text: `[Click here](api-reference)` or `[Read more](guide)`
+- ✅ Keep paths relative: `[Guide](../guide)`
+- ❌ Don't hardcode: `[Guide](/docs/package/v1/guide)`
+- ✅ Test all links after import
+- ❌ Don't link to README.md (it's not displayed)
+
+### Asset Handling
+
+**Image References**
+- Use relative paths in markdown: ``
+- Always include alt text for accessibility: ``
+- System automatically rewrites to absolute URLs during render
+- Supports paths relative to current file or document root
+- All assets copied to: `public/docs/{package}/v1/`
+
+**Image Syntax Examples**
+```markdown
+# Image in same directory
+
+
+# Image in subdirectory
+
+
+# Image in parent images folder
+
+
+# Image with title tooltip
+
+```
+
+**Supported Asset Types**
+- Images: `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`
+- Can be in any subdirectory within `/docs`
+- Common patterns: `/docs/images/`, `/docs/assets/`, `/docs/screenshots/`
+
+**Asset Organization**
+```
+docs/
+├── images/ # Shared images for all docs
+│ ├── logo.png
+│ └── architecture.svg
+├── basic-usage/
+│ ├── cpu-chart.png # Feature-specific image
+│ └── cpu-metrics.md
+└── screenshots/ # UI screenshots
+ └── dashboard.png
+```
+
+### Code Blocks
+
+**Syntax Highlighting**
+- Supported languages: PHP, JavaScript, Bash, JSON, YAML, XML, HTML, Markdown, SQL, Dockerfile
+- Specify language after opening fence: \`\`\`php, \`\`\`bash, \`\`\`json
+- Code blocks automatically get copy button
+- Custom GitHub Dark theme with PHPeek brand colors
+
+**Best Practices**
+```markdown
+# ✅ Good - Specify language
+\`\`\`php
+$metrics = SystemMetrics::cpu()->get();
+\`\`\`
+
+# ❌ Avoid - No language specified
+\`\`\`
+$metrics = SystemMetrics::cpu()->get();
+\`\`\`
+```
+
+### Import Process (Automated)
+
+**How It Works**
+1. Command: `php artisan docs:import {owner} {repo}`
+2. Fetches all GitHub releases via API
+3. For each release:
+ - Extracts major version (1.2.3 → v1)
+ - Compares with existing entry for that major version
+ - Skips if existing is newer or same
+ - Updates if new version is newer
+4. Clones repository with `git clone --depth 1 --single-branch`
+5. Copies docs and assets to storage
+6. Scans `/docs` folder and builds navigation structure
+7. Creates/updates Statamic entry with metadata
+
+**Storage Locations**
+- Git repos: `storage/app/docs/{package}/v1/repo/`
+- Public assets: `public/docs/{package}/v1/`
+- Statamic entries: `content/collections/documentation/{package}-{major_version}.md`
+
+### Common Patterns
+
+**Standard Documentation Set**
+```
+docs/
+├── introduction.md # What is this package?
+├── installation.md # How to install
+├── quickstart.md # 5-minute getting started
+├── basic-usage/ # Core features
+│ ├── _index.md
+│ ├── feature-1.md
+│ └── feature-2.md
+├── advanced-usage/ # Complex scenarios
+│ ├── _index.md
+│ └── advanced.md
+├── api-reference.md # Complete API docs
+└── testing.md # How to test
+```
+
+**Minimal Documentation Set**
+```
+docs/
+├── introduction.md # Overview
+├── installation.md # Setup
+└── quickstart.md # Examples
+```
+
+### Quality Guidelines for LLMs
+
+**When Creating Documentation Structure**
+
+✅ **DO**
+- Add frontmatter to every `.md` file
+- Use descriptive titles and descriptions
+- Create `_index.md` for major sections
+- Use weight to control order
+- Keep directory nesting shallow (2-3 levels max)
+- Use lowercase-with-hyphens for file/folder names
+- Specify language for code blocks
+- Use relative paths for images
+
+❌ **DON'T**
+- Reference README.md in docs (it's not displayed)
+- Create deeply nested directories (>3 levels)
+- Use spaces or special characters in filenames
+- Omit frontmatter metadata
+- Use generic titles like "Page 1", "Document"
+- Hardcode absolute URLs for assets
+- Forget to specify code block languages
+
+**Metadata Quality Checklist**
+- [ ] Every `.md` file has `title` and `description`
+- [ ] Titles are unique and descriptive
+- [ ] Descriptions are 60-160 characters
+- [ ] Major sections have `_index.md` files
+- [ ] Weight values create logical ordering
+- [ ] No hidden files unless intentional
+- [ ] File names match content (no generic names)
+- [ ] Directory structure is logical and shallow
+
+### Example Documentation Entry
+
+**File**: `docs/basic-usage/cpu-metrics.md`
+
+```markdown
+---
+title: "CPU Metrics"
+description: "Get raw CPU time counters and per-core metrics from the system"
+weight: 10
+---
+
+# CPU Metrics
+
+Monitor CPU usage and performance with real-time metrics.
+
+## Getting CPU Statistics
+
+\`\`\`php
+use PHPeek\SystemMetrics\SystemMetrics;
+
+$cpu = SystemMetrics::cpu()->get();
+
+echo "CPU Cores: {$cpu->cores}\n";
+echo "User Time: {$cpu->user}ms\n";
+echo "System Time: {$cpu->system}ms\n";
+\`\`\`
+
+## Per-Core Metrics
+
+\`\`\`php
+foreach ($cpu->perCore as $core) {
+ echo "Core {$core->id}: {$core->usage}%\n";
+}
+\`\`\`
+
+## Platform Support
+
+- ✅ Linux: Full support via `/proc/stat`
+- ✅ macOS: Full support via `host_processor_info()`
+
+See [Platform Comparison](../platform-support/comparison) for details.
+```
+
+### Troubleshooting
+
+**Navigation Not Showing**
+- Check frontmatter exists and is valid YAML
+- Verify `title` and `description` are present
+- Ensure file is `.md` extension
+- Check `hidden: true` is not set
+- Verify file is in `/docs` folder (not root)
+
+**Images Not Loading**
+- Use relative paths: ``
+- Verify image exists in repository
+- Check file extension is supported
+- Ensure image is within `/docs` directory
+- Run import again to copy assets
+
+**Wrong Page Order**
+- Add `weight` to frontmatter
+- Lower numbers appear first (1, 2, 3...)
+- Default weight is 99
+- Same weight = alphabetical by title
+
+**Code Not Highlighting**
+- Specify language: \`\`\`php not just \`\`\`
+- Supported: php, js, bash, json, yaml, xml, html, md, sql, dockerfile
+- Check spelling of language name
+- Ensure code block is properly closed
+
+### SEO and URL Structure
+
+**URL Pattern**
+- Base: `/docs/{package}/{major_version}/{page_path}`
+- Package: `system-metrics`, `laravel-helpers`
+- Major Version: `v1`, `v2`, `v3`
+- Page Path: Mirrors file structure without `.md`
+
+**URL Examples**
+```
+# Root documentation page
+File: docs/introduction.md
+URL: /docs/system-metrics/v1/introduction
+
+# Nested in directory
+File: docs/basic-usage/cpu-metrics.md
+URL: /docs/system-metrics/v1/basic-usage/cpu-metrics
+
+# Directory index
+File: docs/basic-usage/_index.md
+URL: /docs/system-metrics/v1/basic-usage/_index
+
+# Deep nesting
+File: docs/advanced/features/custom.md
+URL: /docs/system-metrics/v1/advanced/features/custom
+```
+
+**SEO Metadata Generated**
+```html
+
+CPU Metrics - System Metrics v1 - PHPeek
+
+
+
+
+
+
+
+
+
+
+```
+
+**SEO Best Practices**
+- ✅ Unique title and description for each page
+- ✅ Keep URLs short and descriptive
+- ✅ Use kebab-case for readability: `cpu-metrics` not `cpumetrics`
+- ✅ Include keywords in title and description
+- ✅ Descriptive file names that match content
+- ❌ Don't stuff keywords
+- ❌ Don't use generic titles like "Page 1"
+- ❌ Don't create duplicate content
+
+**URL Optimization**
+```yaml
+# ✅ Good URL structure
+File: docs/basic-usage/cpu-metrics.md
+URL: /docs/system-metrics/v1/basic-usage/cpu-metrics
+SEO: Clear, descriptive, hierarchical
+
+# ✅ Good file naming
+cpu-metrics.md # Clear purpose
+error-handling.md # Descriptive
+platform-comparison.md # Specific
+
+# ❌ Poor URL structure
+File: docs/stuff/page1.md
+URL: /docs/system-metrics/v1/stuff/page1
+SEO: Unclear, generic, no value
+
+# ❌ Poor file naming
+file1.md # Generic
+doc.md # Vague
+metrics.md # Too broad
+```
+
+**Metadata Impact on SEO**
+
+**Title Impact**
+- Search engine result headline
+- Social media share title
+- Browser bookmark name
+- Navigation link text
+
+**Description Impact**
+- Search result snippet
+- Social media preview text
+- May influence click-through rate
+- Helps search engines understand content
+
+**Weight Impact**
+- No direct SEO effect
+- Affects user navigation experience
+- May indirectly affect engagement metrics
+
+**Hidden Impact**
+- Page not in sitemap (if implemented)
+- Not linked from navigation
+- Still accessible via direct URL
+- May be indexed by search engines unless robots meta tag added
diff --git a/composer.json b/composer.json
index fa6d302..e29a25a 100644
--- a/composer.json
+++ b/composer.json
@@ -22,7 +22,7 @@
],
"require": {
"php": "^8.3|^8.4",
- "gophpeek/system-metrics": "^1.0",
+ "gophpeek/system-metrics": "^1.2",
"illuminate/contracts": "^11.0|^12.0",
"spatie/laravel-package-tools": "^1.16",
"spatie/laravel-prometheus": "^1.3"
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index a81713c..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# Laravel Queue Metrics Documentation
-
-Welcome to the Laravel Queue Metrics documentation. This package provides comprehensive queue monitoring and metrics collection for Laravel applications.
-
-## Getting Started
-
-- [Installation](installation.md) - Complete installation guide
-- [Quick Start](quickstart.md) - Get up and running in 5 minutes
-- [Configuration Reference](configuration-reference.md) - All configuration options
-
-## Core Features
-
-- [Architecture Overview](architecture.md) - How the package works internally
-- [Facade API](facade-api.md) - Primary developer interface
-- [HTTP API](api-endpoints.md) - REST endpoints for metrics access
-- [Prometheus Export](prometheus.md) - Monitoring integration
-- [Artisan Commands](artisan-commands.md) - CLI tools and maintenance
-
-## Extensibility
-
-- [Hooks System](hooks.md) - Transform metrics data
-- [Events](events.md) - React to metrics changes
-
-## Optimization
-
-- [Performance Tuning](performance.md) - Optimize for your workload
-
----
-
-**Need help?** See the [Quick Start Guide](quickstart.md).
-
-**Found an issue?** Please report it on [GitHub](https://github.com/gophpeek/laravel-queue-metrics/issues).
diff --git a/docs/advanced-usage/_index.md b/docs/advanced-usage/_index.md
new file mode 100644
index 0000000..5d06b93
--- /dev/null
+++ b/docs/advanced-usage/_index.md
@@ -0,0 +1,86 @@
+---
+title: "Advanced Usage"
+description: "Deep dive into architecture, events, integrations, and performance optimization"
+weight: 30
+---
+
+# Advanced Usage
+
+This section covers advanced features, internal architecture, and optimization strategies for Laravel Queue Metrics.
+
+## Advanced Topics
+
+### Architecture & Internals
+
+Understand how Laravel Queue Metrics works under the hood, including the event-driven architecture, data storage patterns, and internal components.
+
+**Learn more:** [Architecture Overview](architecture)
+
+### Events System
+
+React to metrics changes and lifecycle events in your application. Build custom integrations, trigger alerts, and implement auto-scaling based on queue metrics.
+
+**Learn more:** [Events System](events)
+
+### Prometheus Integration
+
+Export queue metrics to Prometheus for centralized monitoring with Grafana dashboards and alerting rules.
+
+**Learn more:** [Prometheus Integration](prometheus)
+
+### Performance Tuning
+
+Optimize Laravel Queue Metrics for your specific workload, including storage configuration, batch processing, and resource management.
+
+**Learn more:** [Performance Tuning](performance)
+
+## When to Use Advanced Features
+
+### Architecture Knowledge
+
+Understanding the architecture helps when:
+- Debugging complex issues
+- Building custom storage drivers
+- Extending the package with hooks
+- Optimizing for specific use cases
+
+### Events Integration
+
+Use events when you need to:
+- Send alerts based on queue health
+- Trigger auto-scaling decisions
+- Integrate with external monitoring
+- Implement custom business logic
+
+### Prometheus Export
+
+Consider Prometheus integration for:
+- Centralized monitoring infrastructure
+- Long-term metrics retention
+- Multi-service dashboards
+- Advanced alerting rules
+
+### Performance Optimization
+
+Performance tuning is essential for:
+- High-throughput systems (>10,000 jobs/min)
+- Large-scale deployments
+- Resource-constrained environments
+- Cost optimization
+
+## Prerequisites
+
+Before diving into advanced topics, ensure you're familiar with:
+
+- [Basic Usage](../basic-usage) - Facade API, HTTP endpoints, and commands
+- [Configuration Reference](../configuration-reference) - Available configuration options
+- Laravel's event system and service container
+
+## Getting Started
+
+Choose a topic based on your needs:
+
+1. **Understanding the System:** Start with [Architecture](architecture)
+2. **Integration & Automation:** Explore [Events](events)
+3. **Monitoring & Observability:** Set up [Prometheus](prometheus)
+4. **Scale & Optimize:** Review [Performance](performance)
diff --git a/docs/architecture.md b/docs/advanced-usage/architecture.md
similarity index 99%
rename from docs/architecture.md
rename to docs/advanced-usage/architecture.md
index 98708b9..43ee3f4 100644
--- a/docs/architecture.md
+++ b/docs/advanced-usage/architecture.md
@@ -1,3 +1,9 @@
+---
+title: "Architecture Overview"
+description: "Deep dive into how Laravel Queue Metrics works internally"
+weight: 31
+---
+
# Architecture Overview
Understanding how Laravel Queue Metrics works under the hood.
diff --git a/docs/events.md b/docs/advanced-usage/events.md
similarity index 98%
rename from docs/events.md
rename to docs/advanced-usage/events.md
index 126e8bc..8c7a716 100644
--- a/docs/events.md
+++ b/docs/advanced-usage/events.md
@@ -1,6 +1,12 @@
-# Events
+---
+title: "Events System"
+description: "React to queue metrics changes and lifecycle events in your application"
+weight: 32
+---
-Laravel Queue Metrics dispatches domain events that allow you to react to significant occurrences in your queue system. Unlike [Hooks](hooks.md) which transform data, events are notifications that enable integrations, alerts, and side-effects.
+# Events System
+
+Laravel Queue Metrics dispatches domain events that allow you to react to significant occurrences in your queue system. Events are notifications that enable integrations, alerts, and side-effects.
## Overview
diff --git a/docs/performance.md b/docs/advanced-usage/performance.md
similarity index 98%
rename from docs/performance.md
rename to docs/advanced-usage/performance.md
index 39aa518..d4b4235 100644
--- a/docs/performance.md
+++ b/docs/advanced-usage/performance.md
@@ -1,3 +1,9 @@
+---
+title: "Performance Tuning"
+description: "Optimize Laravel Queue Metrics for high-throughput systems and resource efficiency"
+weight: 34
+---
+
# Performance Tuning
Optimize Laravel Queue Metrics for your workload and scale.
diff --git a/docs/prometheus.md b/docs/advanced-usage/prometheus.md
similarity index 98%
rename from docs/prometheus.md
rename to docs/advanced-usage/prometheus.md
index 2cabe88..28f52ad 100644
--- a/docs/prometheus.md
+++ b/docs/advanced-usage/prometheus.md
@@ -1,3 +1,9 @@
+---
+title: "Prometheus Integration"
+description: "Export queue metrics to Prometheus for centralized monitoring with Grafana dashboards"
+weight: 33
+---
+
# Prometheus Integration
Laravel Queue Metrics provides native Prometheus metrics export for monitoring and alerting.
diff --git a/docs/basic-usage/_index.md b/docs/basic-usage/_index.md
new file mode 100644
index 0000000..5ad0e45
--- /dev/null
+++ b/docs/basic-usage/_index.md
@@ -0,0 +1,68 @@
+---
+title: "Basic Usage"
+description: "Essential features and interfaces for accessing queue metrics"
+weight: 10
+---
+
+# Basic Usage
+
+This section covers the fundamental ways to access and work with Laravel Queue Metrics in your application.
+
+## Available Interfaces
+
+Laravel Queue Metrics provides three primary interfaces for accessing metrics:
+
+### Facade API
+
+The recommended way to access metrics programmatically in your PHP code. The `QueueMetrics` facade provides a clean, type-safe interface with full IDE autocomplete support.
+
+**Best for:** Background jobs, scheduled commands, application logic
+
+**Learn more:** [Facade API](facade-api)
+
+### HTTP API
+
+RESTful endpoints for accessing metrics via HTTP requests. Perfect for dashboards, monitoring tools, and external integrations.
+
+**Best for:** Frontend dashboards, external monitoring, API integrations
+
+**Learn more:** [HTTP API Endpoints](api-endpoints)
+
+### Artisan Commands
+
+Command-line tools for metrics management, maintenance, and debugging during development.
+
+**Best for:** Development, debugging, scheduled maintenance tasks
+
+**Learn more:** [Artisan Commands](artisan-commands)
+
+## Quick Examples
+
+### Get Job Metrics
+
+```php
+use PHPeek\LaravelQueueMetrics\Facades\QueueMetrics;
+
+$metrics = QueueMetrics::getJobMetrics(\App\Jobs\ProcessOrder::class);
+echo "Success rate: {$metrics->successRate}%\n";
+```
+
+### Check Queue Health
+
+```bash
+curl http://your-app.test/queue-metrics/queues/default
+```
+
+### View Active Workers
+
+```bash
+php artisan queue-metrics:workers
+```
+
+## Next Steps
+
+Once you're familiar with the basic interfaces, explore:
+
+- [Architecture](../advanced-usage/architecture) - Understand how metrics are collected
+- [Events](../advanced-usage/events) - React to metrics changes
+- [Performance Tuning](../advanced-usage/performance) - Optimize for your workload
diff --git a/docs/api-endpoints.md b/docs/basic-usage/api-endpoints.md
similarity index 99%
rename from docs/api-endpoints.md
rename to docs/basic-usage/api-endpoints.md
index 280240a..4a13a2e 100644
--- a/docs/api-endpoints.md
+++ b/docs/basic-usage/api-endpoints.md
@@ -1,4 +1,10 @@
-# API Endpoints
+---
+title: "HTTP API Endpoints"
+description: "RESTful HTTP endpoints for accessing queue metrics via HTTP requests"
+weight: 11
+---
+
+# HTTP API Endpoints
Complete HTTP API reference for Laravel Queue Metrics.
diff --git a/docs/artisan-commands.md b/docs/basic-usage/artisan-commands.md
similarity index 92%
rename from docs/artisan-commands.md
rename to docs/basic-usage/artisan-commands.md
index a83b4ac..9cf34f8 100644
--- a/docs/artisan-commands.md
+++ b/docs/basic-usage/artisan-commands.md
@@ -1,3 +1,9 @@
+---
+title: "Artisan Commands"
+description: "Command-line tools for queue metrics management and maintenance"
+weight: 12
+---
+
# Artisan Commands
Laravel Queue Metrics provides Artisan commands for maintenance and monitoring.
diff --git a/docs/facade-api.md b/docs/basic-usage/facade-api.md
similarity index 98%
rename from docs/facade-api.md
rename to docs/basic-usage/facade-api.md
index 2e52be6..5ad6b7e 100644
--- a/docs/facade-api.md
+++ b/docs/basic-usage/facade-api.md
@@ -1,3 +1,9 @@
+---
+title: "Facade API"
+description: "PHP facade interface for programmatic access to queue metrics"
+weight: 10
+---
+
# Facade API
The `QueueMetrics` facade provides a clean, developer-friendly interface for accessing queue metrics. It's the recommended way to interact with the package programmatically.
@@ -486,5 +492,5 @@ function analyzeJob(string $jobClass): array
## Next Steps
-- See [API Documentation](api-endpoints.md) for HTTP endpoints
-- Learn about [Events](events.md) for reacting to metrics
+- See [HTTP API](api-endpoints) for REST endpoints
+- Learn about [Events](../advanced-usage/events) for reacting to metrics
diff --git a/docs/configuration-reference.md b/docs/configuration-reference.md
index df6eb4e..b4472b0 100644
--- a/docs/configuration-reference.md
+++ b/docs/configuration-reference.md
@@ -1,3 +1,9 @@
+---
+title: "Configuration Reference"
+description: "Complete configuration options and environment variables for Laravel Queue Metrics"
+weight: 90
+---
+
# Configuration Reference
Complete configuration options for Laravel Queue Metrics.
@@ -181,5 +187,5 @@ QUEUE_METRICS_BASELINE_DEVIATION_THRESHOLD=2.0
## Next Steps
-- [API Endpoints](api-endpoints.md) - HTTP access to metrics
-- [Prometheus](prometheus.md) - Monitoring integration
+- [API Endpoints](basic-usage/api-endpoints) - HTTP access to metrics
+- [Prometheus](advanced-usage/prometheus) - Monitoring integration
diff --git a/docs/installation.md b/docs/installation.md
index 01d4a42..39ee88e 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -1,3 +1,9 @@
+---
+title: "Installation"
+description: "Complete installation guide for Laravel Queue Metrics with Redis and database storage options"
+weight: 2
+---
+
# Installation
Get Laravel Queue Metrics up and running in your Laravel application.
@@ -96,7 +102,7 @@ This creates `config/queue-metrics.php` where you can customize:
- Performance settings
- Worker heartbeat intervals
-See [Configuration Reference](configuration-reference.md) for all options.
+See [Configuration Reference](configuration-reference) for all options.
## Verification
@@ -229,7 +235,7 @@ protected $listen = [
];
```
-See [Events](events.md) for detailed usage.
+See [Events](advanced-usage/events) for detailed usage.
### 4. Register Hooks (Optional)
@@ -250,7 +256,7 @@ public function boot(): void
}
```
-See [Hooks](hooks.md) for detailed usage.
+See Hooks documentation for detailed usage.
## Common Issues
@@ -426,8 +432,7 @@ php artisan route:clear
## Next Steps
-- [Quick Start Guide](quickstart.md) - Start using the package
-- [Configuration Reference](configuration-reference.md) - Customize behavior
-- [Facade API](facade-api.md) - Learn the developer interface
-- [Hooks](hooks.md) - Add custom data enrichment
-- [Events](events.md) - React to metrics events
+- [Quick Start Guide](quickstart) - Start using the package
+- [Configuration Reference](configuration-reference) - Customize behavior
+- [Facade API](basic-usage/facade-api) - Learn the developer interface
+- [Events](advanced-usage/events) - React to metrics events
diff --git a/docs/introduction.md b/docs/introduction.md
new file mode 100644
index 0000000..b183bb8
--- /dev/null
+++ b/docs/introduction.md
@@ -0,0 +1,74 @@
+---
+title: "Introduction"
+description: "Comprehensive queue monitoring and metrics collection for Laravel applications"
+weight: 1
+---
+
+# Laravel Queue Metrics
+
+Welcome to Laravel Queue Metrics - a comprehensive package for queue monitoring and metrics collection in Laravel applications.
+
+## What is Laravel Queue Metrics?
+
+Laravel Queue Metrics provides deep visibility into your Laravel queue operations, helping you monitor performance, identify bottlenecks, and optimize your background job processing.
+
+### Key Features
+
+- **Real-time Metrics**: Track job execution times, success rates, and failure patterns
+- **Queue Health Monitoring**: Monitor queue sizes, worker status, and system resources
+- **Multiple Storage Backends**: Choose between Redis (recommended) or database storage
+- **Prometheus Integration**: Export metrics for monitoring with Prometheus and Grafana
+- **Flexible API**: HTTP endpoints and PHP facade for programmatic access
+- **Event System**: React to metrics changes and integrate with your application
+- **Performance Optimized**: Minimal overhead with efficient data structures
+
+## Getting Started
+
+Choose your path based on your needs:
+
+### Quick Start (5 Minutes)
+
+Want to get up and running immediately? Follow the [Quick Start Guide](quickstart) to install and start collecting metrics in minutes.
+
+### Complete Installation
+
+For detailed installation instructions including storage backend selection and configuration options, see the [Installation Guide](installation).
+
+### Configuration
+
+Need to customize behavior? Check the [Configuration Reference](configuration-reference) for all available options.
+
+## Core Features
+
+### Basic Usage
+
+Learn how to access metrics and integrate with your application:
+
+- [Facade API](basic-usage/facade-api) - Primary developer interface for programmatic access
+- [HTTP API](basic-usage/api-endpoints) - REST endpoints for metrics retrieval
+- [Artisan Commands](basic-usage/artisan-commands) - CLI tools for metrics management
+
+### Advanced Features
+
+Deep dive into advanced capabilities:
+
+- [Architecture Overview](advanced-usage/architecture) - Understand how the package works internally
+- [Events System](advanced-usage/events) - React to metrics changes and lifecycle events
+- [Prometheus Integration](advanced-usage/prometheus) - Export metrics for monitoring infrastructure
+- [Performance Tuning](advanced-usage/performance) - Optimize for your specific workload
+
+## Need Help?
+
+- **Quick answers**: See the [Quick Start Guide](quickstart)
+- **Configuration issues**: Check the [Configuration Reference](configuration-reference)
+- **Found a bug?**: Report it on [GitHub Issues](https://github.com/gophpeek/laravel-queue-metrics/issues)
+
+## Package Requirements
+
+- **PHP**: 8.3 or higher
+- **Laravel**: 11.0 or higher (12.0+ recommended)
+- **Storage**: Redis (recommended) or Database
+
+## License
+
+Laravel Queue Metrics is open-source software licensed under the MIT license.
diff --git a/docs/quickstart.md b/docs/quickstart.md
index ecdfce4..d79a99b 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -1,3 +1,9 @@
+---
+title: "Quick Start"
+description: "Get up and running with Laravel Queue Metrics in 5 minutes"
+weight: 3
+---
+
# Quick Start
Get up and running with Laravel Queue Metrics in 5 minutes.
@@ -352,12 +358,10 @@ DB::table('queue_job_metrics')->get();
## Next Steps
📚 **Learn More:**
-- [Hooks System](hooks.md) - Transform metrics data
-- [Events](events.md) - React to metrics changes
-- [Facade API](facade-api.md) - Complete API reference
-- [Configuration](configuration-reference.md) - All config options
+- [Events](advanced-usage/events) - React to metrics changes
+- [Facade API](basic-usage/facade-api) - Complete API reference
+- [Configuration](configuration-reference) - All config options
🏗️ **Advanced:**
-- [Architecture](architecture.md) - How it works
-- [Custom Storage Drivers](custom-storage-drivers.md) - Extend storage
-- [Performance Tuning](performance.md) - Optimize for scale
+- [Architecture](advanced-usage/architecture) - How it works
+- [Performance Tuning](advanced-usage/performance) - Optimize for scale
diff --git a/src/Services/RedisKeyScannerService.php b/src/Services/RedisKeyScannerService.php
index dc5a40c..a3a04ce 100644
--- a/src/Services/RedisKeyScannerService.php
+++ b/src/Services/RedisKeyScannerService.php
@@ -10,19 +10,28 @@
* Service for scanning and parsing Redis keys to discover entities.
* Provides proper dependency injection and testability.
*/
-final readonly class RedisKeyScannerService
+final class RedisKeyScannerService
{
- private string $fullPrefix;
+ private ?string $fullPrefix = null;
public function __construct(
- private RedisMetricsStore $redisStore,
- ) {
- // Cache the full prefix calculation
- /** @var string $redisConnection */
- $redisConnection = config('queue-metrics.storage.connection', 'default');
- $connectionPrefix = app('redis')->connection($redisConnection)->_prefix('');
- $ourPrefix = $this->redisStore->key('');
- $this->fullPrefix = $connectionPrefix.$ourPrefix;
+ private readonly RedisMetricsStore $redisStore,
+ ) {}
+
+ /**
+ * Get the full Redis key prefix (lazy loaded to avoid connection during boot).
+ */
+ private function getFullPrefix(): string
+ {
+ if ($this->fullPrefix === null) {
+ /** @var string $redisConnection */
+ $redisConnection = config('queue-metrics.storage.connection', 'default');
+ $connectionPrefix = app('redis')->connection($redisConnection)->_prefix('');
+ $ourPrefix = $this->redisStore->key('');
+ $this->fullPrefix = $connectionPrefix.$ourPrefix;
+ }
+
+ return $this->fullPrefix;
}
/**
@@ -46,15 +55,17 @@ public function scanAndParseKeys(string $jobsPattern, string $queuedPattern, cal
return [];
}
+ $fullPrefix = $this->getFullPrefix();
+
// Extract unique entities from all keys using the provided parser
$discovered = [];
foreach ($allKeys as $key) {
// Remove the full prefix first
- if (! str_starts_with($key, $this->fullPrefix)) {
+ if (! str_starts_with($key, $fullPrefix)) {
continue;
}
- $keyWithoutPrefix = substr($key, strlen($this->fullPrefix));
+ $keyWithoutPrefix = substr($key, strlen($fullPrefix));
// Parse the key using the provided callable
$entity = $keyParser($keyWithoutPrefix);
diff --git a/src/Support/RedisMetricsStore.php b/src/Support/RedisMetricsStore.php
index 71e9d56..224e581 100644
--- a/src/Support/RedisMetricsStore.php
+++ b/src/Support/RedisMetricsStore.php
@@ -135,7 +135,9 @@ public function addToSortedSet(string $key, array $membersWithScores, ?int $ttl
*/
public function getSortedSetByRank(string $key, int $start, int $stop): array
{
- return $this->getRedis()->zrange($key, $start, $stop);
+ $result = $this->getRedis()->zrange($key, $start, $stop);
+
+ return is_array($result) ? $result : [];
}
/**
@@ -143,7 +145,9 @@ public function getSortedSetByRank(string $key, int $start, int $stop): array
*/
public function getSortedSetByScore(string $key, string $min, string $max): array
{
- return $this->getRedis()->zrangebyscore($key, $min, $max);
+ $result = $this->getRedis()->zrangebyscore($key, $min, $max);
+
+ return is_array($result) ? $result : [];
}
public function countSortedSetByScore(string $key, string $min, string $max): int
@@ -172,7 +176,7 @@ public function removeFromSortedSet(string $key, string $member): void
public function addToSet(string $key, array $members): void
{
if (! empty($members)) {
- $this->getRedis()->sadd($key, $members);
+ $this->getRedis()->sadd($key, ...$members);
}
}
@@ -181,7 +185,9 @@ public function addToSet(string $key, array $members): void
*/
public function getSetMembers(string $key): array
{
- return $this->getRedis()->smembers($key);
+ $result = $this->getRedis()->smembers($key);
+
+ return is_array($result) ? $result : [];
}
/**
@@ -190,7 +196,7 @@ public function getSetMembers(string $key): array
public function removeFromSet(string $key, array $members): void
{
if (! empty($members)) {
- $this->getRedis()->srem($key, $members);
+ $this->getRedis()->srem($key, ...$members);
}
}
diff --git a/tests/ArchTest.php b/tests/ArchTest.php
index 87fb64c..d32b17e 100644
--- a/tests/ArchTest.php
+++ b/tests/ArchTest.php
@@ -2,4 +2,5 @@
arch('it will not use debugging functions')
->expect(['dd', 'dump', 'ray'])
- ->each->not->toBeUsed();
+ ->each->not->toBeUsed()
+ ->group('arch', 'functional');
diff --git a/tests/Feature/ChildProcessTrackingTest.php b/tests/Feature/ChildProcessTrackingTest.php
index 3a9db55..9e4fba7 100644
--- a/tests/Feature/ChildProcessTrackingTest.php
+++ b/tests/Feature/ChildProcessTrackingTest.php
@@ -59,7 +59,8 @@
expect($stats->processCount)->toBeGreaterThanOrEqual(1);
unset($data);
-})->skip(PHP_OS_FAMILY === 'Windows', 'Child process tracking not supported on Windows');
+})->skip(PHP_OS_FAMILY === 'Windows', 'Child process tracking not supported on Windows')
+ ->group('functional');
it('measures accurate delta between job start and completion', function () {
$trackerId = 'delta_accuracy_test_'.uniqid();
@@ -96,7 +97,7 @@
// CPU usage should be measurable for computational work
$cpuPercent = $stats->delta->cpuUsagePercentage();
expect($cpuPercent)->toBeGreaterThanOrEqual(0.0);
-});
+})->group('functional');
it('tracks peak memory correctly for memory-intensive jobs', function () {
$trackerId = 'peak_memory_job_'.uniqid();
@@ -138,7 +139,7 @@
// Current memory at end should be less than peak (after freeing $largeData)
expect($stats->current->memoryRssBytes)->toBeLessThanOrEqual($stats->peak->memoryRssBytes);
-});
+})->group('functional');
it('provides process resource usage compatible with JobProcessedListener calculations', function () {
$trackerId = 'listener_compat_test_'.uniqid();
@@ -175,4 +176,4 @@
expect($cpuTimeMs)->toBeLessThan(60000.0); // Sanity check (< 1 minute)
unset($data);
-});
+})->group('functional');
diff --git a/tests/Feature/EventListenersTest.php b/tests/Feature/EventListenersTest.php
index b64294a..6e466d5 100644
--- a/tests/Feature/EventListenersTest.php
+++ b/tests/Feature/EventListenersTest.php
@@ -6,24 +6,31 @@
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
-use Orchestra\Testbench\TestCase;
-use PHPeek\LaravelQueueMetrics\LaravelQueueMetricsServiceProvider;
use PHPeek\LaravelQueueMetrics\Repositories\Contracts\JobMetricsRepository;
use PHPeek\LaravelQueueMetrics\Tests\Feature\Support\TestJob;
+use PHPeek\LaravelQueueMetrics\Tests\TestCase;
+/**
+ * @group redis
+ */
final class EventListenersTest extends TestCase
{
use RefreshDatabase;
- protected function getPackageProviders($app): array
+ protected function setUp(): void
{
- return [
- LaravelQueueMetricsServiceProvider::class,
- ];
+ // Skip entire test class if Redis is not available
+ if (! getenv('REDIS_AVAILABLE')) {
+ $this->markTestSkipped('Requires Redis - run with redis group');
+ }
+
+ parent::setUp();
}
protected function defineEnvironment($app): void
{
+ parent::defineEnvironment($app);
+
$app['config']->set('queue.default', 'sync');
$app['config']->set('queue-metrics.enabled', true);
$app['config']->set('queue-metrics.storage.driver', 'redis');
@@ -66,6 +73,8 @@ public function test_job_processing_event_records_start(): void
public function test_metrics_are_recorded_with_sync_queue(): void
{
+ $this->markTestSkipped('Requires Redis connection - run with redis group');
+
// This test verifies that metrics ARE recorded even with sync queue
config(['queue.default' => 'sync']);
diff --git a/tests/Performance/BaselineCalculationBenchmarkTest.php b/tests/Performance/BaselineCalculationBenchmarkTest.php
index 6afc23a..773a321 100644
--- a/tests/Performance/BaselineCalculationBenchmarkTest.php
+++ b/tests/Performance/BaselineCalculationBenchmarkTest.php
@@ -10,6 +10,12 @@
* Performance benchmark tests for critical operations.
* These tests ensure performance doesn't regress.
*/
+beforeEach(function () {
+ if (! getenv('REDIS_AVAILABLE')) {
+ $this->markTestSkipped('Requires Redis - run with redis group');
+ }
+});
+
test('baseline calculation completes within acceptable time', function () {
$action = app(CalculateBaselinesAction::class);
$queueRepository = app(QueueMetricsRepository::class);
@@ -23,7 +29,7 @@
// Assert: Should complete within 5 seconds even with 1000 samples
expect($duration)->toBeLessThan(5.0);
-})->group('performance', 'slow');
+})->group('performance', 'slow', 'redis', 'functional');
test('batch baseline fetching is faster than sequential', function () {
$baselineRepository = app(BaselineRepository::class);
@@ -51,7 +57,7 @@
// Assert: Batch should not be significantly slower (allow 2x tolerance for overhead)
// With empty data, batch operations may have pipeline overhead
expect($batchDuration)->toBeLessThan($sequentialDuration * 2);
-})->group('performance', 'slow');
+})->group('performance', 'slow', 'redis', 'functional');
test('key scanning performance is acceptable for large datasets', function () {
$keyScanner = app(\PHPeek\LaravelQueueMetrics\Services\RedisKeyScannerService::class);
@@ -75,7 +81,7 @@
// Assert: Scanning should complete within 2 seconds
expect($duration)->toBeLessThan(2.0);
-})->group('performance');
+})->group('performance', 'redis', 'functional');
test('overview query aggregation completes within acceptable time', function () {
$overviewService = app(\PHPeek\LaravelQueueMetrics\Services\OverviewQueryService::class);
@@ -91,7 +97,7 @@
expect($duration)->toBeLessThan(3.0);
expect($overview)->toBeArray();
expect($overview)->toHaveKeys(['queues', 'jobs', 'servers', 'workers', 'baselines', 'metadata']);
-})->group('performance', 'slow');
+})->group('performance', 'slow', 'redis', 'functional');
test('redis transaction is faster or same speed as pipeline for critical mutations', function () {
$jobMetricsRepository = app(\PHPeek\LaravelQueueMetrics\Repositories\Contracts\JobMetricsRepository::class);
@@ -127,7 +133,7 @@
// Assert: 10 transactions should complete within 1 second
expect($duration)->toBeLessThan(1.0);
-})->group('performance');
+})->group('performance', 'redis', 'functional');
test('memory usage stays reasonable during large batch operations', function () {
$overviewService = app(\PHPeek\LaravelQueueMetrics\Services\OverviewQueryService::class);
@@ -141,4 +147,4 @@
// Assert: Memory increase should be less than 50MB for overview query
expect($memoryIncrease)->toBeLessThan(50);
-})->group('performance');
+})->group('performance', 'redis', 'functional');
diff --git a/tests/Pest.php b/tests/Pest.php
index 5ef75c0..0a6ccf4 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -1,15 +1,5 @@
in(__DIR__);
-
-// Mock HookManager globally to make hooks no-op during tests
-beforeEach(function () {
- $hookManager = Mockery::mock(HookManager::class);
- $hookManager->shouldReceive('execute')
- ->andReturnUsing(fn ($context, $payload) => $payload);
-
- $this->app->instance(HookManager::class, $hookManager);
-});
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 60b220d..6efb6a7 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -29,6 +29,9 @@ public function getEnvironmentSetUp($app)
{
config()->set('database.default', 'testing');
+ // Disable queue metrics during tests to avoid Redis connection attempts
+ config()->set('queue-metrics.enabled', false);
+
/*
foreach (\Illuminate\Support\Facades\File::allFiles(__DIR__ . '/../database/migrations') as $migration) {
(include $migration->getRealPath())->up();
diff --git a/tests/Unit/Actions/RecordJobCompletionActionTest.php b/tests/Unit/Actions/RecordJobCompletionActionTest.php
index b6896a8..28cf525 100644
--- a/tests/Unit/Actions/RecordJobCompletionActionTest.php
+++ b/tests/Unit/Actions/RecordJobCompletionActionTest.php
@@ -43,7 +43,7 @@
memoryMb: 25.3,
cpuTimeMs: 300.0,
);
-});
+})->group('functional');
it('records job completion with zero CPU time by default', function () {
$this->repository->shouldReceive('recordCompletion')
@@ -68,7 +68,7 @@
durationMs: 500.0,
memoryMb: 10.5,
);
-});
+})->group('functional');
it('does nothing when metrics are disabled', function () {
config(['queue-metrics.enabled' => false]);
@@ -83,7 +83,7 @@
durationMs: 1000.0,
memoryMb: 20.0,
);
-});
+})->group('functional');
it('handles very short duration jobs', function () {
$this->repository->shouldReceive('recordCompletion')
@@ -109,7 +109,7 @@
memoryMb: 5.2,
cpuTimeMs: 0.1,
);
-});
+})->group('functional');
it('handles very long duration jobs', function () {
$this->repository->shouldReceive('recordCompletion')
@@ -135,7 +135,7 @@
memoryMb: 512.0,
cpuTimeMs: 3000000.0,
);
-});
+})->group('functional');
it('records completion time at execution moment', function () {
Carbon::setTestNow('2024-01-15 14:45:30');
@@ -163,7 +163,7 @@
memoryMb: 30.0,
cpuTimeMs: 500.0,
);
-});
+})->group('functional');
it('handles different queue connections', function () {
$this->repository->shouldReceive('recordCompletion')
@@ -189,4 +189,4 @@
memoryMb: 15.5,
cpuTimeMs: 250.0,
);
-});
+})->group('functional');
diff --git a/tests/Unit/Actions/RecordJobFailureActionTest.php b/tests/Unit/Actions/RecordJobFailureActionTest.php
index aa04ceb..5315418 100644
--- a/tests/Unit/Actions/RecordJobFailureActionTest.php
+++ b/tests/Unit/Actions/RecordJobFailureActionTest.php
@@ -41,7 +41,7 @@
queue: 'default',
exception: $exception,
);
-});
+})->group('functional');
it('does nothing when metrics are disabled', function () {
config(['queue-metrics.enabled' => false]);
@@ -57,7 +57,7 @@
queue: 'default',
exception: $exception,
);
-});
+})->group('functional');
it('handles different exception types', function () {
$exception = new InvalidArgumentException('Invalid input data');
@@ -81,7 +81,7 @@
queue: 'validation',
exception: $exception,
);
-});
+})->group('functional');
it('records failure time at execution moment', function () {
Carbon::setTestNow('2024-01-15 14:45:30');
@@ -107,7 +107,7 @@
queue: 'default',
exception: $exception,
);
-});
+})->group('functional');
it('handles exceptions with special characters in message', function () {
$exception = new RuntimeException("Error: 'string' with \"quotes\" & symbols!");
@@ -131,7 +131,7 @@
queue: 'default',
exception: $exception,
);
-});
+})->group('functional');
it('includes file path and line number in exception message', function () {
$exception = new RuntimeException('Critical error');
@@ -159,7 +159,7 @@
queue: 'default',
exception: $exception,
);
-});
+})->group('functional');
it('handles different queue connections', function () {
$exception = new RuntimeException('Database error');
@@ -183,4 +183,4 @@
queue: 'sync',
exception: $exception,
);
-});
+})->group('functional');
diff --git a/tests/Unit/Actions/RecordJobStartActionTest.php b/tests/Unit/Actions/RecordJobStartActionTest.php
index c54e8f6..5238191 100644
--- a/tests/Unit/Actions/RecordJobStartActionTest.php
+++ b/tests/Unit/Actions/RecordJobStartActionTest.php
@@ -42,7 +42,7 @@
connection: 'redis',
queue: 'default',
);
-});
+})->group('functional');
it('does nothing when metrics are disabled', function () {
config(['queue-metrics.enabled' => false]);
@@ -55,7 +55,7 @@
connection: 'redis',
queue: 'default',
);
-});
+})->group('functional');
it('handles different queue connections', function () {
$this->queueRepository->shouldReceive('markQueueDiscovered')
@@ -78,7 +78,7 @@
connection: 'database',
queue: 'emails',
);
-});
+})->group('functional');
it('records start time at execution moment', function () {
Carbon::setTestNow('2024-01-15 14:45:30');
@@ -103,7 +103,7 @@
connection: 'redis',
queue: 'reports',
);
-});
+})->group('functional');
it('handles job IDs with special characters', function () {
$this->queueRepository->shouldReceive('markQueueDiscovered')
@@ -126,4 +126,4 @@
connection: 'redis',
queue: 'default',
);
-});
+})->group('functional');
diff --git a/tests/Unit/Actions/TransitionWorkerStateActionTest.php b/tests/Unit/Actions/TransitionWorkerStateActionTest.php
index 5f278b9..5eb70f4 100644
--- a/tests/Unit/Actions/TransitionWorkerStateActionTest.php
+++ b/tests/Unit/Actions/TransitionWorkerStateActionTest.php
@@ -33,7 +33,7 @@
workerId: 'worker-123',
newState: WorkerState::BUSY,
);
-});
+})->group('functional');
it('transitions worker to new state with specific transition time', function () {
$transitionTime = Carbon::parse('2024-01-15 14:00:00');
@@ -51,7 +51,7 @@
newState: WorkerState::IDLE,
transitionTime: $transitionTime,
);
-});
+})->group('functional');
it('does nothing when metrics are disabled', function () {
config(['queue-metrics.enabled' => false]);
@@ -62,7 +62,7 @@
workerId: 'worker-123',
newState: WorkerState::BUSY,
);
-});
+})->group('functional');
it('handles transition to idle state', function () {
$this->repository->shouldReceive('transitionState')
@@ -77,7 +77,7 @@
workerId: 'worker-789',
newState: WorkerState::IDLE,
);
-});
+})->group('functional');
it('handles transition to busy state', function () {
$this->repository->shouldReceive('transitionState')
@@ -92,7 +92,7 @@
workerId: 'worker-abc',
newState: WorkerState::BUSY,
);
-});
+})->group('functional');
it('handles transition to terminated state', function () {
$this->repository->shouldReceive('transitionState')
@@ -107,7 +107,7 @@
workerId: 'worker-xyz',
newState: WorkerState::STOPPED,
);
-});
+})->group('functional');
it('uses current time when transition time is not provided', function () {
Carbon::setTestNow('2024-01-15 16:45:30');
@@ -127,7 +127,7 @@
workerId: 'worker-test',
newState: WorkerState::BUSY,
);
-});
+})->group('functional');
it('handles worker IDs with special characters', function () {
$this->repository->shouldReceive('transitionState')
@@ -142,4 +142,4 @@
workerId: 'worker-123-abc-xyz',
newState: WorkerState::IDLE,
);
-});
+})->group('functional');
diff --git a/tests/Unit/Config/QueueMetricsConfigTest.php b/tests/Unit/Config/QueueMetricsConfigTest.php
index 2829048..da35b8a 100644
--- a/tests/Unit/Config/QueueMetricsConfigTest.php
+++ b/tests/Unit/Config/QueueMetricsConfigTest.php
@@ -60,7 +60,7 @@
'job_history' => 604800,
'worker_history' => 86400,
]);
-});
+})->group('functional');
it('has storage config instance', function () {
$config = QueueMetricsConfig::fromConfig();
@@ -68,20 +68,20 @@
expect($config->storage->driver)->toBe('redis')
->and($config->storage->connection)->toBe('default')
->and($config->storage->prefix)->toBe('queue_metrics');
-});
+})->group('functional');
it('gets prometheus namespace', function () {
$config = QueueMetricsConfig::fromConfig();
expect($config->getPrometheusNamespace())->toBe('laravel_queue');
-});
+})->group('functional');
it('returns default prometheus namespace when missing', function () {
config()->set('queue-metrics.prometheus', []);
$config = QueueMetricsConfig::fromConfig();
expect($config->getPrometheusNamespace())->toBe('laravel_queue');
-});
+})->group('functional');
it('handles missing config with defaults', function () {
config()->set('queue-metrics', []);
@@ -89,31 +89,31 @@
expect($config->enabled)->toBeTrue()
->and($config->storage)->toBeInstanceOf(StorageConfig::class);
-});
+})->group('functional');
it('respects disabled state', function () {
config()->set('queue-metrics.enabled', false);
$config = QueueMetricsConfig::fromConfig();
expect($config->enabled)->toBeFalse();
-});
+})->group('functional');
it('is readonly', function () {
$config = QueueMetricsConfig::fromConfig();
expect(fn () => $config->enabled = false)
->toThrow(Error::class);
-});
+})->group('functional');
it('gets api middleware array', function () {
$config = QueueMetricsConfig::fromConfig();
expect($config->api['middleware'])->toBe(['api']);
-});
+})->group('functional');
it('gets worker heartbeat interval', function () {
$config = QueueMetricsConfig::fromConfig();
expect($config->workerHeartbeat['interval'])->toBe(30)
->and($config->workerHeartbeat['timeout'])->toBe(90);
-});
+})->group('functional');
diff --git a/tests/Unit/Config/StorageConfigTest.php b/tests/Unit/Config/StorageConfigTest.php
index 58ecd72..1218194 100644
--- a/tests/Unit/Config/StorageConfigTest.php
+++ b/tests/Unit/Config/StorageConfigTest.php
@@ -24,7 +24,7 @@
'aggregated' => 86400,
'baseline' => 604800,
]);
-});
+})->group('functional');
it('creates config with defaults when values missing', function () {
$config = StorageConfig::fromArray([]);
@@ -37,7 +37,7 @@
'aggregated' => 604800,
'baseline' => 2592000,
]);
-});
+})->group('functional');
it('gets ttl for known type', function () {
$config = StorageConfig::fromArray([
@@ -51,7 +51,7 @@
expect($config->getTtl('raw'))->toBe(1800)
->and($config->getTtl('aggregated'))->toBe(86400)
->and($config->getTtl('baseline'))->toBe(604800);
-});
+})->group('functional');
it('returns default ttl for unknown type', function () {
$config = StorageConfig::fromArray([
@@ -61,7 +61,7 @@
]);
expect($config->getTtl('unknown'))->toBe(3600);
-});
+})->group('functional');
it('handles database driver', function () {
$config = StorageConfig::fromArray([
@@ -71,11 +71,11 @@
expect($config->driver)->toBe('database')
->and($config->connection)->toBe('mysql');
-});
+})->group('functional');
it('is readonly', function () {
$config = StorageConfig::fromArray([]);
expect(fn () => $config->driver = 'changed')
->toThrow(Error::class);
-});
+})->group('functional');
diff --git a/tests/Unit/Events/BaselineRecalculatedTest.php b/tests/Unit/Events/BaselineRecalculatedTest.php
index 7193b8c..57c4778 100644
--- a/tests/Unit/Events/BaselineRecalculatedTest.php
+++ b/tests/Unit/Events/BaselineRecalculatedTest.php
@@ -42,7 +42,7 @@
&& $event->baseline->jobClass === 'App\Jobs\ProcessOrder'
&& $event->significantChange === true;
});
-});
+})->group('functional');
it('indicates significant change when baseline changes by more than 20%', function () {
$baseline = new BaselineData(
@@ -62,7 +62,7 @@
expect($event->significantChange)->toBeTrue()
->and($event->baseline->cpuPercentPerJob)->toBe(10.0)
->and($event->baseline->confidenceScore)->toBe(0.90);
-});
+})->group('functional');
it('indicates no significant change when baseline is stable', function () {
$baseline = new BaselineData(
@@ -81,7 +81,7 @@
expect($event->significantChange)->toBeFalse()
->and($event->baseline->confidenceScore)->toBe(1.0);
-});
+})->group('functional');
it('handles aggregated baseline without job class', function () {
$aggregatedBaseline = new BaselineData(
@@ -100,9 +100,9 @@
expect($event->baseline->jobClass)->toBe('')
->and($event->baseline->sampleCount)->toBe(500);
-});
+})->group('functional');
it('is dispatchable using trait', function () {
expect(class_uses(BaselineRecalculated::class))
->toContain('Illuminate\Foundation\Events\Dispatchable');
-});
+})->group('functional');
diff --git a/tests/Unit/Events/HealthScoreChangedTest.php b/tests/Unit/Events/HealthScoreChangedTest.php
index ab2f56e..a42337d 100644
--- a/tests/Unit/Events/HealthScoreChangedTest.php
+++ b/tests/Unit/Events/HealthScoreChangedTest.php
@@ -25,7 +25,7 @@
&& $event->previousScore === 70.0
&& $event->status === 'critical';
});
-});
+})->group('functional');
it('detects critical severity when score drops significantly', function () {
$event = new HealthScoreChanged(
@@ -39,7 +39,7 @@
expect($event->getSeverity())->toBe('critical')
->and($event->status)->toBe('critical')
->and($event->currentScore)->toBeLessThan(50.0);
-});
+})->group('functional');
it('detects warning severity when score changes moderately', function () {
$event = new HealthScoreChanged(
@@ -53,7 +53,7 @@
expect($event->getSeverity())->toBe('warning')
->and($event->status)->toBe('warning')
->and(abs($event->currentScore - $event->previousScore))->toBeGreaterThan(20.0);
-});
+})->group('functional');
it('detects info severity when score changes slightly', function () {
$event = new HealthScoreChanged(
@@ -66,7 +66,7 @@
expect($event->getSeverity())->toBe('info')
->and(abs($event->currentScore - $event->previousScore))->toBeLessThan(15.0);
-});
+})->group('functional');
it('detects normal severity when score is stable', function () {
$event = new HealthScoreChanged(
@@ -79,7 +79,7 @@
expect($event->getSeverity())->toBe('normal')
->and(abs($event->currentScore - $event->previousScore))->toBeLessThan(10.0);
-});
+})->group('functional');
it('handles healthy status transition', function () {
$event = new HealthScoreChanged(
@@ -93,7 +93,7 @@
expect($event->status)->toBe('healthy')
->and($event->currentScore)->toBeGreaterThan($event->previousScore)
->and($event->getSeverity())->toBe('critical'); // Large change = critical severity
-});
+})->group('functional');
it('handles warning status transition', function () {
$event = new HealthScoreChanged(
@@ -107,7 +107,7 @@
expect($event->status)->toBe('warning')
->and($event->currentScore)->toBeGreaterThan(50.0)
->and($event->currentScore)->toBeLessThan(80.0);
-});
+})->group('functional');
it('handles critical status transition', function () {
$event = new HealthScoreChanged(
@@ -121,9 +121,9 @@
expect($event->status)->toBe('critical')
->and($event->currentScore)->toBeLessThan(50.0)
->and($event->getSeverity())->toBe('critical');
-});
+})->group('functional');
it('is dispatchable using trait', function () {
expect(class_uses(HealthScoreChanged::class))
->toContain('Illuminate\Foundation\Events\Dispatchable');
-});
+})->group('functional');
diff --git a/tests/Unit/Events/MetricsRecordedTest.php b/tests/Unit/Events/MetricsRecordedTest.php
index 6831bbc..6750743 100644
--- a/tests/Unit/Events/MetricsRecordedTest.php
+++ b/tests/Unit/Events/MetricsRecordedTest.php
@@ -70,7 +70,7 @@
&& $event->metrics->queue === 'default'
&& $event->metrics->execution->totalProcessed === 95;
});
-});
+})->group('functional');
it('contains complete job metrics data', function () {
$metricsData = new JobMetricsData(
@@ -94,9 +94,9 @@
->and($event->metrics->duration->avg)->toBe(100.0)
->and($event->metrics->memory->peak)->toBe(45.0)
->and($event->metrics->throughput->perMinute)->toBe(5.0);
-});
+})->group('functional');
it('is dispatchable using trait', function () {
expect(class_uses(MetricsRecorded::class))
->toContain('Illuminate\Foundation\Events\Dispatchable');
-});
+})->group('functional');
diff --git a/tests/Unit/Events/QueueDepthThresholdExceededTest.php b/tests/Unit/Events/QueueDepthThresholdExceededTest.php
index 300ab08..bd12e14 100644
--- a/tests/Unit/Events/QueueDepthThresholdExceededTest.php
+++ b/tests/Unit/Events/QueueDepthThresholdExceededTest.php
@@ -36,7 +36,7 @@
&& $event->threshold === 100
&& $event->percentageOver === 50.0;
});
-});
+})->group('functional');
it('calculates percentage over threshold correctly', function () {
$depthData = new QueueDepthData(
@@ -54,7 +54,7 @@
expect($event->percentageOver)->toBe(100.0) // 100% over threshold
->and($event->depth->totalJobs())->toBe(200);
-});
+})->group('functional');
it('provides depth data for horizontal scaling decisions', function () {
$depthData = new QueueDepthData(
@@ -74,7 +74,7 @@
->and($event->depth->hasBacklog())->toBeTrue()
->and($event->depth->pendingJobs)->toBe(500)
->and($event->percentageOver)->toBe(460.0);
-});
+})->group('functional');
it('includes oldest job age information', function () {
$oldestAge = Carbon::now()->subHours(1);
@@ -94,9 +94,9 @@
expect($event->depth->secondsOldestPendingJob())->toBeGreaterThan(3500.0) // ~1 hour
->and($event->depth->oldestPendingJobAge)->toBe($oldestAge);
-});
+})->group('functional');
it('is dispatchable using trait', function () {
expect(class_uses(QueueDepthThresholdExceeded::class))
->toContain('Illuminate\Foundation\Events\Dispatchable');
-});
+})->group('functional');
diff --git a/tests/Unit/Events/WorkerEfficiencyChangedTest.php b/tests/Unit/Events/WorkerEfficiencyChangedTest.php
index d934af7..f922d9d 100644
--- a/tests/Unit/Events/WorkerEfficiencyChangedTest.php
+++ b/tests/Unit/Events/WorkerEfficiencyChangedTest.php
@@ -25,7 +25,7 @@
&& $event->activeWorkers === 8
&& $event->idleWorkers === 2;
});
-});
+})->group('functional');
it('recommends scale up when efficiency is high and no idle workers', function () {
$event = new WorkerEfficiencyChanged(
@@ -39,7 +39,7 @@
expect($event->getScalingRecommendation())->toBe('scale_up')
->and($event->currentEfficiency)->toBe(92.0)
->and($event->idleWorkers)->toBe(0);
-});
+})->group('functional');
it('recommends scale down when efficiency is low with many idle workers', function () {
$event = new WorkerEfficiencyChanged(
@@ -53,7 +53,7 @@
expect($event->getScalingRecommendation())->toBe('scale_down')
->and($event->currentEfficiency)->toBe(45.0)
->and($event->idleWorkers)->toBeGreaterThan(3);
-});
+})->group('functional');
it('recommends maintain when efficiency is in acceptable range', function () {
$event = new WorkerEfficiencyChanged(
@@ -67,7 +67,7 @@
expect($event->getScalingRecommendation())->toBe('maintain')
->and($event->currentEfficiency)->toBeGreaterThan(50.0)
->and($event->currentEfficiency)->toBeLessThan(90.0);
-});
+})->group('functional');
it('detects efficiency increase', function () {
$event = new WorkerEfficiencyChanged(
@@ -80,7 +80,7 @@
expect($event->currentEfficiency)->toBeGreaterThan($event->previousEfficiency)
->and($event->changePercentage)->toBe(15.0);
-});
+})->group('functional');
it('detects efficiency decrease', function () {
$event = new WorkerEfficiencyChanged(
@@ -93,9 +93,9 @@
expect($event->currentEfficiency)->toBeLessThan($event->previousEfficiency)
->and($event->changePercentage)->toBe(20.0);
-});
+})->group('functional');
it('is dispatchable using trait', function () {
expect(class_uses(WorkerEfficiencyChanged::class))
->toContain('Illuminate\Foundation\Events\Dispatchable');
-});
+})->group('functional');
diff --git a/tests/Unit/ProcessMetricsIntegrationTest.php b/tests/Unit/ProcessMetricsIntegrationTest.php
index 450471a..0c3e65e 100644
--- a/tests/Unit/ProcessMetricsIntegrationTest.php
+++ b/tests/Unit/ProcessMetricsIntegrationTest.php
@@ -70,7 +70,7 @@
// Cleanup
unset($data);
-});
+})->group('functional');
it('calculates CPU time correctly from delta metrics', function () {
$pid = getmypid();
@@ -96,7 +96,7 @@
expect($cpuTimeMs)->toBeGreaterThanOrEqual(0.0);
expect($cpuUsagePercent)->toBeGreaterThanOrEqual(0.0);
-});
+})->group('functional');
it('tracks peak memory during execution', function () {
$pid = getmypid();
@@ -129,7 +129,7 @@
$peakMemory = $stats->peak->memoryRssBytes;
expect($peakMemory)->toBeGreaterThanOrEqual($initialMemory);
-});
+})->group('functional');
it('handles process group snapshots with children', function () {
$pid = getmypid();
@@ -152,7 +152,7 @@
// Process group reading may not be supported on all systems
expect($groupResult->isFailure())->toBeTrue();
}
-});
+})->group('functional');
it('provides process count including children', function () {
$pid = getmypid();
@@ -177,4 +177,4 @@
// If we have child processes, count should be > 1
// Note: In test environment, we typically only have the parent process
expect($stats->processCount)->toBe(1);
-});
+})->group('functional');
diff --git a/tests/Unit/Services/PrometheusServiceTest.php b/tests/Unit/Services/PrometheusServiceTest.php
index f15a967..c1eec69 100644
--- a/tests/Unit/Services/PrometheusServiceTest.php
+++ b/tests/Unit/Services/PrometheusServiceTest.php
@@ -51,7 +51,7 @@
// Verify specific metric values with labels
expect($metrics)->toContain('test_namespace_queue_depth_pending{queue="default",connection="redis"} 80');
expect($metrics)->toContain('test_namespace_queue_depth_total{queue="default",connection="redis"} 100');
-});
+})->group('functional');
test('it exports job metrics with execution counters', function () {
config(['queue-metrics.prometheus.namespace' => 'test_namespace']);
@@ -91,7 +91,7 @@
// Verify success/failure rates are percentages (0-100 scale)
expect($metrics)->toContain('test_namespace_job_success_rate_percent{job="App\\\\Jobs\\\\ProcessOrder",queue="default",connection="redis"} 95');
expect($metrics)->toContain('test_namespace_job_failure_rate_percent{job="App\\\\Jobs\\\\ProcessOrder",queue="default",connection="redis"} 5');
-});
+})->group('functional');
test('it exports worker metrics', function () {
config(['queue-metrics.prometheus.namespace' => 'test_namespace']);
@@ -128,7 +128,7 @@
// Verify total jobs processed
expect($metrics)->toContain('test_namespace_worker_jobs_processed_total 5000');
-});
+})->group('functional');
test('it exports baseline metrics with labels', function () {
config(['queue-metrics.prometheus.namespace' => 'test_namespace']);
@@ -169,7 +169,7 @@
// Verify specific values with all 3 labels
expect($metrics)->toContain('test_namespace_baseline_cpu_percent_per_job{queue="default",connection="redis",job="App\\\\Jobs\\\\SendEmail"} 15.5');
expect($metrics)->toContain('test_namespace_baseline_confidence_score{queue="default",connection="redis",job="App\\\\Jobs\\\\SendEmail"} 0.85');
-});
+})->group('functional');
test('it handles missing queue labels gracefully', function () {
config(['queue-metrics.prometheus.namespace' => 'test_namespace']);
@@ -198,4 +198,4 @@
// Should use 'unknown' for missing labels
expect($metrics)->toContain('queue="unknown"');
expect($metrics)->toContain('connection="unknown"');
-});
+})->group('functional');