Skip to content

v0.2.0

Choose a tag to compare

@mattyhansen mattyhansen released this 27 May 19:09
· 66 commits to main since this release
ffb82c6

gruff-php 0.2.0 - strict gating, better triage, cleaner config

Released: 2026-05-28
Install: composer require --dev blundergoat/gruff-php:^0.2
Full changelog: CHANGELOG.md

Headline

analyse now fails CI on any finding by default, configs must declare schemaVersion:, and a new per-rule triage surface (list-rules <ruleId>, branch-review deltas, output-volume hint) makes large result sets browsable instead of overwhelming.

Highlights

  • Strict CI gating out of the box. analyse --fail-on default lowered error → advisory. A clean CI run no longer hides warning- or advisory-tier findings - they exit 1 by default. Pass --fail-on error or set minimumSeverity.analyse: error in .gruff-php.yaml to restore the old behaviour.
  • Per-command threshold config. minimumSeverity: block in .gruff-php.yaml lets you pin exit-code policy per command (analyse | report | dashboard) once instead of remembering --fail-on on every invocation. Precedence: CLI flag > YAML > binary default.
  • Visibility without scoring. rules.<id>.excludeFromScore: true keeps a rule running and surfaces its findings in every report, but those findings stop tanking the composite score. Distinct from enabled: false, which silences the rule entirely. Useful when a team has decided a rule is informational but still wants to see what it catches.
  • list-rules <ruleId> detail view. A new positional arg renders the rule's defaults, escape-hatch config paths (rules.<id>.options.*, enabled, excludeFromScore), and catalogued false-positive shapes. Typos suggest near matches and exit 2. Pass --format=json for structured triage tooling.
  • Triage surface for large scans. Branch-review reporters render "Top 5 improved / Top 5 regressed" rules before the composite score. analyse --format=text adds a footer hint pointing at summary when findings exceed 50. Finding::stableIdentity is a line-shift-resilient sibling to fingerprint for diff tooling.

Breaking Changes

Five user-visible breaks. Each requires action:

1. .gruff-php.yaml requires schemaVersion:

# Before - 0.1.4 and earlier
minimumPhpVersion: 8.3
rules:
  size.file-length: ...

# After - 0.2.0
schemaVersion: gruff-php.config.v0.1
minimumPhpVersion: 8.3
rules:
  size.file-length: ...

Configs missing the key fail to load with: Config key "schemaVersion" is required. Add 'schemaVersion: gruff-php.config.v0.1' to the top of .gruff-php.yaml, or regenerate with 'gruff-php init --force'.

Migration effort: one line per config file, or one gruff-php init --force invocation (preserves your existing paths.ignore, custom rule tunings, and allowlists).

2. analyse --fail-on default lowered error → advisory

CI jobs running gruff-php analyse with no --fail-on flag now exit 1 on any advisory finding that previously exited 0. The rationale is the cross-port "show everything, fail on anything for gating commands" philosophy.

# Option A: restore the previous gate
minimumSeverity:
  analyse: error

# Option B: opt into the new default explicitly (no-op, but documents intent)
minimumSeverity:
  analyse: advisory

Or pass --fail-on error on every CI invocation.

Migration effort: zero if you already pass --fail-on explicitly. Otherwise, decide whether new CI behaviour matches your policy and pin the threshold either way.

3. JSON output schemas bumped to v2

Both gruff-php analyse --format=json and gruff-php summary --format=json advertise gruff.analysis.v2 / gruff.summary.v2 and have renamed per-severity keys:

- "findings": { "advisories": 12, "warnings": 3, "errors": 1 }
+ "findings": { "advisory": 12, "warning": 3, "error": 1 }

Pillar and file-score payloads use the same singular names everywhere.

Migration effort: update consumer parsers (key names + the schemaVersion literal). No backward-compatible aliases - v1 consumers must update.

4. naming.parameter-type-name rule retired

Adopters with a rules.naming.parameter-type-name block in their config will hit Unknown rule id "naming.parameter-type-name". at load time.

# Remove this entire block from your .gruff-php.yaml
rules:
  naming.parameter-type-name:
    enabled: true

The rule produced too many domain-DTO false positives without a reliable signal. The gruff-py port retires the sibling rule in lockstep. PHP naming-rule count drops 12 → 11. Rationale and reversibility plan in ADR-014.

Migration effort: delete the block. Findings disappear from baselines and reports automatically.

5. waste.one-line-method defaults tightened

New defaults match gruff-php's own self-tuning:

# Old (implicit)                          # New (implicit)
minInFileCallers: 0                       minInFileCallers: 2
namedAlternativeFactoryExempt: false      namedAlternativeFactoryExempt: true

Most projects will see fewer findings (named factory pairs like Money::fromCents() / Money::fromDollars() and same-file wrappers stop reporting). Projects that pinned the old defaults explicitly can drop the pin or keep it.

Migration effort: zero. If your scoring suddenly improves, that's why.

Other Notable Changes

  • modernisation.phpdoc-mixed-overuse accepts precise envelope shapes. array{entries: list<array<string, mixed>>, total: int|null, complete: bool} no longer fires (one or more non-mixed sibling fields signals the leaf-mixed is intentional). Loose shapes still fire: array<string|int, mixed>, Collection<mixed>, array{value: mixed}.
  • Per-rule remediation messages name their escape hatch. Findings now include "If this is intentional, add it to rules.<id>.options.<key> in .gruff-php.yaml" so users can act on a finding without grepping for the right config path.
  • init scaffolds default accepted abbreviations. Fresh .gruff-php.yaml files no longer flood naming.abbreviation-allowlist with universal tokens (id, url, db, etc.).
  • HTML and Markdown reporters render pillar tables. Per-severity columns instead of single-count rows; easier to read at a glance.

Upgrade

# 1. Update Composer
composer update blundergoat/gruff-php --with-dependencies

# 2. Pick one: regenerate config (preserves your tunings)
vendor/bin/gruff-php init --force

# 2'. Or edit by hand - add this line at the top of .gruff-php.yaml
#     schemaVersion: gruff-php.config.v0.1

# 3. Confirm version
vendor/bin/gruff-php --version
# → gruff-php 0.2.0

After upgrade, run a scan to see what shifts. Use the new triage flow:

# Big scan? See the per-rule breakdown
vendor/bin/gruff-php summary

# Drill into one rule's defaults + escape hatches
vendor/bin/gruff-php list-rules naming.identifier-quality

If a CI job that passed on 0.1.x now fails, you are seeing the lowered --fail-on default. Pick your policy: pass --fail-on error on the command, or set minimumSeverity.analyse: error in .gruff-php.yaml. Either is fine; the YAML form documents intent for future maintainers.

Troubleshooting

Symptom Cause Fix
Config key "schemaVersion" is required. Pre-0.2.0 config Add schemaVersion: gruff-php.config.v0.1 to top of file, or run gruff-php init --force.
Unknown rule id "naming.parameter-type-name" Retired rule referenced Delete the rules.naming.parameter-type-name: block.
CI fails where it passed before Lowered analyse --fail-on default Pass --fail-on error or set minimumSeverity.analyse: error.
JSON consumer misses advisories/warnings/errors keys Schema bumped to v2 (singular keys) Update parser; new keys are advisory/warning/error.
Config key "minimumSeverity.summary" is rejected Non-gating command name Use analyse, report, or dashboard - summary and init don't gate exit code.