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
43 changes: 43 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Project

PHP library (tiny-blocks). Immutable domain models, zero infrastructure dependencies in core.

## Stack

Refer to `composer.json` for the full dependency list, version constraints, and PHP version.

## Project layout

```
src/
├── <PublicInterface>.php # Primary contract for consumers
├── <Implementation>.php # Main implementation or extension point
├── Contracts/ # Interfaces for data returned to consumers
├── Internal/ # Implementation details (not part of public API)
│ └── Exceptions/ # Internal exception classes
└── Exceptions/ # Public exception classes (when part of the API)
tests/
├── Models/ # Domain-specific fixtures reused across tests
├── Mocks/ # Test doubles for system boundaries
├── Unit/ # Unit tests for public API
└── Integration/ # Tests requiring real external resources (when applicable)
```

See `rules/domain.md` for folder conventions and naming rules.

## Commands

- Run tests: `make test`.
- Run lint: `make review`.
- Run `make help` to list all available commands.

## Post-change validation

After any code change, run `make review` and `make test`.
If either fails, iterate on the fix while respecting all project rules until both pass.
Never deliver code that breaks lint or tests.

## Reference-first approach

Always read all rule files and reference sources before generating any code or documentation.
Never generate from memory. Read the source and match the pattern exactly.
82 changes: 82 additions & 0 deletions .claude/rules/code-style.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
description: Pre-output checklist, naming, typing, comparisons, and PHPDoc rules for all PHP files in libraries.
paths:
- "src/**/*.php"
- "tests/**/*.php"
---

# Code style

Semantic code rules for all PHP files. Formatting rules (PSR-1, PSR-4, PSR-12, line length) are enforced by `phpcs.xml`
and are not repeated here. Refer to `rules/domain.md` for domain modeling rules.

## Pre-output checklist

Verify every item before producing any PHP code. If any item fails, revise before outputting.

1. `declare(strict_types=1)` is present.
2. All classes are `final readonly` by default. Use `class` (without `final` or `readonly`) only when the class is
designed as an extension point for consumers (e.g., `Collection`, `ValueObject`). Use `final class` without
`readonly` only when the parent class is not readonly (e.g., extending a third-party abstract class).
3. All parameters, return types, and properties have explicit types.
4. Constructor property promotion is used.
5. Named arguments are used at call sites for own code, tests, and third-party library methods (e.g., tiny-blocks).
Never use named arguments on native PHP functions (`array_map`, `in_array`, `preg_match`, `is_null`,
`iterator_to_array`, `sprintf`, `implode`, etc.).
6. No `else` or `else if` exists anywhere. Use early returns, polymorphism, or map dispatch instead.
7. No abbreviations appear in identifiers. Use `$index` instead of `$i`, `$account` instead of `$acc`.
8. No generic identifiers exist. Use domain-specific names instead:
`$data` → `$payload`, `$value` → `$totalAmount`, `$item` → `$element`,
`$info` → `$details`, `$result` → `$outcome`.
9. No raw arrays exist where a typed collection or value object is available. Use `tiny-blocks/collection`
(`Collection`, `Collectible`) instead of raw `array` for any list of domain objects. Raw arrays are acceptable
only for primitive configuration data, variadic pass-through, or interop at system boundaries.
10. No private methods exist except private constructors for factory patterns. Inline trivial logic at the call site
or extract it to a collaborator or value object.
11. Members are ordered: constants first, then constructor, then static methods, then instance methods. Within each
group, order by body size ascending (number of lines between `{` and `}`). Constants and enum cases, which have
no body, are ordered by name length ascending.
12. Constructor parameters are ordered by parameter name length ascending (count the name only, without `$` or type),
except when parameters have an implicit semantic order (e.g., `$start/$end`, `$from/$to`, `$startAt/$endAt`),
which takes precedence. The same rule applies to named arguments at call sites.
Example: `$id` (2) → `$value` (5) → `$status` (6) → `$precision` (9).
13. No O(N²) or worse complexity exists.
14. No logic is duplicated across two or more places (DRY).
15. No abstraction exists without real duplication or isolation need (KISS).
16. All identifiers, comments, and documentation are written in American English.
17. No justification comments exist (`// NOTE:`, `// REASON:`, etc.). Code speaks for itself.
18. `// TODO: <reason>` is used when implementation is unknown, uncertain, or intentionally deferred.
Never leave silent gaps.
19. All class references use `use` imports at the top of the file. Fully qualified names inline are prohibited.
20. No dead or unused code exists. Remove unreferenced classes, methods, constants, and imports.
21. Never create public methods, constants, or classes in `src/` solely to serve tests. If production code does not
need it, it does not exist.

## Naming

- Names describe **what** in domain terms, not **how** technically: `$monthlyRevenue` instead of `$calculatedValue`.
- Generic technical verbs (`process`, `handle`, `execute`, `mark`, `enforce`, `manage`, `ensure`, `validate`,
`check`, `verify`, `assert`, `transform`, `parse`, `compute`, `sanitize`, `normalize`) **should be avoided**.
Prefer names that describe the domain operation.
- Booleans use predicate form: `isActive`, `hasPermission`, `wasProcessed`.
- Collections are always plural: `$orders`, `$lines`.
- Methods returning bool use prefixes: `is`, `has`, `can`, `was`, `should`.

## Comparisons

1. Null checks: use `is_null($variable)`, never `$variable === null`.
2. Empty string checks on typed `string` parameters: use `$variable === ''`. Avoid `empty()` on typed strings because
`empty('0')` returns `true`.
3. Mixed or untyped checks (value may be `null`, empty string, `0`, or `false`): use `empty($variable)`.

## American English

All identifiers, enum values, comments, and error codes use American English spelling:
`canceled` (not `cancelled`), `organization` (not `organisation`), `initialize` (not `initialise`),
`behavior` (not `behaviour`), `modeling` (not `modelling`), `labeled` (not `labelled`),
`fulfill` (not `fulfil`), `color` (not `colour`).

## PHPDoc

- PHPDoc is restricted to interfaces only, documenting obligations and `@throws`.
- Never add PHPDoc to concrete classes.
34 changes: 34 additions & 0 deletions .claude/rules/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
description: Standards for README files and all project documentation in PHP libraries.
paths:
- "**/*.md"
---

# Documentation

## README

1. Include an anchor-linked table of contents.
2. Start with a concise one-line description of what the library does.
3. Include a **badges** section (license, build status, coverage, latest version, PHP version).
4. Provide an **Overview** section explaining the problem the library solves and its design philosophy.
5. **Installation** section: Composer command (`composer require vendor/package`).
6. **How to use** section: complete, runnable code examples covering the primary use cases. Each example
includes a brief heading describing what it demonstrates.
7. If the library exposes multiple entry points, strategies, or container types, document each with its own
subsection and example.
8. **License** and **Contributing** sections at the end.
9. Write strictly in American English. See `rules/code-style.md` American English section for spelling conventions.

## Structured data

1. When documenting constructors, factory methods, or configuration options with more than 3 parameters,
use tables with columns: Parameter, Type, Required, Description.
2. Prefer tables to prose for any structured information.

## Style

1. Keep language concise and scannable.
2. Never include placeholder content (`TODO`, `TBD`).
3. Code examples must be syntactically correct and self-contained.
4. Do not document `Internal/` classes or private API. Only document what consumers interact with.
96 changes: 96 additions & 0 deletions .claude/rules/domain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
description: Domain modeling rules for PHP libraries — folder structure, naming, value objects, exceptions, enums, and SOLID.
paths:
- "src/**/*.php"
---

# Domain modeling

Libraries are self-contained packages. The core has no dependency on frameworks, databases, or I/O.
Refer to `rules/code-style.md` for the pre-output checklist applied to all PHP code.

## Folder structure

```
src/
├── <PublicInterface>.php # Primary contract for consumers
├── <Implementation>.php # Main implementation or extension point
├── <Enum>.php # Public enum
├── Contracts/ # Interfaces for data returned to consumers
├── Internal/ # Implementation details (not part of public API)
│ ├── <Collaborator>.php
│ └── Exceptions/ # Internal exception classes
├── <Feature>/ # Feature-specific subdirectory when needed
└── Exceptions/ # Public exception classes (when part of the API)
```

**Public API boundary:** Only interfaces, extension points, enums, and thin orchestration classes live at the
`src/` root. These classes define the contract consumers interact with and delegate all real work to collaborators
inside `src/Internal/`. If a class contains substantial logic (algorithms, state machines, I/O), it belongs in
`Internal/`, not at the root.

The `Internal/` namespace signals classes that are implementation details. Consumers must not depend on them.
Never use `Entities/`, `ValueObjects/`, `Enums/`, or `Domain/` as folder names.

## Nomenclature

1. Every class, property, method, and exception name reflects the **domain concept** the library represents.
A math library uses `Precision`, `RoundingMode`; a money library uses `Currency`, `Amount`; a collection
library uses `Collectible`, `Order`.
2. Never use generic technical names: `Manager`, `Helper`, `Processor`, `Data`, `Info`, `Utils`,
`Item`, `Record`, `Entity`, `Exception`, `Ensure`, `Validate`, `Check`, `Verify`,
`Assert`, `Transform`, `Parse`, `Compute`, `Sanitize`, or `Normalize` as class suffixes or prefixes.
3. Name classes after what they represent: `Money`, `Color`, `Pipeline` — not after what they do technically.
4. Name methods after the operation in domain terms: `add()`, `convertTo()`, `splitAt()` — not `process()`,
`handle()`, `execute()`, `manage()`, `ensure()`, `validate()`, `check()`, `verify()`, `assert()`,
`transform()`, `parse()`, `compute()`, `sanitize()`, or `normalize()`.

## Value objects

1. Are immutable: no setters, no mutation after construction. Operations return new instances.
2. Compare by value, not by reference.
3. Validate invariants in the constructor and throw on invalid input.
4. Have no identity field.
5. Use static factory methods (e.g., `from`, `of`, `zero`) with a private constructor when multiple creation
paths exist.

## Exceptions

1. Extend native PHP exceptions (`DomainException`, `InvalidArgumentException`, `OverflowException`, etc.).
2. Are pure: no formatted `code`/`message` for HTTP responses.
3. Signal invariant violations only.
4. Name after the invariant violated, never after the technical type:
`PrecisionOutOfRange` — not `InvalidPrecisionException`.
`CurrencyMismatch` — not `BadCurrencyException`.
`ContainerWaitTimeout` — not `TimeoutException`.
5. Create the exception class directly with the invariant name and the appropriate native parent. The exception
is dedicated by definition when its name describes the specific invariant it guards.

## Enums

1. Are PHP backed enums.
2. Include domain-meaningful methods when needed (e.g., `Order::ASCENDING_KEY`).

## Extension points

1. When a class is designed to be extended by consumers (e.g., `Collection`, `ValueObject`), it uses `class`
instead of `final readonly class`. All other classes use `final readonly class`.
2. Extension point classes use a private constructor with static factory methods (`createFrom`, `createFromEmpty`)
as the only creation path.
3. Internal state is injected via the constructor and stored in a `private readonly` property.

## Principles

- **Immutability**: all models and value objects adopt immutability. Operations return new instances.
- **Zero dependencies**: the library's core has no dependency on frameworks, databases, or I/O.
- **Small surface area**: expose only what consumers need. Hide implementation in `Internal/`.

## SOLID reference

| Principle | Failure signal |
|---------------------------|---------------------------------------------|
| S — Single responsibility | Class does two unrelated things |
| O — Open/closed | Adding a feature requires editing internals |
| L — Liskov substitution | Subclass throws on parent method |
| I — Interface segregation | Interface has unused methods |
| D — Dependency inversion | Constructor uses `new ConcreteClass()` |
98 changes: 98 additions & 0 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
description: BDD Given/When/Then structure, PHPUnit conventions, test organization, and fixture rules for PHP libraries.
paths:
- "tests/**/*.php"
---

# Testing conventions

Framework: **PHPUnit**. Refer to `rules/code-style.md` for the code style checklist, which also applies to test files.

## Structure: Given/When/Then (BDD)

Every test uses `/** @Given */`, `/** @And */`, `/** @When */`, `/** @Then */` doc comments without exception.

### Happy path example

```php
public function testAddMoneyWhenSameCurrencyThenAmountsAreSummed(): void
{
/** @Given two money instances in the same currency */
$ten = Money::of(amount: 1000, currency: Currency::BRL);
$five = Money::of(amount: 500, currency: Currency::BRL);

/** @When adding them together */
$total = $ten->add(other: $five);

/** @Then the result contains the sum of both amounts */
self::assertEquals(expected: 1500, actual: $total->amount());
}
```

### Exception example

When testing that an exception is thrown, place `@Then` (expectException) **before** `@When`. PHPUnit requires this
ordering.

```php
public function testAddMoneyWhenDifferentCurrenciesThenCurrencyMismatch(): void
{
/** @Given two money instances in different currencies */
$brl = Money::of(amount: 1000, currency: Currency::BRL);
$usd = Money::of(amount: 500, currency: Currency::USD);

/** @Then an exception indicating currency mismatch should be thrown */
$this->expectException(CurrencyMismatch::class);

/** @When trying to add money with different currencies */
$brl->add(other: $usd);
}
```

Use `@And` for complementary preconditions or actions within the same scenario, avoiding consecutive `@Given` or
`@When` tags.

## Rules

1. Include exactly one `@When` per test. Two actions require two tests.
2. Test only the public API. Never assert on private state or `Internal/` classes directly.
3. Never mock internal collaborators. Use real objects. Use test doubles only at system boundaries (filesystem,
clock, network) when the library interacts with external resources.
4. Name tests to describe behavior, not method names.
5. Never include conditional logic inside tests.
6. Include one logical concept per `@Then` block.
7. Maintain strict independence between tests. No inherited state.
8. For exception tests, place `@Then` (expectException) before `@When`.
9. Use domain-specific model classes in `tests/Models/` for test fixtures that represent domain concepts
(e.g., `Amount`, `Invoice`, `Order`).
10. Use mock classes in `tests/Mocks/` (or `tests/Unit/Mocks/`) for test doubles of system boundaries
(e.g., `ClientMock`, `ExecutionCompletedMock`).
11. Exercise invariants and edge cases through the library's public entry point. Create a dedicated test class
for an internal model only when the condition cannot be reached through the public API.

## Test organization

```
tests/
├── Models/ # Domain-specific fixtures reused across tests
├── Mocks/ # Test doubles for system boundaries
├── Unit/ # Unit tests for public API
│ └── Mocks/ # Alternative location for test doubles
├── Integration/ # Tests requiring real external resources (Docker, filesystem)
└── bootstrap.php # Test bootstrap when needed
```

- `tests/` or `tests/Unit/`: pure unit tests exercising the library's public API.
- `tests/Integration/`: tests requiring real external resources (e.g., Docker containers, databases).
Only present when the library interacts with infrastructure.
- `tests/Models/`: domain-specific fixture classes reused across test files.
- `tests/Mocks/` or `tests/Unit/Mocks/`: test doubles for system boundaries.

## Coverage and mutation testing

1. Line and branch coverage must be **100%**. No annotations (`@codeCoverageIgnore`), attributes, or configuration
that exclude code from coverage are allowed.
2. All mutations reported by Infection must be **killed**. Never ignore or suppress mutants via `infection.json.dist`
or any other mechanism.
3. If a line or mutation cannot be covered or killed, it signals a design problem in the production code. Refactor
the code to make it testable, do not work around the tool.
11 changes: 11 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copilot instructions

## Context

PHP library (tiny-blocks). Immutable domain models, zero infrastructure dependencies in core.

## Mandatory pre-task step

Before starting any task, read and strictly follow all instruction files located in `.claude/CLAUDE.md` and
`.claude/rules/`. These files are the absolute source of truth for code generation. Apply every rule strictly. Do not
deviate from the patterns, folder structure, or naming conventions defined in them.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
tests:
name: Tests
runs-on: ubuntu-latest
needs: auto-review
needs: build

steps:
- name: Checkout
Expand Down
Loading
Loading