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
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Webware alignment Dev Container.
//
// This is a thin wrapper around compose.yml — it reuses the `tooling` service
// This is a thin wrapper around compose.yml - it reuses the `tooling` service
// rather than defining a parallel environment, so VS Code and plain
// `docker compose` users share the exact same container. Non-VS Code
// developers are not orphaned: they use `docker compose up -d` +
Expand Down
28 changes: 14 additions & 14 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# webware/webware-validator — Copilot Agent Instructions
# webware/webware-validator: Copilot Agent Instructions

## Read this before acting — fleet working agreements
## Read this before acting: fleet working agreements

`vendor/webware/webware-tools/agent-working-agreements.md` is authoritative for how work is done
in this fleet, and it overrides anything else in this file or in any local note. Read it before
acting, and again before reporting. The non-negotiables:

- Do exactly the scope asked — only the files, branches and repositories named. No adjacent edits,
- Do exactly the scope asked - only the files, branches and repositories named. No adjacent edits,
no cleanup, no onboarding, no surveys.
- Anything else you notice gets one line at most, with no proposed fix.
- Never close a reply with a condensed restatement of it, and never present an inference as a
Expand All @@ -26,11 +26,11 @@ always a dependency, so this file does not need to be duplicated per package.

Mago distinguishes what a `*::class` string may reference:

- `class-string<T>` — a concrete **class**
- `interface-string<T>` — an **interface**
- `enum-string<T>` — an **enum**
- `trait-string<T>` — a **trait**
- `class-like-string<T>` — any of class / interface / enum
- `class-string<T>` - a concrete **class**
- `interface-string<T>` - an **interface**
- `enum-string<T>` - an **enum**
- `trait-string<T>` - a **trait**
- `class-like-string<T>` - any of class / interface / enum

Webware aliases against interfaces and builds to interfaces, so prefer the precise form:

Expand All @@ -47,22 +47,22 @@ PHPUnit 13 enforces a strict separation between mocks and stubs. Violating these

- **Use `createStub()`** when the test double only needs to return values (`method()->willReturn()`, `method()->willReturnCallback()`). No expectations are configured.
- **Use `createMock()`** only when the test verifies behavior with `expects()` (e.g. `expects($this->once())`, `expects($this->never())`).
- **Never** call `createMock()` without also calling `expects()` on at least one method — PHPUnit 13 will issue a notice.
- **Never** call `createMock()` without also calling `expects()` on at least one method - PHPUnit 13 will issue a notice.
- **Remove** `/** @var ClassName&MockObject */` annotations and `MockObject` intersection types on variables created with `createStub()`.
- **Remove** the `use PHPUnit\Framework\MockObject\MockObject;` import from any file where no `createMock()` + `expects()` usage remains.

### Examples

```php
// CORRECT — stub returns value, no expectations
// CORRECT - stub returns value, no expectations
$container = $this->createStub(ContainerInterface::class);
$container->method('get')->willReturnCallback(static fn(string $id): mixed => ...);

// CORRECT — mock verifies behavior with expects()
// CORRECT - mock verifies behavior with expects()
$handler = $this->createMock(ErrorHandler::class);
$handler->expects($this->once())->method('attachListener');

// WRONG — createMock() with no expects() triggers PHPUnit notice
// WRONG - createMock() with no expects() triggers PHPUnit notice
$logger = $this->createMock(Logger::class);
$logger->method('withName')->willReturn($logger); // should be createStub()
```
Expand All @@ -71,6 +71,6 @@ $logger->method('withName')->willReturn($logger); // should be createStub()

`phpunit.xml.dist` has `requireCoverageMetadata="true"`. Every test class **must** have:

1. `#[CoversClass(ClassName::class)]` — one per source class under test.
2. `#[CoversMethod(ClassName::class, 'methodName')]` — one per public/protected method exercised.
1. `#[CoversClass(ClassName::class)]` - one per source class under test.
2. `#[CoversMethod(ClassName::class, 'methodName')]` - one per public/protected method exercised.
3. `use PHPUnit\Framework\Attributes\CoversClass;` and `use PHPUnit\Framework\Attributes\CoversMethod;` imports.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#
# Builds a self-contained, interactive PHP development environment carrying
# the Webware toolchain: PHP CLI, Composer, Mago, and Xdebug. Developers work
# inside this container — either through the VS Code Dev Container (backed by
# inside this container: either through the VS Code Dev Container (backed by
# compose.yml) or by `docker compose up -d` + `docker compose exec tooling`.
# The host machine (Windows, WSL, Linux, or macOS) never needs a native PHP
# toolchain.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ config:
| Path | Role |
|---|---|
| `mago.toml` | Extends the centre (`vendor/webware/webware-tools/mago.toml`) and overrides `php-version` only. Never re-add general rules locally. |
| `webware-ci.json` | The required CI workflow's parameter contract — read from the repository root by `webinertia/.github`. |
| `webware-ci.json` | The required CI workflow's parameter contract - read from the repository root by `webinertia/.github`. |
| `phpunit.xml.dist` | PHPUnit 13 strict mode: `requireCoverageMetadata`, `failOnNotice`, `failOnWarning`, `failOnDeprecation`. |
| `compose.yml` / `Dockerfile` / `.devcontainer/` | The containerized toolchain (Composer, PHPUnit, Mago, Infection, PHPBench, roave BC-check). |
| `src/ConfigProvider.php` | The package wiring entry point, declared under `extra.laminas.config-provider`. |

`mago.toml`, `phpunit.xml.dist`, `.gitattributes`, `codecov.yml`, `Dockerfile`,
`.dockerignore`, `infection.json5.dist`, `phpbench.json.dist` and devcontainer config are
byte-identical to the canonical artifacts in
`webware-tools/presets/webware-alignment/artifacts/` — copy updates from there rather than
`webware-tools/presets/webware-alignment/artifacts/` - copy updates from there rather than
editing them here.

## Quality gates

Both MSI gates are set to **95** — the ecosystem standard, not a starting point. Lower them
Both MSI gates are set to **95**: the ecosystem standard, not a starting point. Lower them
only with a deliberate decision, and never silently:

```json
Expand All @@ -43,7 +43,7 @@ only with a deliberate decision, and never silently:

Four Mago gates run in CI and must be clean: `format --check`, `lint`, `analyze`, `guard`.
Run `mago fmt` first when making changes, and fix findings at source rather than adding
`@mago-expect` — a suppression needs to be a decision, not a reflex.
`@mago-expect` - a suppression needs to be a decision, not a reflex.

## Development

Expand Down
14 changes: 7 additions & 7 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# This compose file is the single source of truth for the environment. Two
# ways to use it:
#
# 1. VS Code Dev Container — "Reopen in Container" uses
# 1. VS Code Dev Container: "Reopen in Container" uses
# .devcontainer/devcontainer.json, which is *backed by* this file (it
# references the `tooling` service rather than defining its own).
#
Expand All @@ -24,7 +24,7 @@
# Packages whose tests need MySQL uncomment the `mysql` service below (and the
# `phpmyadmin` web UI if wanted), plus the `depends_on` block on `tooling`. The
# `mysql` service mirrors the `db_image` package parameter used by CI, and is
# reachable from `tooling` as host `mysql` on port 3306 — matching the PhpDb
# reachable from `tooling` as host `mysql` on port 3306: matching the PhpDb
# `mysql.local.php` connection convention. Packages that need a different
# engine add an equivalent service here (postgres, mariadb, ...) mirroring
# their own `db-image` parameter.
Expand Down Expand Up @@ -56,13 +56,13 @@ services:
- "host.docker.internal:host-gateway"
volumes:
# Bind-mount the source so edits are visible on both the host and the
# container. vendor/ is NOT overridden — it is the host's own copy.
# container. vendor/ is NOT overridden - it is the host's own copy.
# Composer/installs inside the container write straight into the
# developer's working tree on disk.
- .:/app
# The Composer cache lives in a named volume (Linux-native I/O, fast on
# Windows) so downloads survive container rebuilds. It is only a cache
# — the developer's real artifacts all live on the host.
# - the developer's real artifacts all live on the host.
- composer-cache:/tmp/composer-cache
# Uncomment when the package's tests need MySQL (see the `mysql` service
# below). Ensures the database is ready before the shell attaches.
Expand All @@ -72,7 +72,7 @@ services:
working_dir: /app

# -------------------------------------------------------------------------
# MySQL (optional) — uncomment this whole block for packages whose tests
# MySQL (optional) - uncomment this whole block for packages whose tests
# need a MySQL database (e.g. webware-acl via php-db/phpdb-mysql).
#
# Mirror the spec's `db_image` / `db_env_json` / `db_port` Package Parameters
Expand All @@ -81,7 +81,7 @@ services:
# credentials below. Adjust MYSQL_DATABASE / MYSQL_USER / MYSQL_PASSWORD to
# the package's test-database convention. MYSQL_ALLOW_EMPTY_PASSWORD keeps
# the test-only database free of a root password and in sync with the CI
# `db_env_json` — remove it if the package pins a root password instead.
# `db_env_json` - remove it if the package pins a root password instead.
# -------------------------------------------------------------------------
# mysql:
# image: mysql:9.7
Expand All @@ -101,7 +101,7 @@ services:
# # survives `docker compose down` (use `down -v` to wipe it).
# - mysql-data:/var/lib/mysql
#
# # phpMyAdmin — optional web UI for the `mysql` service above. Reachable at
# # phpMyAdmin - optional web UI for the `mysql` service above. Reachable at
# # http://localhost:8080 (log in as root with an empty password, or with
# # the MYSQL_USER / MYSQL_PASSWORD credentials). Uncomment alongside
# # `mysql`.
Expand Down