Skip to content

Releases: blundergoat/gruff-php

v0.4.1

13 Jun 22:17
ce2a22c

Choose a tag to compare

gruff-php 0.4.1 - rule-rubric precision, enum-candidate retired, src namespace consolidation

Headline

0.4.1 focuses on rule-rubric precision: fewer false positives and fewer over-severe findings without disabling the rules that catch real maintainability problems.

Highlights

  • BREAKING: Removed modernisation.enum-candidate - Constant-only classes are no longer pushed toward enums.
  • Tighter rubrics (docs/naming/size/complexity/modernisation/dead-code) - Fewer false positives and over-severe findings; real waste still reports.
  • Constant PHPDoc is configurable - docs.missing-constant-phpdoc accepts local comments by default; require strict API PHPDoc via requirePhpdocForApiConstants / apiPathPatterns.
  • report rejects unknown rule filters - --include-rule/--exclude-rule typos fail fast (exit 2) up front, like analyse.
  • Namespace consolidation - src/ reorganised into six packages; public contracts unchanged, only direct internal imports change (GruffPhp\Rule\Rules\).

Upgrade

composer require --dev blundergoat/gruff-php:^0.4
vendor/bin/gruff-php --version   # gruff-php 0.4.1

Resetting gruff-php.yaml is required since a rubric was removed.

vendor/bin/gruff-php init --force

v0.4.0

10 Jun 20:09
9ecb9e1

Choose a tag to compare

gruff-php 0.4.0 - project rules retired, per-file cache on by default, fast single-file scans

Release date: 2026-06-11

Headline

0.4.0 makes per-edit feedback fast and trustworthy: the four whole-project rules are removed (breaking), the per-file cache engages on every default run, and single-file scans drop from tens of seconds to under 0.1s. --include-rule/--exclude-rule now genuinely select rule execution, and eight per-unit rules stop flagging legitimate framework patterns.

Highlights

  • BREAKING: removed dead-code.unused-internal-class, dead-code.unused-internal-constant, dead-code.unused-internal-function, and design.single-implementor-interface - sampled false-positive rates of 45-100% on framework code, and each forced a 13-31s whole-project reparse per single-file scan. The catalogue is now 129 rules.
  • BEHAVIOUR CHANGE: analyse --include-rule/--exclude-rule now select rule execution - excluded rules neither run nor count toward --fail-on, failureConditions, scoring, or baselines; unknown rule ids are usage errors (exit 2).
  • Per-file cache on by default: warm whole-repo corpus scans dropped 33-82s → 1.8-5.0s with byte-identical findings; single-file analyse/hook dropped 13-31s → under 0.1s.
  • Eight per-unit precision fixes: naming/test-quality (snake_case test bases and boolean names, closure params, superglobal writes), security (variable-include fixed paths with new treatGlobalConstantsAsFixed/dynamicPathConstants options; sql-concatenation understands prepare(), safe interpolation, non-SQL query()), sensitive-data (high-entropy-string identifier literals; pii-test-fixture synthetic markers).
  • Config-less runs no longer hang or write config on piped stdin.

Upgrade

composer require --dev blundergoat/gruff-php:^0.4
vendor/bin/gruff-php --version   # gruff-php 0.4.0

v0.3.1

08 Jun 20:55
f036932

Choose a tag to compare

gruff-php 0.3.1 - agent-hook contract, fairer changed-code feedback, new test-quality rule

0.3.1 gives editors and coding agents a stable JSON hook contract for changed-code feedback. It also makes changed-region scanning fairer, adds one conservative test-quality rule, fixes Symfony YAML route and changed-region accounting edges in project-wide dead-code analysis, and moves the headline numbers to the top of text reports. No breaking changes.

Highlights

  • Agent-hook contract gruff.hook.v1. gruff-php hook --format json gives editors and coding agents a stable, self-describing contract; --baseline/--diff/--since use value-independent identities so pre-existing findings stay suppressed and only new ones surface.
  • Changed-code feedback is fairer. --changed-scope=symbol returns only findings on changed symbols (plus anything new vs baseline) and drops untouched file/class aggregates; new --changed-scope=file keeps file-level aggregates. Full scans are unchanged.
  • New rule test-quality.static-analysis-redundant-test. Advisory rule flagging tests whose main assertion only restates a static declaration (class_exists, method_exists, and similar); it recommends asserting behaviour. On by default.
  • Symfony YAML routes no longer make live controllers look dead. dead-code.unused-internal-class now recognises FQCN::method controllers under YAML _controller and the 4.1+ top-level controller: shortcut; service-ids and legacy strings are still ignored.
  • Text reports lead with the numbers. analyse and summary text output now show the composite score and finding counts up top, with the subcommand in the header. JSON output is unchanged.

v0.3.0

01 Jun 17:14
17baeae

Choose a tag to compare

gruff-php 0.3.0 - changed-code gates

analyse can now check the code a change touched or introduced. CI can block new problems while old baseline debt stays visible.

Highlights

  • Changed-code scans. analyse now supports --changed-ranges, --since, bare --diff, --diff -, and --changed-scope=symbol|hunk. JSON adds suppressedCount when findings are outside the changed region.
  • Ignore rules are easier to debug. paths.ignore now applies to directory scans, explicit files, diff scans, and changed-region scans. JSON adds ignoredPathDetails, and check-ignore explains why a path was skipped.
  • Baselines show movement. Baseline reports now split findings into new, unchanged, and resolved. Use --baseline-include-absent when you want resolved entries in the rendered output.
  • CI can fail only on new debt. failureConditions: sets count caps, and --fail-on-new fails only findings introduced by the current branch, baseline comparison, or both.
  • Configs can inherit presets. extends: gruff.recommended, gruff.starter, gruff.strict, or another YAML file can replace most of a large .gruff-php.yaml.

Breaking Changes

1. complexity.npath is retired

The rule was noisy and overlapped with cognitive, cyclomatic, and nesting checks. Configs that still reference it now fail with an unknown rule id.

# Remove this block
rules:
  complexity.npath:
    enabled: true

Refresh baselines if they contain complexity.npath findings:

vendor/bin/gruff-php analyse --generate-baseline --fail-on none

2. Synthetic design.god-method findings are retired

gruff no longer adds a second design finding when size and complexity rules flag the same method. The original size and complexity findings still appear. There is no config block to remove; refresh baselines if they contain design.god-method.

Other Changes

  • 132 rules across 11 pillars. The catalogue drops complexity.npath but adds new dead-code, security, sensitive-data, documentation, and test-quality checks.
  • Complexity now focuses on readability. Cognitive complexity errors at 20, nesting depth errors at 4, cyclomatic complexity is a warning, and Halstead plus maintainability index are advisory.
  • One root cause gets one score penalty. A method that is long, nested, and cyclomatically complex still shows every finding, but the score no longer charges three times for the same method.
  • Fake-green tests now fail error gates. test-quality.no-assertions, test-quality.sut-not-called, and test-quality.tautological-type-assertion are now error.
  • More dead-code and secret checks. New rules cover unused internal classes/functions/constants, GCP service-account JSON, and HTTP(S) URLs with embedded credentials.
  • Fewer forced public renames. rules.naming.boolean-prefix.options.acceptedBooleanNames lets teams allow names like valid() when changing callers would be too costly.
  • Documentation rules are clearer. docs.return-comment now checks useful @return text, docs.missing-param-tag covers documented non-public code, and array<string, mixed>|null JSON bags no longer trigger phpdoc-mixed-overuse.
  • Some scans can reuse cached results. .gruff-cache/ stores unchanged-file results for runs without project-wide rules. Use --no-cache to disable it.
  • The mission is explicit. README, docs/mission.md, agent instructions, and ADR-017 now tie rule choices to making AI-generated code easier for humans to review.

Upgrade

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

# 2. Confirm the binary version
vendor/bin/gruff-php --version
# gruff-php 0.3.0

# 3. Remove retired rules if present
grep -n "complexity.npath" .gruff-php.yaml || true

# 4. Inspect the new finding and score surface
vendor/bin/gruff-php analyse --fail-on none
vendor/bin/gruff-php summary

If you use a baseline:

vendor/bin/gruff-php analyse --baseline=gruff-baseline.json --fail-on none
vendor/bin/gruff-php analyse --generate-baseline --fail-on none

For pull-request gates:

vendor/bin/gruff-php analyse --diff-vs=origin/main --baseline=gruff-baseline.json --fail-on-new
vendor/bin/gruff-php check-ignore --format=json path/to/file.php

To try presets:

schemaVersion: gruff-php.config.v0.1
extends: gruff.recommended

rules:
  size.method-length:
    threshold: 80

Troubleshooting

Symptom Fix
Unknown rule id "complexity.npath" Delete the rules.complexity.npath block.
Baseline mentions complexity.npath or design.god-method Regenerate the baseline or remove those entries.
--fail-on-new exits during setup Pass --diff-vs <ref>, --baseline <file>, or both.
A path is skipped even when passed directly Check paths.ignore; confirm with check-ignore.
CI prints Failed: ... with a threshold message Fix findings, raise the cap, or baseline reviewed debt.

v0.2.0

27 May 19:09
ffb82c6

Choose a tag to compare

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.

v0.1.3

24 May 11:08
03cc924

Choose a tag to compare

gruff-php 0.1.3

0.1.3 is a patch release for the Composer-installed binary bootstrap. It fixes the install path users hit when they run composer require --dev blundergoat/gruff-php and then execute vendor/bin/gruff-php init.

There are no new analyzer rules, CLI options, report schema changes, or config format changes in this release.

Highlights

  • Fixed vendor/bin/gruff-php in consuming projects by loading Composer's generated _composer_autoload_path before falling back to source-checkout autoload locations.
  • Preserved direct source-checkout usage, so php bin/gruff-php --version and local development commands still boot normally from this repository.
  • Added an installed-dependency regression test that creates a throwaway consumer project, installs blundergoat/gruff-php through a path repository, verifies the package copy has no nested vendor/autoload.php, and runs vendor/bin/gruff-php init.

v0.1.2

24 May 10:36
3c359be

Choose a tag to compare

gruff-php 0.1.2

0.1.2 is a maintenance release that keeps the project harness, release
metadata, and documentation aligned with the current gruff-php CLI surface.
There are no new analyzer rules in this release; the practical benefit is less
stale guidance for agents and a cleaner setup for future work.

Highlights

  • Updated Codex and Claude instruction files to use the packaged
    @blundergoat/goat-flow audit CLI instead of the TypeScript source entry
    point.
  • Expanded the documented app/quality surface to include real project files
    such as .gruff-php.yaml, phpstan.neon.dist, scripts, package-lock.json,
    and GitHub workflows.
  • Refreshed goat-flow architecture and code-map documentation for the current
    CLI shape, including init, dedicated Console test files, and
    skill-playbooks routing.
  • Updated dangerous-command hook self-tests to use the real
    healthkit/healthkit repository identifier instead of placeholder
    example-org/example-repo values.
  • Broadened the symfony/yaml runtime constraint to match the other Symfony
    components: ^6.4 || ^7.0 || ^8.0.

v0.1.1

24 May 08:48

Choose a tag to compare

gruff-php 0.1.1

An onboarding-focused follow-up to 0.1.0. The headline additions are an init command, an interactive prompt when you run a scan without a config, expanded docs, and a lockfile security audit in CI.

gruff-php is a heuristic PHP code-quality scanner that reports findings for the terminal, CI, GitHub annotations, SARIF, HTML, or a local dashboard. Pair it with PHPStan, PHPUnit, PHP-CS-Fixer, or Psalm - it doesn't replace type checking or tests.

What you get

  • 120 rules across 11 pillars: size, complexity, maintainability, dead-code, naming, documentation, modernisation, security, sensitive-data, test-quality, design. Run list-rules to see them all.
  • Commands: analyse, summary, report, dashboard, list-rules, init.
  • analyse output formats: text, json, html, markdown, github, hotspot, sarif.
  • YAML config at .gruff-php.yaml, with strict unknown-key rejection.
  • Baselines to suppress known findings without disabling rules.
  • Branch review: --diff, --diff-vs=<base>, --changed-only.
  • Optional Infection mutation analysis, with baselines and budgets.

New in 0.1.1

  • init writes a .gruff-php.yaml from registry defaults, with an ignore list covering agent harness dirs, generated reports, fixtures, and vendored copies. --force keeps any existing ignore list and is required to overwrite a legacy .gruff.yaml. --project-root <dir> writes somewhere other than the current directory.
  • If you run analyse, summary, report, or dashboard in a terminal with no config, it now offers to run init for you. The prompt only fires after option validation, so a bad command no longer leaves a stray config file. Prompt text goes to STDERR, so JSON, SARIF, and HTML on STDOUT stay parseable.
  • Three test-quality rules now run by default: multiple-aaa-cycles (minCycles 3), mocking-domain-object, and testdox-readability (minWords 2).
  • summary now tells you how to baseline - analyse --generate-baseline to record current findings as known debt, or --no-baseline to audit without one.
  • composer audit:dependencies runs inside composer check and CI verify, failing the build on known security advisories. New helper scripts dependency-install.sh and dependency-update.sh wrap the Composer commands; the release preflight script is now stricter.
  • README rewritten. New docs/ guides cover the rule catalogue, CI integration, configuration, output formats, dashboard, naming conventions, and releasing.

v0.1.0

23 May 05:05
5383f38

Choose a tag to compare

gruff-php 0.1.0

First public release. gruff-php is an opinionated PHP code-quality analyzer that scans your project, scores findings across rule pillars, and prints reports for the terminal, CI, GitHub annotations, SARIF, static HTML, or a local dashboard.

It is heuristic tooling. Use it alongside PHPStan, PHPUnit, PHP-CS-Fixer, or Psalm - not as a replacement for type checking or tests.

What you get

  • 120 rules across 11 pillars: size, complexity, maintainability, dead-code, naming, documentation, modernisation, security, sensitive-data, test-quality, and design. Run php bin/gruff-php list-rules to see the full catalogue.
  • Commands: analyse, summary, report, dashboard, list-rules.
  • Output formats for analyse: text, json, html, markdown, github, hotspot, sarif.
  • YAML config at .gruff-php.yaml with strict unknown-key rejection.
  • Baselines to suppress known findings without disabling rules.
  • Branch review with --diff, --diff-vs=<base>, and --changed-only.
  • Optional Infection mutation analysis, with baselines and budgets.
  • Stable schemas: gruff.analysis.v1, gruff.summary.v1, gruff.baseline.v1.

Install

composer require --dev devgoat/gruff-php
vendor/bin/gruff-php --help

Requires PHP ^8.3. CI runs on PHP 8.3 and 8.4.

Quick start

php bin/gruff-php analyse                                  # scan the project
php bin/gruff-php analyse --format markdown > report.md    # PR comment
php bin/gruff-php analyse --format sarif > gruff.sarif     # code scanning
php bin/gruff-php report --format html --output gruff.html # static report
php bin/gruff-php dashboard                                # local dashboard

Default fail threshold is error. Use --fail-on warning, --fail-on advisory, or --fail-on none to change it. Exit codes: 0 clean, 1 finding hit the threshold, 2 run diagnostic (config/parse/diff/baseline/mutation error).