Releases: blundergoat/gruff-php
v0.4.1
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-phpdocaccepts local comments by default; require strict API PHPDoc viarequirePhpdocForApiConstants/apiPathPatterns. reportrejects unknown rule filters ---include-rule/--exclude-ruletypos fail fast (exit 2) up front, likeanalyse.- 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.1Resetting gruff-php.yaml is required since a rubric was removed.
vendor/bin/gruff-php init --force
v0.4.0
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, anddesign.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-rulenow 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/hookdropped 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-includefixed paths with newtreatGlobalConstantsAsFixed/dynamicPathConstantsoptions;sql-concatenationunderstandsprepare(), safe interpolation, non-SQLquery()), sensitive-data (high-entropy-stringidentifier literals;pii-test-fixturesynthetic 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.0v0.3.1
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 jsongives editors and coding agents a stable, self-describing contract;--baseline/--diff/--sinceuse value-independent identities so pre-existing findings stay suppressed and only new ones surface. - Changed-code feedback is fairer.
--changed-scope=symbolreturns only findings on changed symbols (plus anything new vs baseline) and drops untouched file/class aggregates; new--changed-scope=filekeeps 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-classnow recognisesFQCN::methodcontrollers under YAML_controllerand the 4.1+ top-levelcontroller:shortcut; service-ids and legacy strings are still ignored. - Text reports lead with the numbers.
analyseandsummarytext output now show the composite score and finding counts up top, with the subcommand in the header. JSON output is unchanged.
v0.3.0
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.
analysenow supports--changed-ranges,--since, bare--diff,--diff -, and--changed-scope=symbol|hunk. JSON addssuppressedCountwhen findings are outside the changed region. - Ignore rules are easier to debug.
paths.ignorenow applies to directory scans, explicit files, diff scans, and changed-region scans. JSON addsignoredPathDetails, andcheck-ignoreexplains why a path was skipped. - Baselines show movement. Baseline reports now split findings into
new,unchanged, andresolved. Use--baseline-include-absentwhen you want resolved entries in the rendered output. - CI can fail only on new debt.
failureConditions:sets count caps, and--fail-on-newfails 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: trueRefresh baselines if they contain complexity.npath findings:
vendor/bin/gruff-php analyse --generate-baseline --fail-on none2. 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.npathbut 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, andtest-quality.tautological-type-assertionare nowerror. - 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.acceptedBooleanNameslets teams allow names likevalid()when changing callers would be too costly. - Documentation rules are clearer.
docs.return-commentnow checks useful@returntext,docs.missing-param-tagcovers documented non-public code, andarray<string, mixed>|nullJSON bags no longer triggerphpdoc-mixed-overuse. - Some scans can reuse cached results.
.gruff-cache/stores unchanged-file results for runs without project-wide rules. Use--no-cacheto 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 summaryIf 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 noneFor 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.phpTo try presets:
schemaVersion: gruff-php.config.v0.1
extends: gruff.recommended
rules:
size.method-length:
threshold: 80Troubleshooting
| 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
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-ondefault lowerederror → advisory. A clean CI run no longer hides warning- or advisory-tier findings - they exit 1 by default. Pass--fail-on erroror setminimumSeverity.analyse: errorin.gruff-php.yamlto restore the old behaviour. - Per-command threshold config.
minimumSeverity:block in.gruff-php.yamllets you pin exit-code policy per command (analyse | report | dashboard) once instead of remembering--fail-onon every invocation. Precedence: CLI flag > YAML > binary default. - Visibility without scoring.
rules.<id>.excludeFromScore: truekeeps a rule running and surfaces its findings in every report, but those findings stop tanking the composite score. Distinct fromenabled: 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=jsonfor 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=textadds a footer hint pointing atsummarywhen findings exceed 50.Finding::stableIdentityis a line-shift-resilient sibling tofingerprintfor 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: advisoryOr 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: trueThe 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: trueMost 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-overuseaccepts 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. initscaffolds default accepted abbreviations. Fresh.gruff-php.yamlfiles no longer floodnaming.abbreviation-allowlistwith 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.0After 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-qualityIf 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
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-phpin consuming projects by loading Composer's generated_composer_autoload_pathbefore falling back to source-checkout autoload locations. - Preserved direct source-checkout usage, so
php bin/gruff-php --versionand local development commands still boot normally from this repository. - Added an installed-dependency regression test that creates a throwaway consumer project, installs
blundergoat/gruff-phpthrough a path repository, verifies the package copy has no nestedvendor/autoload.php, and runsvendor/bin/gruff-php init.
v0.1.2
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-flowaudit 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, includinginit, dedicated Console test files, and
skill-playbooksrouting. - Updated dangerous-command hook self-tests to use the real
healthkit/healthkitrepository identifier instead of placeholder
example-org/example-repovalues. - Broadened the
symfony/yamlruntime constraint to match the other Symfony
components:^6.4 || ^7.0 || ^8.0.
v0.1.1
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-rulesto see them all. - Commands:
analyse,summary,report,dashboard,list-rules,init. analyseoutput 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
initwrites a.gruff-php.yamlfrom registry defaults, with an ignore list covering agent harness dirs, generated reports, fixtures, and vendored copies.--forcekeeps 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, ordashboardin a terminal with no config, it now offers to runinitfor 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, andtestdox-readability(minWords 2). summarynow tells you how to baseline -analyse --generate-baselineto record current findings as known debt, or--no-baselineto audit without one.composer audit:dependenciesruns insidecomposer checkand CI verify, failing the build on known security advisories. New helper scriptsdependency-install.shanddependency-update.shwrap 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
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-rulesto 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.yamlwith 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 --helpRequires 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 dashboardDefault 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).