Implementation Reference
Specs: S001, S002, S003, S004, S005, S006, S007, S008, S010, S011
This page maps the public CLI behavior to the implementation surface in app/. It is intended as the complete hand-off reference for maintainers, plugin authors, and deployment owners.
Commands
init
Implementation: app/Commands/InitCommand.php
parity init creates parity.yaml in the current working directory. It does not overwrite an existing file. The generated template includes:
settings.namespace_rootssettings.source_extensionsettings.test_suffixsettings.test_extensionsettings.namespace_separatorcoverage_xml: [.parity/per-test, parity-coverage.json, coverage-xml, clover.xml, cobertura.xml]min_coverage- optional
testblock for per-test report generation - one example
structureblock withpaths.source,paths.test, and rules
Exit behavior:
| Condition | Exit |
|---|---|
| New config written | 0 |
| Config already exists | 0 |
| Current directory cannot be resolved | 1 |
| Config file cannot be written | 1 |
check
Implementation: app/Commands/CheckCommand.php
parity check reads a config file and an existing coverage report. It does not run tests.
Options:
| Option | Default | Behavior |
|---|---|---|
--config=path/to/parity.yaml | ./parity.yaml discovered from current/project root | Sets the config path and makes relative paths resolve from that config's directory. |
--format=table | table | Renders human-readable structure tables and a summary. |
--format=json | table | Emits a single JSON document with no warnings, headings, ANSI tags, or extra prose. |
--show-tests | false | Displays individual test names when the selected coverage source includes attribution data. Has no effect for Clover or Cobertura. |
Runtime sequence:
- Resolve the project root from
--configor the current directory. - Parse YAML with clean error handling for invalid YAML.
- Build
Settingsfrom top-level config. - Load project, global, and Composer plugins into the rule registry.
- Resolve the first existing coverage candidate.
- Read coverage through
ParityPerTestCoverageReaderfor directories withindex.json,PhpUnitXmlCoverageReaderfor directories withindex.xml,ParityJsonCoverageReaderfor JSON files, andCoverageReaderfor aggregate XML files. - Resolve each structure's rules, auto-prepending
test-existsand auto-adding attribution rules when the selected coverage source exposes per-test line attribution. - Map every source file to its expected test file.
- Evaluate rules and render table or JSON output.
- Return failure if any enforced rule fails or global coverage is below threshold.
Configuration
Implementation: app/Settings/Settings.php
Top-level keys:
| Key | Type | Default | Purpose |
|---|---|---|---|
settings | map | {} | File naming and namespace/path behavior. |
coverage_xml | string or list | [.parity/per-test, parity-coverage.json, coverage-xml, clover.xml, cobertura.xml] | Coverage candidates, checked in order. Prefer attribution-capable sources first. |
min_coverage | number | 80 | Default per-file minimum for legacy/generated rules. |
min_coverage_global | number or omitted | null | Optional project-level coverage threshold. |
min_matched_coverage | number or omitted | null | Optional default for matching-test-only coverage. |
structure | list | [] | Source/test directory mappings and rules. |
settings keys:
| Key | Type | Default | Purpose |
|---|---|---|---|
namespace_roots | map | { app: App, tests: Tests } | Maps path roots to identifier roots. |
source_extension | string | .php | Source file extension discovered in source paths. |
test_suffix | string | Test | Suffix appended to source basename. |
test_extension | string | same as source_extension | Test file extension. |
namespace_separator | string | \\ | Identifier separator for generated FQCN-like names. |
Structure formats:
structure:
- name: "Unit Services"
paths:
source: "app/Services"
test: "tests/Unit/Services"
rules:
- enforce-coverage-link
- minimum-coverage:
min: 80
file_map:
"Auth/LoginService.php": "Auth/LoginServiceTest.php"Legacy structure keys remain supported:
structure:
- name: "Legacy Actions"
source_path: "app/Actions"
test_path: "tests/Unit/Actions"
enforce_attribute: "PHPUnit\\Framework\\Attributes\\CoversClass"
min_coverage: 90Path and Namespace Mapping
Implementation: app/Services/NamespaceHelper.php
NamespaceHelper normalizes path separators, strips configured source extensions, applies namespace roots, and converts source paths to expected test paths.
Examples:
| Settings | Source | Expected Test |
|---|---|---|
| PHP defaults | app/Services/Invoice.php | tests/Unit/Services/InvoiceTest.php |
| TypeScript sample | src/formatCurrency.ts | tests/formatCurrency.test.ts |
| Rust sample | src/lib.rs | tests/lib_test.rs |
file_map overrides the automatic mapping for a specific source-relative path.
Coverage Readers
Single-file XML reader
Implementation: app/Services/CoverageReader.php
Supported file formats:
| Format | Detection | Per-file coverage | Global coverage | Per-test attribution |
|---|---|---|---|---|
| Parity per-test directory | Existing configured directory with index.json | merged from covered line sets | merged from all source files | Yes |
| Parity JSON | Existing configured .json file | from coveragePercent or computed from covered lines | from globalPercent | Yes |
| Clover XML | Any existing configured file with Clover <file><metrics> nodes | coveredstatements / statements * 100 | project <metrics files="..."> | No |
| Cobertura XML | Any existing configured file with Cobertura <class filename line-rate> nodes | line-rate * 100, or line-hit fallback | root <coverage line-rate> | No |
Malformed XML, missing files, unreadable files, missing metrics, and empty paths return empty/null results without throwing.
Attribution-capable readers
Implementation: app/Services/ParityPerTestCoverageReader.php, app/Services/ParityJsonCoverageReader.php, app/Services/PhpUnitXmlCoverageReader.php
If a configured coverage candidate is a directory containing index.json, Parity treats it as its native per-test report format. If the candidate is a .json file, Parity treats it as parity-coverage.json. If the candidate is a directory containing index.xml, Parity treats it as PHPUnit XML coverage.
All three attribution-capable formats provide:
- per-file line coverage percentages
- project/global coverage
- per-line executable counts
- test names that covered each line
testsByFiledata for attribution and incidental coverage reporting
test
Implementation: app/Commands/TestCommand.php, app/Services/ParityTestWorkspace.php
parity test runs each expected test file individually, normalizes the resulting single-test coverage artifact into Parity's native per-test report format, writes a report directory, and then runs parity check against that directory by default.
Config surface:
test:
command: "./vendor/bin/pest {test_abs} --coverage-clover={coverage}"
coverage: ".parity/tmp/{slug}.xml"
reports: ".parity/per-test"
timeout: 300Supported placeholders are {test}, {test_abs}, {coverage}, {slug}, and {project_root}. --output overrides test.reports, --timeout overrides the default 300-second process timeout, and --no-check stops after report generation.
ParityTestWorkspace validates artifact ownership, every existing path component, and report/coverage separation; removes runner artifacts after every attempt; writes into a sibling staging directory; atomically publishes only complete report sets; and preserves the previous set on failure. The reader rejects unsupported schema versions, mismatched test IDs, and report or source paths that escape the declared report root.
Rules
Built-in rules are registered in app/Providers/AppServiceProvider.php.
| Rule | Implementation | Enforced | Parameters | Output Column |
|---|---|---|---|---|
test-exists | TestExistsRule | Yes | none | ∃ |
enforce-coverage-link | EnforceCoverageLinkRule | Yes | linkers, attribute | Link |
minimum-coverage | MinimumCoverageRule | Yes | required min number 0..100 | Cov |
matched-coverage | MatchedCoverageRule | Yes | optional min number 0..100 | Match |
coverage-attribution | CoverageAttributionRule | No | optional show_names | #, Other |
Rule config formats:
rules:
- test-exists
- minimum-coverage:
min: 80
- name: minimum-coverage
min: 90Unknown rule names or invalid parameters fail cleanly before evaluation.
Coverage Linkers
Implementation: app/Services/CoverageLinkers/
| Linker | Config Name | Supports | Declarations |
|---|---|---|---|
PestCoversLinker | pest-covers | Pest-style files without class declarations that use it(), test(), describe(), beforeEach(), or beforeAll() | ->covers(Foo::class), ->coversClass(Foo::class), covers(Foo::class) |
PhpAttributeLinker | php-attribute | PHP files with class declarations | #[CoversClass(Foo::class)] before the test class |
CoverageLinkerRegistry::fromConfig() accepts:
rules:
- enforce-coverage-link:
linkers: [auto]
attribute: PHPUnit\Framework\Attributes\CoversClassValid linkers values are auto, pest-covers, and php-attribute. auto uses both built-in linkers. attribute customizes the PHP attribute class name for the PHP attribute linker.
Class reference resolution supports:
- imported class aliases from
use Foo\Bar as Baz; - imported short names from
use Foo\Bar; - relative names in the current namespace
- leading-backslash absolute names
- string literal FQCNs
Plugin System
Implementation: app/Services/PluginLoader.php
Load order:
- project-local
.parity/plugins/*.php - user-global
~/.parity/plugins/*.php - Composer packages declaring
extra.parity.rules
Project/global plugin files must return either a RuleInterface instance or an array of RuleInterface instances.
Composer plugin declaration:
{
"extra": {
"parity": {
"rules": [
"Acme\\Parity\\Rules\\NamingRule"
]
}
}
}Plugin warnings are printed in table mode with a Plugin: prefix. They are suppressed in JSON mode so stdout remains parseable.
Plugins are trusted executable PHP. Do not load unreviewed plugins in public CI.
Output Contracts
Table mode
Table mode renders:
- optional global coverage message
- one section per structure
- source/test paths for each structure
- a dynamic table based on resolved rules
- a final summary table
- optional unmatched-test warnings when attribution data is available
The first two columns are always Source and Test; the final column is always OK. Rule columns are added from RuleInterface::columnHeader().
Summary rows use Per rule for structure-specific thresholds and derive OK or FAIL from the evaluated rules, so they cannot contradict file rows with overrides. Files with zero executable lines are excluded from minimum and average values when the coverage format exposes executable-line metadata.
JSON mode
JSON mode emits only this top-level shape:
{
"passed": true,
"global_coverage": null,
"min_coverage_global": null,
"structures": []
}Each structure contains name, source_path, test_path, and files. Each file contains source, test, passed, and rules. Each rule result contains passed, value, and error.
Release and Runtime Providers
config/app.php reads APP_VERSION or VERSION, then falls back to unreleased.
config/commands.php is the Laravel Zero command registration file. Public commands are registered through this runtime configuration and backed by app/Commands/InitCommand.php, app/Commands/CheckCommand.php, and app/Commands/TestCommand.php.
app/Providers/GitVersionFallbackProvider.php binds git.version to the same VERSION fallback behavior for packaged/runtime contexts.
dev/release-version.sh requires curated ## [Unreleased] notes, handles dry-run and real version bumps, promotes those notes into the dated release section, verifies version metadata, creates an annotated tag, and can atomically push the release commit and tag. It also rejects dirty worktrees, duplicate tags, and releases outside main.
Samples
The sample matrix validates representative layouts and coverage formats:
| Sample | Language/Framework | Coverage Fixture |
|---|---|---|
samples/php | Plain PHP | Clover |
samples/laravel | Laravel-style PHP | Clover |
samples/vite | Vite/TypeScript | Cobertura |
samples/adonisjs | AdonisJS-style TypeScript | Clover |
samples/rust | Rust-style layout | Clover |
samples/phpunit | Runnable PHPUnit app | Clover |
samples/pest | Runnable Pest app | Clover plus Pest ->covers() link validation |
samples/jest | Runnable Jest app | Clover |
samples/mocha | Runnable Mocha app | Clover |
samples/vitest | Runnable Vitest app | Clover |
samples/cargo | Runnable Cargo/Rust crate | Cobertura |

