12 KiB
`# Tasker: CLI Tool Management System
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-bridgespackage will providePhredBridge,SymfonyBridge, andLaminasBridge. This avoids copy-pasting code across libraries while keeping the core library decoupled from the mainTaskerrunner.
- PhredBridge: The primary runner used by
2. "Internal Standard" Interfaces (Proposed Implementation)
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 andTaskerdepend 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)
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)
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; }
}
User Implementation Example
class MigrateCommand extends Command
{
protected string $name = 'db:migrate';
protected string $description = 'Run database migrations';
public function execute(InputInterface $input, OutputInterface $output): int
{
// Logic here
return ExitCode::OK;
}
}
InputInterface
namespace GetPhred\ConsoleContracts;
interface InputInterface
{
public function getArgument(string $name, mixed $default = null): mixed;
public function getOption(string $name, mixed $default = null): mixed;
public function hasOption(string $name): bool;
}
OutputInterface
namespace GetPhred\ConsoleContracts;
interface OutputInterface
{
public function write(string $message): void;
public function writeln(string $message): void;
public function success(string $message): void;
public function error(string $message): void;
public function warning(string $message): void;
}
3. Command Discovery & Injection (Proposed Implementation)
Tasker is designed to work with any PSR-11 container.
Discovery Ideas:
- Explicit Registration (DI Container): User adds command classes to the DI container. This takes precedence.
- Auto-Discovery (Composer): Tasker scans for a
phred-tasker.jsonfile in the root of installed packages or aextra.phred-taskerkey incomposer.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.
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:
- Identify
MigrateCommandas the handler. - Request
MigrateCommandfrom the PSR-11 container. - The container injects the
Manager(already configured in the app). - 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.
| Constant | Code | Meaning |
|---|---|---|
ExitCode::OK |
0 | Successful termination. |
ExitCode::USAGE |
64 | Command line usage error (invalid arguments/options). |
ExitCode::DATAERR |
65 | Data format error. |
ExitCode::NOINPUT |
66 | Cannot open input (file not found/unreadable). |
ExitCode::UNAVAILABLE |
69 | Service unavailable. |
ExitCode::SOFTWARE |
70 | Internal software error (exceptions/logic failures). |
ExitCode::IOERR |
74 | Input/output error. |
ExitCode::PERM |
77 | Permission denied. |
ExitCode::CONFIG |
78 | Configuration error. |
5. Interactivity & Portability
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:
TaskerBridgeswill provide implementations of this interface forsymfony/console(wrappingQuestionHelper) and Tasker (usingSTDIN/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:
CommandInterfaceremains method-based (getName(), etc.) to ensure that any runner can reliably retrieve metadata. - Property-Based DX: A base
Commandclass (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-bridgesto housePhredBridge,SymfonyBridge, andLaminasBridge. - Ensure all components use PHP 8.2+ strict typing.
Phase 2: Migration of Existing Libraries
- Pairity: Refactor
Pairity\Console\Applicationlogic into Tasker-compatible commands. - Atlas: Formalize
Atlas\Commands\ListRoutesCommandinto the Tasker standard. - Scape: Identify any CLI needs (e.g., cache clearing) and implement them.
Phase 3: The Framework Glue
- The
Frameworkpackage 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/listUX." - Finalize SPECS.md:
- Document v1 contracts (Input/Output/Command).
- Define the JSON schema for
composer.jsondiscovery. - Define runner behavior (exit codes, verbosity flags, non-interactive mode).
- Populate MILESTONES.md: Map deliverables to specific, verifiable milestones.
2. Contracts (ConsoleContracts)
- Finalize v1 Interfaces: Ensure the proposed
CommandInterface,InputInterface, andOutputInterfaceare finalized ingetphred/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
vendorforextra.phred-tasker). - Implement a basic argv parser (unless delegated).
- Standard UX:
- Implement
listandhelpcommands. - Support
-v/-vv/-vvvfor verbosity and--no-interactionfor CI/CD safety.
- Implement
4. Bridges (TaskerBridges)
- PhredBridge: The primary adapter for the Tasker runner.
- SymfonyBridge: Adapter for
symfony/consoleintegration. - 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=jsonforlist).
Open Questions & Ambiguity Resolution
(All current open questions have been resolved and moved to Architectural Decisions.)