Tasker is a CLI Tool Management system built originally for use in the Phred Framework, but opened to the public to install in any codebase. It is designed to be the central nervous system for all Phred CLI activities while maintaining extreme portability for standalone library use.
## Core Vision
- **Centralized Management**: A single entry point for all Phred-related CLI tasks.
- **Decoupled Architecture**: Individual libraries (Pairity, Atlas, etc.) should NOT depend on Tasker. They should define their own "Actions" or "Services," and provide a thin "Command" layer that Tasker can consume.
- **DI Friendly**: Built to work seamlessly with PSR-11 containers.
- **Bridge-Ready**: Easily integrable into existing CLI ecosystems like `symfony/console`.
## Architectural Decisions
### 1. The Bridge Pattern
To solve the conflict between "Phred Integration" and "Standalone Portability," Tasker will advocate for a split between **Logic** and **Presentation**:
- **Core Logic**: Remains in the library's service layer (e.g., `Pairity\Migrations\Migrator`).
- **Command Wrapper**: A thin class that implements a simple, common interface.
- **Bridges**:
- **PhredBridge**: The primary runner used by `Tasker`.
- **Bridges Package**: A dedicated, lightweight `getphred/tasker-bridges` package will provide `PhredBridge`, `SymfonyBridge`, and `LaminasBridge`. This avoids copy-pasting code across libraries while keeping the core library decoupled from the main `Tasker` runner.
Since there is no `psr/cli`, Tasker will define a minimal, framework-agnostic set of interfaces. To ensure true decoupling, these interfaces should either:
- **Reside in a lightweight Contracts package** (e.g., `getphred/console-contracts`) that both the library and `Tasker` depend on.
- **Be duplicated/re-implemented** as a simple contract within the library if a shared package is not desired.
#### Proposed Command API (Hybrid Approach):
**CommandInterface** (In `ConsoleContracts`)
```php
namespace GetPhred\ConsoleContracts;
interface CommandInterface
{
public function getName(): string;
public function getDescription(): string;
/**
*@return array<string,string> Name => Description
*/
public function getArguments(): array;
/**
*@return array<string,string> Name => Description
*/
public function getOptions(): array;
public function execute(InputInterface $input, OutputInterface $output): int;
}
```
**Base Command Implementation** (In `Tasker` or a Trait)
```php
namespace Phred\Tasker;
use GetPhred\ConsoleContracts\CommandInterface;
abstract class Command implements CommandInterface
{
protected string $name = '';
protected string $description = '';
protected array $arguments = [];
protected array $options = [];
public function getName(): string { return $this->name; }
public function getDescription(): string { return $this->description; }
public function getArguments(): array { return $this->arguments; }
public function getOptions(): array { return $this->options; }
Tasker is designed to work with any PSR-11 container.
#### Discovery Ideas:
1.**Explicit Registration (DI Container)**: User adds command classes to the DI container. **This takes precedence.**
2.**Auto-Discovery (Composer)**: Tasker scans for a `phred-tasker.json` file in the root of installed packages or a `extra.phred-tasker` key in `composer.json`.
```json
"extra": {
"phred-tasker": {
"commands": [
"Pairity\\Console\\Commands\\MigrateCommand",
"Pairity\\Console\\Commands\\SeedCommand"
]
}
}
```
#### Injection & Translation:
Commands should use **Constructor Injection** for their dependencies. Tasker will use the PSR-11 container to instantiate them.
Translation is the **responsibility of the Command implementation**. Commands should inject a translator service (e.g., from `Langer`) via the container and handle translation before calling output methods. This keeps the `OutputInterface` lean and framework-agnostic.
```php
namespace Pairity\Console\Commands;
use GetPhred\ConsoleContracts\CommandInterface;
use Pairity\ORM\Manager;
class MigrateCommand implements CommandInterface
{
public function __construct(
private readonly Manager $manager
) {}
// ... getName, execute, etc.
}
```
Proposed Flow:
1. Identify `MigrateCommand` as the handler.
2. Request `MigrateCommand` from the PSR-11 container.
3. The container injects the `Manager` (already configured in the app).
4. Tasker calls `execute()`.
### 4. Exit Code Standards
Tasker follows the `sysexits.h` standard for exit codes to ensure interoperability with system tools and shell scripts. To simplify development, Tasker provides an `ExitCode` utility with semantic constants.
To maintain extreme portability, the `OutputInterface` will remain a simple pipe for strings. Interactive features (e.g., asking questions, confirmation prompts) will NOT be part of the core `ConsoleContracts\OutputInterface`.
Instead, Tasker will define an optional `InteractionInterface` (or similar helper) that can be injected into Commands via the DI container. This allows:
- **Standalone Library Portability**: Commands only need to know how to write output. If they need user input, they depend on an interface that can be satisfied by any runner (Tasker, Symfony, etc.).
- **Bridge Support**: `TaskerBridges` will provide implementations of this interface for `symfony/console` (wrapping `QuestionHelper`) and Tasker (using `STDIN`/`STDOUT`).
- **Clean Transitions**: If a developer switches from Tasker to native Symfony Console, they only need to swap the DI implementation for the interaction helper; the command logic remains untouched.
### 6. Metadata Configuration (Hybrid Approach)
To balance strict interface enforcement with modern Developer Experience (DX), Tasker adopts a hybrid approach for command metadata (name, description, etc.):
- **Interface-Driven**: `CommandInterface` remains method-based (`getName()`, etc.) to ensure that any runner can reliably retrieve metadata.
- **Property-Based DX**: A base `Command` class (or trait) provides default implementations of these methods that read from protected class properties. This mimics the concise configuration style of Symfony Console while maintaining the architectural purity of interfaces.
- **Lazy Loading Readiness**: This structure allows the runner to potentially use Reflection or static analysis to read metadata without full instantiation if needed in the future (similar to Symfony's static properties or attributes).
### Phase 1: The Standard & Bridges
- **Contracts**: Define the core interfaces in a very lightweight package (e.g., `getphred/console-contracts`).
- **Bridges Package**: Create `getphred/tasker-bridges` to house `PhredBridge`, `SymfonyBridge`, and `LaminasBridge`.
- Ensure all components use PHP 8.2+ strict typing.
### Phase 2: Migration of Existing Libraries
- **Pairity**: Refactor `Pairity\Console\Application` logic into Tasker-compatible commands.
- **Atlas**: Formalize `Atlas\Commands\ListRoutesCommand` into the Tasker standard.
- **Scape**: Identify any CLI needs (e.g., cache clearing) and implement them.
### Phase 3: The Framework Glue
- The `Framework` package will include Tasker by default and pre-configure it to aggregate commands from all installed Phred libraries.
## Implementation Roadmap & Checklist
To ensure Tasker is an efficient, user-friendly, and enterprise-ready package, the following checklist must be completed.
### 1. Product Definition & Scope
- [ ]**Define MVP**: Confirm if the MVP is "Run registered commands with DI, explicit discovery via Composer extra, standard input/output, and `--help`/`list` UX."
- [ ]**Populate MILESTONES.md**: Map deliverables to specific, verifiable milestones.
### 2. Contracts (ConsoleContracts)
- [ ]**Finalize v1 Interfaces**: Ensure the proposed `CommandInterface`, `InputInterface`, and `OutputInterface` are finalized in `getphred/console-contracts`.
- [ ]**Versioning Policy**: Establish a strict Semantic Versioning (SemVer) policy for contracts to ensure stability for library authors.
### 3. Core Runner (Tasker)
- [ ]**Clean up `composer.json`**:
- [ ] Add PSR-4 autoloading.
- [ ] Remove `psr/http-message` (not needed for CLI).
- [ ] Add dev-dependencies for testing and static analysis.
- [ ]**Bootstrap & Routing**:
- [ ] Implement a runner that accepts a PSR-11 container.
- [ ] Implement discovery logic (scanning `vendor` for `extra.phred-tasker`).
- [ ] Implement a basic argv parser (unless delegated).
- [ ]**Standard UX**:
- [ ] Implement `list` and `help` commands.
- [ ] Support `-v/-vv/-vvv` for verbosity and `--no-interaction` for CI/CD safety.
### 4. Bridges (TaskerBridges)
- [ ]**PhredBridge**: The primary adapter for the Tasker runner.
- [ ]**SymfonyBridge**: Adapter for `symfony/console` integration.
- [ ]**LaminasBridge**: (Verify demand first before implementing).
### 5. Quality Assurance & Enterprise Readiness
- [ ]**Testing Suite**:
- [ ] Unit tests for input parsing, discovery, and DI handoff.
- [ ] Integration tests running a mock command through Tasker and Symfony bridges.
- [ ]**CI Pipeline**: GitHub Actions for PHP 8.2/8.3 with zero tolerance for warnings/failures.
- [ ]**Documentation**:
- [ ] Comprehensive README with Quick Start and DI examples.
- [ ] Security policy (`SECURITY.md`) and contribution guidelines.
- [ ] Machine-readable output (e.g., `--format=json` for `list`).
## Open Questions & Ambiguity Resolution
(All current open questions have been resolved and moved to Architectural Decisions.)