Skip to content
Merged
Show file tree
Hide file tree
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
27 changes: 27 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/vendor/bin/pollora-qa hook pre-edit" }
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/vendor/bin/pollora-qa hook post-edit" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/vendor/bin/pollora-qa hook stop" }
]
}
]
}
}
19 changes: 19 additions & 0 deletions .github/workflows/harness.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Harness

on:
push:
branches: [main]
pull_request:
# A label (api-change, breaking) changes what the public surface gate allows.
types: [opened, synchronize, reopened, labeled, unlabeled]
schedule:
- cron: '17 3 * * *'
workflow_dispatch:

permissions: {}

jobs:
harness:
uses: Pollora/harness/.github/workflows/php-package.yml@v0.1.11
permissions:
contents: read
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ composer.lock
.vscode/
*.swp
*.swo
.DS_Store
.DS_Store
# Pollora harness reports
/.harness/
patches.lock.json
8 changes: 8 additions & 0 deletions .harness.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profile": "integration",
"mode": "enforce",
"ratchet": {
"phpstan_level": 5,
"phpstan_baseline": 0
}
}
3 changes: 3 additions & 0 deletions .semgrepignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Documentation, not a template a site renders: its Boost snippet shows the Loop facade calls
# that Pollora 13 removed, `{!! Loop::title() !!}` included.
src/Mcp/Prompts/UpgradePolloraV13/upgrade-pollora-v13.blade.php
56 changes: 56 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Agent instructions

<!-- pollora-harness:start -->
## Pollora harness

This repository follows the Pollora harness (profile `integration`, observe mode). The same gates run on your machine, in agent hooks and in CI; a pull request merges only when they pass.

### Before you hand back

- `composer qa:fast` runs the fast gates on what you changed (style, static analysis, architecture, dependencies). It must pass.
- `composer qa` runs every gate, as CI does.
- `vendor/bin/pollora-qa rules <ID>` explains a rule quoted in a failure.

### Never

- Never edit `.harness.json`, `phpstan-baseline.neon`, `api-surface.json`: they hold the thresholds. If one is wrong, stop and explain why to the user.
- Never add `@phpstan-ignore`, a baseline entry or a `nosemgrep` comment without a written reason on the same line.
- Never weaken a test to make it pass, and never mark a test skipped to get a green run.
- Never break the public API (attributes, hooks fired, configuration keys, commands) in a minor version: deprecate first.

### Always

- A bug fix comes with a test that fails without the fix.
- Domain code depends on ports (interfaces), never on WordPress functions, Laravel or the Infrastructure layer.
- The common case of a public API needs no configuration; options are chained methods or named arguments.

### Rules checked

- **ARCH-01** Domain code imports neither Illuminate nor the Infrastructure or UI layers _(Pest arch, G1)_
- **ARCH-02** Domain code calls no global WordPress function _(PHPStan rule pollora.noWordPressCallInDomain, G1)_
- **ARCH-03** Application code depends on the Domain layer only _(Pest arch, G1)_
- **ARCH-04** A core package requires neither illuminate/* nor laravel/*, unless listed in .harness.json _(pollora-qa deps, G1)_
- **ARCH-05** The framework core references no third-party plugin (WooCommerce, ACF, Meta Box, WP Rocket) _(Semgrep, G1)_
- **ARCH-06** Only integration packages, templates and projects depend on pollora/framework _(pollora-qa deps, G1)_
- **ARCH-07** Every dependency used is declared, none declared is unused _(composer-dependency-analyser, G1)_
- **ARCH-08** Attribute classes are final, readonly and declare their targets _(Pest arch, G1)_
- **ARCH-09** declare(strict_types=1) in every file _(Pest arch + Pint, G1)_
- **CONV-03** Laravel naming: ServiceProvider, Command, Exception, Repository, Factory suffixes _(Pest arch, G1)_
- **CONV-04** Hooks fired by Pollora are named pollora/<module>/<event_snake_case> _(pollora-qa surface, G2)_
- **CONV-05** Configuration lives under pollora.<package>; env() is only called in config/ _(Pest arch, G1)_
- **CONV-06** Commands are named pollora:<verb> or pollora:make-<thing> _(pollora-qa surface, G2)_
- **CONV-07** Domain and Application code never calls app() or resolve(); it receives contracts _(Pest arch, G1)_
- **CONV-10** One named exception per business case; never throw a bare \Exception _(Semgrep, G1)_
- **CONV-11** Native types everywhere; PHPDoc only for generics, array shapes, @internal, @deprecated _(PHPStan + Pest type coverage, G1)_
- **SEC-01** A REST route declared by attribute states its permission; a public route says so explicitly _(Semgrep, G1)_
- **SEC-03** No $wpdb query built by interpolation without prepare() _(Semgrep, G1)_
- **SEC-04** No unescaped Blade output {!! !!} outside a commented allowlist _(Semgrep, G1)_
- **SEC-05** No unserialize, eval or extract _(Semgrep, G1)_
- **SEC-08** No secret in the repository _(gitleaks, G1)_
- **SEC-09** No dependency with a known vulnerability _(composer audit, G1)_
- **QA-01** Ratchet: no metric gets worse (PHPStan level and baseline, coverage, mutation score) _(pollora-qa ratchet, G2)_
- **QA-02** The harness files are protected: baseline, .harness.json and api-surface.json change only through a human _(Agent hooks + CODEOWNERS, G0)_
- **QA-04** The public surface (attributes, hooks fired, configuration keys, commands) changes only with a label: api-change to add, breaking to remove or change _(pollora-qa surface, G2)_
- **QA-05** No PHP signature breaks backward compatibility outside a major version _(roave/backward-compatibility-check, G2)_
- **QA-03** A change to a core package keeps the framework green _(Downstream job, G2)_
<!-- pollora-harness:end -->
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- `laravel/mcp` (`^1.0`) and `pollora/discovery` (`^0.1 || ^1.0`) are declared: the package uses their classes and relied on them being installed by `laravel/boost` and `pollora/framework`. `laravel/mcp` 0.x, which Boost also accepts, is no longer accepted
- The REST examples of the `pollora-rest-api` skill and the core guidelines declare a permission on every route (`Everyone`, `IsLoggedIn`, `IsAdmin`): `pollora/wp-rest` 0.2 deprecates a route without one

## [1.7.0] - 2026-10-14
Expand Down
15 changes: 15 additions & 0 deletions api-surface.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": 1,
"surface": {
"command:nectar:mcp": "command",
"config:nectar.enabled": "key",
"config:nectar.mcp": "key",
"config:nectar.mcp.tools": "key",
"config:nectar.mcp.tools.exclude": "key",
"config:nectar.mcp.tools.include": "key",
"config:nectar.wp_cli": "key",
"config:nectar.wp_cli.allowed_commands": "key",
"hook:pollora/async/recover": "hook",
"hook:pollora/async/run": "hook"
}
}
14 changes: 14 additions & 0 deletions composer-dependency-analyser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;

/*
* The harness's preset covers WordPress's classes and functions and the tests.
* The package builds on pollora/framework, which requires the illuminate/* components it uses:
* when laravel/framework is installed (Testbench, a Pollora project) it replaces them, and the
* analyser then attributes their classes to laravel/framework.
*/
return (require __DIR__.'/vendor/pollora/harness/presets/composer-dependency-analyser.php')
->ignoreErrorsOnPackage('laravel/framework', [ErrorType::SHADOW_DEPENDENCY]);
23 changes: 15 additions & 8 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@
],
"type": "library",
"require": {
"php": "^8.3",
"laravel/boost": "^2.0",
"laravel/mcp": "^1.0",
"php": "^8.3",
"pollora/discovery": "^0.1 || ^1.0",
"pollora/framework": "^12.0 || ^13.0"
},
"require-dev": {
"driftingly/rector-laravel": "^2.3",
"laravel/pint": "^1.0",
"orchestra/testbench": "^9.0|^10.0|^11.0",
"pestphp/pest": "^3.0",
"phpstan/phpstan": "^2.0"
"phpstan/phpstan": "^2.0",
"pollora/harness": "^0.1.11"
},
"autoload": {
"psr-4": {
Expand All @@ -30,10 +33,7 @@
"autoload-dev": {
"psr-4": {
"Pollora\\Nectar\\Tests\\": "tests/"
},
"files": [
"tests/Helpers/wordpress-stubs.php"
]
}
},
"extra": {
"laravel": {
Expand All @@ -48,7 +48,12 @@
"wikimedia/composer-merge-plugin": true,
"cweagans/composer-patches": true
},
"sort-packages": true
"sort-packages": true,
"audit": {
"ignore-abandoned": [
"symplify/rule-doc-generator-contracts"
]
}
},
"scripts": {
"test": [
Expand All @@ -60,7 +65,9 @@
"test:types": "phpstan analyse",
"test:unit": "pest",
"lint": "pint",
"refacto": "rector"
"refacto": "rector",
"qa": "@php vendor/bin/pollora-qa check",
"qa:fast": "@php vendor/bin/pollora-qa check --fast"
},
"minimum-stability": "dev",
"prefer-stable": true
Expand Down
2 changes: 2 additions & 0 deletions config/nectar.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

return [

/*
Expand Down
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
includes:
- vendor/pollora/harness/presets/phpstan/base.neon

parameters:
level: 5
paths:
Expand Down
7 changes: 5 additions & 2 deletions pint.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"preset": "laravel"
}
"preset": "laravel",
"rules": {
"declare_strict_types": true
}
}
2 changes: 1 addition & 1 deletion src/Mcp/Tools/ActiveThemeInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ private function findProviders(string $themePath): array
}

return collect(File::files($providersDir))
->filter(fn ($file) => $file->getExtension() === 'php')
->filter(fn ($file): bool => $file->getExtension() === 'php')
->map(fn ($file) => $file->getFilenameWithoutExtension())
->values()
->all();
Expand Down
2 changes: 1 addition & 1 deletion src/Mcp/Tools/DiscoveredComponents.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class DiscoveredComponents extends Tool
*
* @var array<string, string>
*/
private const TYPE_MAP = [
private const array TYPE_MAP = [
'post_type' => 'post_types',
'taxonomy' => 'taxonomies',
'hook' => 'hooks',
Expand Down
2 changes: 1 addition & 1 deletion src/Mcp/Tools/WordPressInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function handle(Request $request): Response
if (! function_exists('get_plugins') && defined('ABSPATH')) {
$pluginFile = ABSPATH.'wp-admin/includes/plugin.php';
if (file_exists($pluginFile)) {
require_once $pluginFile; // @phpstan-ignore requireOnce.fileNotFound
require_once $pluginFile;
}
}

Expand Down
15 changes: 15 additions & 0 deletions tests/Arch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

use Pollora\Harness\Pest\Architecture;

/*
* Architecture rules of the Pollora harness (ARCH-*, CONV-*).
*
* The rules follow the profile in .harness.json and the layers found under
* the PSR-4 roots: nothing to maintain here. List them with
* `vendor/bin/pollora-qa rules`.
*/

Architecture::for(dirname(__DIR__))->register();
3 changes: 3 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@

use Pollora\Nectar\Tests\TestCase;

// Loaded here, not by Composer's autoload-dev: PHPStan loads that autoload, then WordPress's stubs, and both declare these functions.
require_once __DIR__.'/Helpers/wordpress-stubs.php';

uses(TestCase::class)->in('Feature');
Loading