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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Added Phase 4.7 repository cutover with root-native Composer, PHPUnit, PHPStan, PHP-CS-Fixer, Deptrac, release-validation, package split validation, supply-chain checks and developer-experience commands; removed the tracked workspace directory and EvolvePHP 1 runtime files from the 2.x working tree while preserving master and Git history. The PHP 8.4/8.5 root quality matrix now runs from the repository root; the protected `Workspace quality (PHP 8.4)` and `Workspace quality (PHP 8.5)` context names are retained for governance compatibility.

### Audit foundation

- Added Phase 7.1 Evolve Audit foundation inside `evolvephp/dev-tools`, including public pre-beta experimental `AuditSeverity`, `AuditFinding`, `AuditReport`, `AuditInspector`, `AuditRunner` and `ComposerProjectInspector` APIs for read-only root `composer.json` inspection, deterministic direct dependency/framework evidence, raw root PHP constraint preservation, Composer platform PHP review evidence, malformed Composer evidence reporting and tests proving target autoload files, Composer scripts and target PHP files are not executed or modified. Audit remains development-time tooling, not Doctor, and does not add an `evolvephp/audit` package, Composer solving, lockfile/transitive analysis, JSON output, CLI commands, remediation, Bridge adapters, remote clients or compatibility claims from raw Composer constraints.

### Component foundation

- Added Phase 5.7 Testing SDK and independent component acceptance coverage inside `evolvephp/testing`, including public pre-beta experimental `ComponentDefinitionFixture` and `ComponentEntryPointFixture` closure-backed fixtures for the accepted component contracts, no direct PHPUnit dependency in Testing source, no Module/Plugin replacement semantics, no hidden registries or lifecycle orchestration, and independent integration coverage for explicit `ModuleDefinition`, `ComposerPluginDiscovery`, app-controlled enablement, Core `ComponentBootstrapper`, real graph ordering, restricted registration, service-registry freeze, `ApplicationKernel` boot, component boot, ready and reverse shutdown. Create-project tooling, generators, Doctor, route inspection, environment inspection and broader developer tooling remain Phase 6 work.
Expand Down
8 changes: 5 additions & 3 deletions packages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

`packages/` contains the initial EvolvePHP 2 modular-monorepo package set.

The packages define Composer package identities, namespace ownership, dependency direction, lifecycle, configuration, service-container, execution-scope, runtime-neutral execution orchestration, generic Core instrumentation, minimal Core console APIs, PSR HTTP middleware and routing, response/error and health handling, explicit response emission, component identity, descriptors, graph declarations and resolution, restricted registration, lifecycle entry points, explicit component bootstrap, Composer plugin discovery, testing fixtures, development-time generators and command-output recording. Complete runtime implementation is not yet present, and the packages are not yet published.
The packages define Composer package identities, namespace ownership, dependency direction, lifecycle, configuration, service-container, execution-scope, runtime-neutral execution orchestration, generic Core instrumentation, minimal Core console APIs, PSR HTTP middleware and routing, response/error and health handling, explicit response emission, component identity, descriptors, graph declarations and resolution, restricted registration, lifecycle entry points, explicit component bootstrap, Composer plugin discovery, testing fixtures, development-time generators, read-only project audit evidence and command-output recording. Complete runtime implementation is not yet present, and the packages are not yet published.

All package manifests require PHP `^8.4`.

Expand All @@ -12,7 +12,7 @@ All package manifests require PHP `^8.4`.
| --- | --- | --- |
| `evolvephp/contracts` | `Evolve\Contracts\` | Foundational public-contract boundary, including application lifecycle, configuration, reset-participant and exception contracts, experimental shared identity vocabulary, graph declaration vocabulary, `ServiceDefinitionRegistrar`, `ComponentEntryPoint` / `ComponentBootContext` lifecycle contracts and `ComponentDefinition`. |
| `evolvephp/core` | `Evolve\Core\` | Core orchestration boundary, including the minimal application lifecycle kernel, array-backed configuration, PSR-11-readable service container, explicit execution scopes, runtime-neutral execution outcomes, generic execution-lifecycle observation hooks, runtime-neutral command foundation, `ComponentGraphResolver`, `ResolvedComponentGraph`, consumer-scoped `CapabilityProviderSelection`, restricted registration, component lifecycle coordination and explicit `ComponentBootstrapper`. |
| `evolvephp/dev-tools` | `Evolve\DevTools\` | Development-only generator boundary with public experimental `module:new` and `plugin:new` command adapters for application-owned module and plugin starter files. |
| `evolvephp/dev-tools` | `Evolve\DevTools\` | Development-only tooling boundary with public experimental `module:new` and `plugin:new` command adapters plus read-only Audit APIs for root Composer evidence. |
| `evolvephp/http` | `Evolve\Http\` | HTTP boundary with PSR HTTP interoperability, `MiddlewarePipeline`, route definitions and matching, routed handler dispatch, typed routing failures, `HttpKernel` integration with Core execution orchestration, response/error and health foundations and explicit response-emitter boundary; runtime adapters remain deferred. |
| `evolvephp/module` | `Evolve\Module\` | Module SDK boundary with the public experimental `ModuleDescriptor`, EvolvePHP-major compatibility validation, `graphDeclaration()` projection, `Module` entry point extending `ComponentEntryPoint` and explicit `ModuleDefinition`; discovery remains deferred. |
| `evolvephp/plugin` | `Evolve\Plugin\` | Plugin SDK boundary with the public experimental `PluginDescriptor`, EvolvePHP-major compatibility validation, `graphDeclaration()` projection, `Plugin` entry point extending `ComponentEntryPoint`, explicit `PluginDefinition` and `ComposerPluginDiscovery` for packaged plugin metadata. |
Expand All @@ -27,7 +27,7 @@ The package graph follows an inward dependency principle:
- `contracts` is the innermost package.
- `core`, `module` and `plugin` depend inward on `contracts`.
- `http` depends inward on `contracts` and `core`.
- `dev-tools` may depend on `contracts`, `core`, `module` and `plugin` for development-time generators.
- `dev-tools` may depend on `contracts`, `core`, `module` and `plugin` for development-time generators and audit tooling.
- `testing` may depend on the five production packages for development support.

There is no production dependency on Testing.
Expand Down Expand Up @@ -86,6 +86,8 @@ Testing provides concrete SDK surfaces for component tests. `ComponentDefinition

DevTools provides public experimental `ModuleNewCommand` and `PluginNewCommand`. The commands accept an explicit project root and one ASCII StudlyCase name token, generate application-owned module or plugin starter files with deterministic `app/...` component identifiers, refuse invalid usage and existing targets, and do not run Composer, run Git, edit Composer manifests, discover components or auto-enable generated definitions.

DevTools also provides the public experimental Audit foundation. `AuditRunner` composes explicitly supplied inspectors in registration order and validates an explicit existing target root. `ComposerProjectInspector` reads only the target root `composer.json` as data, reports direct runtime/development dependencies, direct Laravel/Symfony/CakePHP/Yii/EvolvePHP package evidence, raw root PHP constraint evidence and Composer platform PHP review evidence, while avoiding framework boot, Composer execution, target autoloading, shell commands, `.env` loading, lockfile/transitive graph analysis, target writes and compatibility claims from raw Composer constraints.

Testing also provides `Evolve\Testing\Console\RecordingCommandOutput`, a public experimental in-memory `CommandOutput` implementation for command tests. It records normal and error lines in order without replacing PHPUnit assertions or becoming a general CLI testing framework.

EvolvePHP 2 does not yet provide full module/plugin runtime managers, descriptor serialization, component versions, dependency version ranges, Composer semantic-version constraint evaluation, runtime CLI adapters beyond the explicit skeleton shell, Symfony Console integration, broad Doctor checks, generator discovery, automatic component enablement, Bridge, Insight, Observe, telemetry storage/export, persistent-worker concurrency guarantees or production-ready framework runtime behavior. DevTools does not add automatic discovery, application bootstrapping, route generation, Composer mutation or package publication. Testing does not add a general testing framework.
Expand Down
31 changes: 31 additions & 0 deletions packages/dev-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,37 @@ PHP `^8.4`

`evolvephp/contracts`, `evolvephp/core`, `evolvephp/module`, `evolvephp/plugin`

## Audit Foundation

The package provides a public experimental Audit foundation under
`Evolve\DevTools\Audit`.

`AuditRunner` accepts explicitly supplied inspectors and an explicit target
project root. It performs no automatic discovery. `ComposerProjectInspector`
inspects only the target root `composer.json`; it does not scan parent
directories, nested manifests, lock files or transitive dependency graphs.

Audit treats the target project as data. It does not include target PHP files,
include the target `vendor/autoload.php`, bootstrap Laravel, Symfony, CakePHP,
Yii, EvolvePHP or custom application code, run Composer, run scripts, invoke
shell commands, load `.env`, write caches or modify target files.

The first inspector reports structured findings for root Composer evidence:
direct runtime and development dependencies, direct framework package evidence
for Laravel, Symfony, CakePHP, Yii and EvolvePHP, the raw root `require.php`
constraint when present, missing PHP constraint evidence, malformed Composer
evidence and `config.platform.php` review evidence.

Raw Composer constraints are preserved exactly as declared. Audit does not solve
Composer SemVer constraints and does not claim PHP, package or framework
compatibility from raw constraints. A Composer platform override is reported as
compatibility-review evidence only; it is not proof of the actual runtime PHP
version.

Current limitations: Audit has no CLI command, JSON output, remediation,
lockfile analysis, vulnerability lookup, PHP source analysis, framework
bootstrap analysis, route discovery, migration planning or Bridge integration.

## Generator Commands

The package provides two public experimental command adapters:
Expand Down
79 changes: 79 additions & 0 deletions packages/dev-tools/src/Audit/AuditFinding.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace Evolve\DevTools\Audit;

use InvalidArgumentException;

/**
* @experimental
*/
final readonly class AuditFinding
{
private const IDENTIFIER_PATTERN = '/^[a-z0-9_-]+(?:\.[a-z0-9_-]+)*$/';

/**
* @param array<string|int, mixed> $evidence
*/
public function __construct(
private string $identifier,
private AuditSeverity $severity,
private string $message,
private array $evidence,
) {
if (preg_match(self::IDENTIFIER_PATTERN, $identifier) !== 1) {
throw new InvalidArgumentException('Audit finding identifier is invalid.');
}

if (trim($message) === '') {
throw new InvalidArgumentException('Audit finding message must be non-empty.');
}

if (! self::isPlainData($evidence)) {
throw new InvalidArgumentException('Audit finding evidence must contain only plain data.');
}
}

public function identifier(): string
{
return $this->identifier;
}

public function severity(): AuditSeverity
{
return $this->severity;
}

public function message(): string
{
return $this->message;
}

/**
* @return array<string|int, mixed>
*/
public function evidence(): array
{
return $this->evidence;
}

private static function isPlainData(mixed $value): bool
{
if ($value === null || is_scalar($value)) {
return true;
}

if (! is_array($value)) {
return false;
}

foreach ($value as $nestedValue) {
if (! self::isPlainData($nestedValue)) {
return false;
}
}

return true;
}
}
13 changes: 13 additions & 0 deletions packages/dev-tools/src/Audit/AuditInspector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Evolve\DevTools\Audit;

/**
* @experimental
*/
interface AuditInspector
{
public function inspect(string $projectRoot): mixed;
}
60 changes: 60 additions & 0 deletions packages/dev-tools/src/Audit/AuditReport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Evolve\DevTools\Audit;

use ArrayIterator;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
use Traversable;

/**
* @experimental
*
* @implements IteratorAggregate<int, AuditFinding>
*/
final readonly class AuditReport implements Countable, IteratorAggregate
{
/**
* @var list<AuditFinding>
*/
private array $findings;

/**
* @param iterable<mixed> $findings
*/
public function __construct(iterable $findings)
{
$orderedFindings = [];

foreach ($findings as $finding) {
if (! $finding instanceof AuditFinding) {
throw new InvalidArgumentException('Audit reports may contain only audit findings.');
}

$orderedFindings[] = $finding;
}

$this->findings = $orderedFindings;
}

/**
* @return list<AuditFinding>
*/
public function findings(): array
{
return $this->findings;
}

public function count(): int
{
return count($this->findings);
}

public function getIterator(): Traversable
{
return new ArrayIterator($this->findings);
}
}
65 changes: 65 additions & 0 deletions packages/dev-tools/src/Audit/AuditRunner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Evolve\DevTools\Audit;

use InvalidArgumentException;

/**
* @experimental
*/
final readonly class AuditRunner
{
/**
* @var list<AuditInspector>
*/
private array $inspectors;

/**
* @param iterable<mixed> $inspectors
*/
public function __construct(iterable $inspectors)
{
$orderedInspectors = [];

foreach ($inspectors as $inspector) {
if (! $inspector instanceof AuditInspector) {
throw new InvalidArgumentException('Audit runner accepts only audit inspectors.');
}

$orderedInspectors[] = $inspector;
}

$this->inspectors = $orderedInspectors;
}

public function inspect(string $projectRoot): AuditReport
{
$realProjectRoot = realpath($projectRoot);

if ($realProjectRoot === false || ! is_dir($realProjectRoot)) {
throw new InvalidArgumentException('Audit target root must be an existing directory.');
}

$findings = [];

foreach ($this->inspectors as $inspector) {
$inspectorFindings = $inspector->inspect($realProjectRoot);

if (! is_iterable($inspectorFindings)) {
throw new InvalidArgumentException('Audit inspectors must return iterable findings.');
}

foreach ($inspectorFindings as $finding) {
if (! $finding instanceof AuditFinding) {
throw new InvalidArgumentException('Audit inspectors must return audit findings.');
}

$findings[] = $finding;
}
}

return new AuditReport($findings);
}
}
15 changes: 15 additions & 0 deletions packages/dev-tools/src/Audit/AuditSeverity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Evolve\DevTools\Audit;

/**
* @experimental
*/
enum AuditSeverity: string
{
case Info = 'info';
case Warning = 'warning';
case Risk = 'risk';
}
Loading