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
158 changes: 158 additions & 0 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<!--
SYNC IMPACT REPORT
==================
Version change: (none) → 1.0.0
Modified principles: N/A (initial ratification)
Added sections: Project Identity, Principles 1–5, Governance
Removed sections: N/A
Templates requiring updates:
- .specify/templates/plan-template.md ⚠ pending (does not exist yet)
- .specify/templates/spec-template.md ⚠ pending (does not exist yet)
- .specify/templates/tasks-template.md ⚠ pending (does not exist yet)
Follow-up TODOs:
- Create the three template files above so speckit-plan/specify/tasks can use them.
- Expand supported PHP/PHPStan version matrix in composer.json as new releases arrive.
-->

# Project Constitution — knplabs/phpstan-rules

**Constitution Version:** 1.0.0
**Ratification Date:** 2026-07-15
**Last Amended Date:** 2026-07-15
**Maintainers:** KnpLabs organization

---

## Project Identity

**Name:** knplabs/phpstan-rules
**Package type:** PHPStan extension (open-source, MIT)
**Purpose:** Provide PHPStan static-analysis rules shared across KnpLabs organization projects,
made available to the wider PHP community as a Composer package.
**Primary audience:** PHP developers who want to enforce organization-wide coding conventions via
PHPStan.

---

## Core Principles

### Principle 1 — Broad Compatibility

Every release MUST remain installable on the widest reasonable range of PHP and PHPStan versions
to maximize adoption. Dropping a supported version is a MAJOR version bump and MUST be explicitly
justified.

**Non-negotiable rules:**
- `composer.json` `require.php` MUST target `^8.2` or broader (never pin to a single patch).
- `composer.json` `require.phpstan/phpstan` MUST support at least the current stable major series
and, where API-compatible, the previous major.
- CI MUST gate on every declared PHP minor version (currently 8.2, 8.3, 8.4, 8.5).
- No rule implementation MUST use PHP APIs unavailable in the declared minimum version.
- Dependencies MUST be kept minimal; new `require` entries need explicit justification.

**Rationale:** Restrictive version constraints are the primary reason PHPStan extensions go
un-adopted. A wide compatibility matrix lowers the barrier to entry and encourages community
contributions.

---

### Principle 2 — Test Coverage per Rule

Every PHPStan rule MUST ship with a dedicated `PHPUnit` / `PHPStan\Testing\RuleTestCase` test
covering both triggering and non-triggering cases.

**Non-negotiable rules:**
- Each rule class in `src/Rules/<Namespace>/` MUST have a corresponding test in
`tests/Rules/<Namespace>/` following the naming convention `<RuleName>Test.php`.
- Each test MUST include at least one fixture file in `tests/Rules/<Namespace>/fixtures/` that
exercises both the positive (violation) and negative (no violation) paths.
- New rules MUST NOT be registered in `extension.neon` until their test passes on all declared
PHP versions.
- CI MUST run the full test suite (`composer test`) and it MUST be green before merging.

**Rationale:** Rules without tests are liabilities — they cannot be refactored safely and may
produce false positives or negatives undetected across PHPStan/PHP upgrades.

---

### Principle 3 — Documentation per Rule

Every PHPStan rule MUST be documented in `README.md` before it is released.

**Non-negotiable rules:**
- Each rule entry in `README.md` MUST include:
- The rule identifier (e.g., `clock.disallowDateTimeNow`).
- A one-sentence summary of what the rule enforces and why.
- A "Triggers on" code block with at least one violating example.
- A "Does not trigger on" code block with at least one passing example.
- A "Recommended fix" section showing the preferred alternative pattern.
- Documentation MUST be kept in sync with rule behavior; a PR that changes a rule's logic
MUST also update its documentation.
- Rules that are not yet documented MUST NOT appear in a stable release.

**Rationale:** Without clear documentation, developers cannot evaluate whether a rule is
appropriate for their project, and they cannot understand why a violation was flagged.

---

### Principle 4 — Open-Source Quality Standards

All source files MUST meet the project's code-quality baseline so that community contributors
can work effectively.

**Non-negotiable rules:**
- All PHP source files MUST declare `strict_types=1`.
- Code MUST conform to PSR-12 enforced by `php-cs-fixer` (`composer cs-check` MUST pass).
- The codebase itself MUST pass PHPStan analysis at the configured level (`composer phpstan`
MUST pass).
- PHPStan baseline (`phpstan-baseline.neon`) MUST be kept as small as possible; new suppressions
MUST be justified in the PR description.
- Rule classes MUST be `final readonly` where PHP version and design allow.

**Rationale:** Consistent code style and static analysis lower the cognitive load for contributors
and prevent quality regressions from being introduced silently.

---

### Principle 5 — Single-Responsibility Rule Design

Each rule class MUST enforce exactly one logical constraint.

**Non-negotiable rules:**
- A rule MUST be scoped to a single AST node type (one `getNodeType()` return value per class).
- If two related constraints share an AST node, they MUST be implemented as separate classes
unless their logic is inseparably coupled (requires explicit justification in the PR).
- Rule namespaces (`src/Rules/<Namespace>/`) MUST group rules by domain, not by node type.
- The rule's error message MUST be actionable: it MUST state what to do instead, not only
what was found wrong.

**Rationale:** Small, focused rules are easier to test, easier to selectively disable in
downstream projects, and easier for contributors to understand and maintain.

---

## Governance

### Amendment Procedure

1. Open a GitHub issue or PR describing the proposed change and its rationale.
2. At least one maintainer MUST review and approve the change.
3. The `CONSTITUTION_VERSION` MUST be bumped according to the semantic rules below.
4. `LAST_AMENDED_DATE` MUST be updated to the merge date.
5. The Sync Impact Report (HTML comment at top of this file) MUST be refreshed.

### Versioning Policy

- **MAJOR** bump: backward-incompatible governance change — removing a principle, redefining
a non-negotiable rule in a stricter or incompatible way.
- **MINOR** bump: new principle added, new mandatory section introduced, materially expanded
guidance.
- **PATCH** bump: clarification, wording improvement, typo fix, non-semantic refinement.

### Compliance Review

- Constitution compliance MUST be assessed during each PR review.
- Each speckit feature cycle (specify → plan → tasks → implement) MUST reference the active
constitution version.
- If a task or implementation choice would violate a principle, the violation MUST be escalated
before merging — not suppressed silently.
60 changes: 60 additions & 0 deletions .specify/templates/plan-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Implementation Plan — [FEATURE_NAME]

**Constitution version:** 1.0.0
**Spec reference:** `.specify/specs/[feature-slug]/spec.md`
**Author:** [AUTHOR]
**Date:** [DATE]

---

## Constitution Check

Before finalizing this plan, confirm compliance with the active constitution:

| Principle | Check |
|-----------|-------|
| 1 — Broad Compatibility | [ ] No new PHP/PHPStan version constraint tightening |
| 2 — Test Coverage per Rule | [ ] Test class and fixture planned |
| 3 — Documentation per Rule | [ ] README section planned |
| 4 — Open-Source Quality Standards | [ ] strict_types, PSR-12, PHPStan passing |
| 5 — Single-Responsibility Rule Design | [ ] One node type per class confirmed |

---

## Approach

[Describe the chosen implementation approach and why it was selected over alternatives.]

### Alternatives considered

| Alternative | Rejected because |
|-------------|-----------------|
| [option] | [reason] |

---

## File Inventory

| File | Action | Notes |
|------|--------|-------|
| `src/Rules/[Namespace]/[RuleName].php` | Create | Core rule class |
| `tests/Rules/[Namespace]/[RuleName]Test.php` | Create | PHPUnit test |
| `tests/Rules/[Namespace]/fixtures/[name].php` | Create | Test fixture |
| `extension.neon` | Edit | Register rule service |
| `README.md` | Edit | Add rule documentation |

---

## Edge Cases & Risks

- [List known edge cases the implementation must handle]
- [List risks, e.g., PHPStan API instability, PHP version quirks]

---

## Validation Steps

1. `docker compose run --rm php vendor/bin/phpunit tests/Rules/[Namespace]/[RuleName]Test.php`
2. `docker compose run --rm php composer phpstan`
3. `docker compose run --rm php composer cs-check`
4. Manual review of fixture file to confirm positive/negative cases.
75 changes: 75 additions & 0 deletions .specify/templates/spec-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Feature Specification — [FEATURE_NAME]

**Constitution version:** 1.0.0
**Status:** [draft | review | approved]
**Author:** [AUTHOR]
**Date:** [DATE]

---

## Overview

[One paragraph describing the feature, the problem it solves, and why it belongs in this package.]

---

## Scope

### In scope
- [What this feature covers]

### Out of scope
- [What this feature explicitly does not cover]

---

## Rule Definition (if applicable)

**Rule identifier:** `[namespace.ruleName]`
**Node type:** `[PhpParser\Node\...]`
**Namespace / directory:** `src/Rules/[Namespace]/`

### Triggers on
```php
// [example code that should produce a violation]
```

### Does not trigger on
```php
// [example code that should NOT produce a violation]
```

### Error message
> [Exact error message string, must be actionable]

### Recommended fix
[Describe the pattern the developer should use instead.]

---

## Compatibility Requirements

<!-- Constitution Principle 1 check -->
- Minimum PHP version affected: [e.g., 8.2+]
- PHPStan version requirements: [e.g., ^2.0]
- Any new `composer.json` dependencies: [none | list with justification]

---

## Acceptance Criteria

<!-- Constitution Principle 2 & 3 check -->
- [ ] Rule class exists at `src/Rules/[Namespace]/[RuleName].php` and is `final readonly`.
- [ ] Rule is registered in `extension.neon`.
- [ ] Test class exists at `tests/Rules/[Namespace]/[RuleName]Test.php`.
- [ ] Fixture file covers at least one triggering and one non-triggering case.
- [ ] `composer test` passes on PHP 8.2, 8.3, 8.4, 8.5.
- [ ] `composer phpstan` passes with no new baseline suppressions (or suppressions justified).
- [ ] `composer cs-check` passes.
- [ ] `README.md` updated with rule documentation (identifier, summary, examples, fix).

---

## Open Questions

- [List any unresolved design or scope questions]
59 changes: 59 additions & 0 deletions .specify/templates/tasks-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Tasks — [FEATURE_NAME]

**Constitution version:** 1.0.0
**Plan reference:** `.specify/specs/[feature-slug]/plan.md`
**Author:** [AUTHOR]
**Date:** [DATE]

---

## Task List

Tasks are ordered by dependency. Mark `[x]` when complete.

### Phase 1 — Rule Implementation

- [ ] **T1** — Create rule class `src/Rules/[Namespace]/[RuleName].php`
- Implement `Rule<[NodeType]>`
- Add `getNodeType()` returning `[NodeType]::class`
- Add `processNode()` with violation detection logic
- Ensure `final readonly` class modifier
- Add `declare(strict_types=1)`

- [ ] **T2** — Register rule in `extension.neon`
- Add service entry with `phpstan.rules.rule` tag

### Phase 2 — Testing (Constitution Principle 2)

- [ ] **T3** — Create fixture file `tests/Rules/[Namespace]/fixtures/[name].php`
- Include at least one triggering case (with inline comment marking expected violation)
- Include at least one non-triggering case

- [ ] **T4** — Create test class `tests/Rules/[Namespace]/[RuleName]Test.php`
- Extend `PHPStan\Testing\RuleTestCase<[RuleName]>`
- Implement `getRule()` returning a new instance of the rule
- Add `testRule()` calling `$this->analyse()` with fixture path and expected errors

- [ ] **T5** — Verify test suite passes
- `docker compose run --rm php composer test` — green on all PHP versions

### Phase 3 — Documentation (Constitution Principle 3)

- [ ] **T6** — Update `README.md`
- Add rule section with identifier, summary, "Triggers on", "Does not trigger on",
and "Recommended fix"

### Phase 4 — Quality Gate (Constitution Principle 4)

- [ ] **T7** — Verify static analysis
- `docker compose run --rm php composer phpstan` — passes, no new baseline entries

- [ ] **T8** — Verify code style
- `docker compose run --rm php composer cs-check` — passes

---

## Definition of Done

All tasks T1–T8 checked AND a PR reviewer has confirmed compliance with the constitution
(all five principles satisfied).