aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src
AgeCommit message (Collapse)Author
2026-08-16feat(plugin): proxy Composer\Util\Filesystem into the plugin workernsfisis
The class was shadowed by a guard, so a plugin doing `new Filesystem()` got an explicit error. It is classified rust-proxy and plugin-constructible and the Rust port is complete, so listing it as a stub target and answering its public surface from the entity is all it takes. The constructor rejects a caller-supplied ProcessExecutor: that class has no proxy stub, so the argument could only be a second instance the Rust side never sees. findShortestPath re-checks its arguments at the boundary because the port panics where PHP throws, and a panic would take the process down instead of reaching the plugin's catch block. phpstan/extension-installer matches upstream Composer byte for byte again.
2026-08-16feat(plugin): guard Rust-owned classes the worker has no proxy fornsfisis
The worker's autoloader fell through to the real Composer source for every Rust-owned FQCN without a proxy stub, so plugin code doing `new Filesystem()` or subclassing `LibraryInstaller` silently ran on a second instance the Rust side never sees. An unimplemented part of the plugin API has to fail with an explicit error naming it, not quietly work on a disconnected copy. The stub generator now emits a guard class for each of those FQCNs: the real declaration, hierarchy and constants, with every constructor and method raising an explicit error. References satisfied by the declaration alone (`instanceof`, `X::class`, `Link::TYPE_REQUIRE`) keep working. Two FQCNs stay resolvable to the real class, each listed with the worker-side mechanism that makes a natively constructed instance correct. The error had nowhere to go: `Installer::run` dropped the `Result` of both `dispatch_script` calls, so an exception from a listener ended in exit 0. Both propagate now, the way the exception does upstream. Three real-plugin E2E comparisons stop at a guard and are ignored, each naming the class it needs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16refactor(php-shim): split json_decode into assoc and obj variantsnsfisis
The assoc flag was always a literal at every call site, so the boolean carried no information the function name could not. json_decode_assoc and json_decode_obj make the resulting PhpMixed shape visible at the call site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16fix(config): stringify cache-files-maxsize like PHP's (string) castnsfisis
Config::get() resolves cache-files-maxsize by matching the stored value against a size regex. The port read that value with as_string(), which yields None for anything that is not a string, where PHP casts with (string). create-project feeds Config::all() back through Config::merge(), so the second read finds the byte count the first read produced rather than the original "300MiB". as_string() then yields an empty string, the regex fails, and the resulting RuntimeException is swallowed by Config::get(), which maps Err to null. FileDownloader turns that null into a zero max size via .as_int().unwrap_or(0), and Cache::gc() deletes every file in the download cache because the total always exceeds zero. Apply the same cast on the path branch, the other site where Config.php writes (string). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16refactor(php-shim): give fstat and lstat a typed FileStat resultnsfisis
fstat and lstat now return Option<FileStat> instead of a PhpMixed array, so Platform::is_tty and Filesystem::is_junction read `mode` as a field. The array carried each of the 13 values twice — once under its numeric index and once under its name — which no caller relied on, and building it spelled the field list out four times. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16refactor(php-shim): give posix_getpwuid a typed PasswdEntry resultnsfisis
posix_getpwuid now returns Option<PasswdEntry> instead of a PhpMixed array, so Platform reads the field it wants rather than digging through the map. posix_getuid and posix_geteuid return u32, matching the uid PasswdEntry is looked up by. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16refactor(php-shim): give parse_url a typed UrlComponents resultnsfisis
parse_url now returns Option<UrlComponents> instead of a PhpMixed array, and the component-selecting overload with the PHP_URL_* constants is gone: callers read the field they want. Two call sites change behaviour as a result, both towards PHP: * CurlDownloader::handle_redirect tested scheme and host with is_null(), so an unparsable Location header (PhpMixed::Bool(false)) counted as an absolute URL. PHP's truthiness test sends it to the relative-path branch. * Url::get_origin appended a literal port 0, which PHP treats as falsy and leaves off. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16refactor(style): type SymfonyStyle messages as strings and cellsnsfisis
SymfonyStyle modelled PHP's string|array message parameters, its array of listing elements and its table headers/rows as PhpMixed, then normalised and stringified them at every entry point. Take &[String] for the message and listing parameters, Vec<Cell>/Vec<Row> for table (matching the horizontal_table signature) and Vec<String> for the choice options, so the is_array/is_iterable normalisation and the php_string helper both go away. PhpMixed stays where PHP is genuinely mixed: the question answers returned by ask/ask_hidden/choice, their validators, choice's string|int|null default, and the progress_iterate elements. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16refactor(io): take &str in ConsoleIO write and sanitizensfisis
doWrite, doOverwrite and sanitize accept PHP's string|list<string>, which this port modelled as PhpMixed. Every call site inside ConsoleIO passes a single string, so take &str and return String instead, dropping the (array) casts and the to_string_list helper. Auditor is the only caller that passed a list: it builds table rows, whose cells must stay separate, so it now sanitizes each cell. select() likewise sanitizes each choice while projecting them into the keyed form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16refactor: narrow pub(crate) items to privatensfisis
Porting mapped every PHP `protected` member onto `pub(crate)`, which is wider than nearly all of them need. Each item demoted here is reached only from the module that defines it, so the crate-wide visibility conveyed nothing. Every `pub(crate)` that survives has at least one reader in another module of the same crate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16feat(php-shim): render human-facing timestamps in the local timezonensfisis
Split date() into date_utc() and date_local(), the latter resolving the system's local timezone through the tzfile crate ($TZ, then /etc/localtime, falling back to UTC when neither is readable). The timestamps Composer renders for humans -- the GitHub OAuth token note, the GitHub API rate limit reset time, the Perforce client spec fields and the "today" check of the show command -- now go through date_local(). PHP resolves its default timezone from the date.timezone ini setting, which Shirabe does not read, so date_default_timezone_get/set have no input left to model and are dropped from the shim and its callers. The resulting difference is recorded in docs/known-incompatibilities.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15refactor(php-shim): drop the unused opendir directory handlensfisis
PhpDirHandle recorded the opened path and nothing else, and its only caller asked just whether the open had succeeded. A resource type that has to gain readdir and closedir before it means anything is worse than the std::fs::read_dir call it wraps, so Filesystem::isReadable now makes that call directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15feat(cli): report Shirabe's own identity instead of Composer'snsfisis
The binary called itself Composer everywhere: the application name, the logo, --version, about, and every warning that talks about the running program. Prompts to file a bug also pointed at Composer's issue tracker. Add SHIRABE_VERSION and SHIRABE_RELEASE_DATE next to the Composer version constants and report those, naming the Composer version this port tracks alongside them. Composer::VERSION and getVersion() are untouched, so the composer platform package, composer-runtime-api and the HTTP User-Agent keep the value plugins and package repositories expect. build.rs stamps the release date with the UTC date of the HEAD commit, the way Composer's Compiler fills in @release_date@ when building the phar. It now also fails the build when git cannot be read, instead of letting COMPOSER_DEV_WARNING_TIME fall back to the tagged-release value and suppress the outdated-build warning forever. Messages about the Composer ecosystem keep their wording. Two of them are pinned by upstream installer fixtures (Rule's "cannot be modified by Composer" and SolverProblemsException's "you can run Composer with") and stay as they are so those fixtures can keep being used verbatim. The e2e list comparison against upstream Composer now skips the banner, which cannot match by design, and compares everything below it as before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15feat(php-shim): drop the modelled PHP version constantsnsfisis
The shim reported a fixed PHP 8.1.0 through PHP_VERSION, PHP_VERSION_ID, the major/minor/release triple and the PHP_WINDOWS_VERSION_* trio. Their uses split in two. Some guarded branches PHP only needs on runtimes this port cannot be: proc_get_status reports the exit status on every call, so Symfony's pre-8.3 exit-code cache has nothing to work around; hash_raw and hash_file always offer xxh3, so the sha1 fallback is unreachable; and http_get_last_response_headers is always available, so the pre-8.4 $http_response_header branch is gone. safeJunctions reads the host Windows version rather than PHP state, and joins the Windows work on hold. The rest ask about the PHP the user actually runs, and now reach the worker through a new php-rpc PhpVersion payload: the startup banner and the 7.2.5 warning, self-update's min-php filter, the ext-* recommendation in VersionSelector, the stream User-Agent, and whether PhpFileParser scans for enums. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15refactor(php-shim): take only the data argument in http_build_querynsfisis
`http_build_query` ignores `numeric_prefix` because a string-keyed slice never holds an integer key, and every caller passes `"&"` as the separator. Drop both parameters and hard-code the separator. Callers that pass a map of scalar literals to `http_build_query_mixed` no longer need to build an `IndexMap<String, PhpMixed>` for it, so move them to the slice form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15fix(composer-repository): encode the security advisory POST body as PHP doesnsfisis
`http_build_query(['packages' => array_keys($packageConstraintMap)])` maps a single array value, so the body PHP sends is `packages%5B0%5D=a&packages%5B1%5D=b`. The port flattened the array into repeated `packages=<name>` pairs, losing the indices, and passed `"&"` as the numeric prefix and `"="` as the argument separator, joining every pair with `=`. Build the query from a `packages` key holding the name list and hand it to `http_build_query_mixed`, which walks the nested value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15fix(php-rpc): unpack the runtime bundle under the cache dirnsfisis
A worker whose PHP cannot read the bundle out of the executable gets it from an unpacked copy, which went to a directory derived from XDG_CACHE_HOME alone. That ignored COMPOSER_CACHE_DIR, COMPOSER_HOME and the cache-dir setting, and put the files outside the directory clear-cache and the platform conventions cover. The callers now pass Composer's configured cache directory down to base_path(). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15feat(diagnose): audit the Composer runtime the executable carriesnsfisis
checkComposerAudit reported success instead of auditing anything, because the binary ships no vendor/composer/installed.json on disk. It reads the one in the embedded Composer PHP runtime now, and Composer's warning for a missing installed.json is back. Only that file leaves the bundle, into a temporary directory that goes away with the handle; the runtime is unpacked whole only for a worker that cannot read the bundle in place. Phar::extractTo's $files argument selects it, which the shim ignored so far. SHIRABE_COMPOSER_PHP_DIR moves into composer_runtime, so the worker and a reader on the Rust side resolve the runtime through the same branch. DiagnoseCommandTest::testCmdSuccess is ignored: packagist has advisories against composer/composer 2.9.7, the version Composer::VERSION reports, so diagnose exits 1 where the test expects 0. Upstream Composer 2.9.7 reports the same advisories. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15feat(php-rpc): embed the Composer PHP runtime in the executablensfisis
Plugins and scripts need the real `Composer\` classes and the packages Composer depends on, which so far came from a checkout found through SHIRABE_COMPOSER_PHP_DIR or a path next to the workspace. Neither exists for a distributed binary. The build script now archives those PHP sources into a phar the way Compiler.php does and the executable carries it. The worker maps it with Phar::loadPhar and reads a content-addressed sentinel back to tell a bundle it can use from one it cannot; where its PHP cannot open the phar, the bundle is unpacked once into the cache directory and autoloaded from there. SHIRABE_COMPOSER_PHP_DIR still overrides both for development. PHP locates a phar's manifest by the first __HALT_COMPILER(); token in the file, so the executable must hold no other copy of it: phar.rs builds the token at run time, and a linter keeps further literals out of the sources that reach the binary. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12fix(command): port ScriptAliasCommand's ignoreValidationErrors() callnsfisis
The command's own definition covers only --dev/--no-dev and the args array, so any other application-level option reaching it (as when GlobalCommand re-parses `--no-interaction=1` out of ArrayInput's stringification) aborted the run with "does not accept a value". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12docs(todo): retag TODO markers by root causensfisis
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11chore(php-shim): drop the HHVM_VERSION constantnsfisis
The constant was None and defined("HHVM_VERSION") reports it undefined, so every branch guarded by it was dead: shirabe is a Rust binary and never runs on HHVM. HhvmDetector keeps probing for an `hhvm` binary in PATH, which is what actually produces the hhvm platform package. Two of the dropped branches ask about the PHP runtime that consumes the result rather than about shirabe itself -- the class loader's Hack file lookup and the class map parser's enum scanning -- so both get a TODO(php-runtime) marker.
2026-08-11chore(php-shim): drop the substring predicate portsnsfisis
str_contains(), str_starts_with() and str_ends_with() were thin wrappers over the str methods of the same semantics. Call sites now use contains()/starts_with()/ends_with() directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11chore(php-shim): drop the PHP_INT_* constantsnsfisis
PHP_INT_MAX/MIN/SIZE have exact Rust counterparts under the int -> i64 mapping, so the call sites use i64::MAX directly. PHP_INT_SIZE is queried from the PHP runtime where it is actually needed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11feat(signal): abort on SIGINT, SIGTERM and SIGHUP at checkpointsnsfisis
The SignalHandler port was a no-op stub, so all four of Composer's abort paths were dead code: nothing removed a half-created project, reverted composer.json, or cleaned up half-installed packages. Composer runs those handlers from pcntl callbacks, which a Rust signal handler cannot do -- it may touch nothing beyond atomics. SignalSubscription records the signal instead, and the abort runs from checkpoints on the normal call stack, where the clean-up can borrow the state it needs. That also resolves the closure-capture TODO(phase-c)s in RequireCommand and InstallationManager, and replaces exit_with_last_signal's exit(0) with the restore-and-re-raise Seld\Signal does. A subscription is live only inside the four abort regions, so elsewhere the signals keep their default disposition and kill the process at once. It is installed without SA_RESTART so a signal interrupts an interactive prompt rather than resuming the read. A signal reaches only the innermost subscription, reproducing SignalHandler's single-stack dispatch. Drop SignalRegistry, SignalableCommandInterface and the Application wiring for them: nothing in Composer reaches that path, and SignalHandler discards whatever they register. Signal handling from plugins and scripts is undefined behavior; see docs/dev/signals.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11refactor(util): drop the unused TlsHelper portnsfisis
Composer's Composer\Util\TlsHelper is marked deprecated for removal in Composer 3.0 and has no caller in composer/composer outside its own test: PHP's stream layer verifies certificate hostnames itself, and the one surviving method delegates to composer/ca-bundle. The Rust port had no caller either, so it, its test, and the openssl_x509_parse shim it was the sole user of are removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10feat(console): import scripts Command classes as application commandsnsfisis
`Application::do_run` registers a `composer.json` script whose value names a `Symfony\Component\Console\Command\Command` subclass as a live command. The class checks and `new $dummy($script)` need a real PHP runtime, so they run in the worker: the command object lives there and this side keeps a metadata mirror for `list`/`help`, forwarding a run to the worker-side console application it is added to. The name and description fixups are applied to the worker-side object, so both sides carry the same values. Loading the Composer PHP runtime into the worker is gated on the Rust-side `ClassLoader`s resolving the class to a file, keeping that load out of every run whose scripts are plain shell commands. The worker-side console application handoff now accepts commands registered after it was published, since the scripts scan runs after plugin commands are collected. Whether it was published is tracked per application: the handoff is process-wide, so a second application must replace it rather than extend it. `shirabe_php_shim::is_subclass_of` has no callers left.
2026-08-10refactor(autoload): resolve include_path in the PHP workernsfisis
ClassLoader's PSR-0 include_path lookup went through a shim stub that could only have answered with an invented include_path: the setting is PHP runtime state, and the one thing that turns useIncludePath on is the generated autoload_real.php, whose set_include_path() call runs inside the worker. Ask the worker instead, so the search sees the same path list as the process that ends up including the resolved file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10refactor(autoload): drop the todo!() composerRequire stubnsfisis
The real port lives in shirabe-php-rpc as __shirabe_composer_require(), which runs inside the PHP worker where $GLOBALS and `require` exist. Record that pointer in the module doc instead of keeping a stub that can never be implemented on the Rust side. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10refactor(symfony): hide leaf modules behind their parent re-exportsnsfisis
Every parent module in the symfony-* crates already re-exported its leaf modules with `pub use`, so each item was reachable by two paths. Make the leaf modules private and route all callers through the single re-exported path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10fix(php-shim): return an error from fileperms instead of 0nsfisis
fileperms swallowed metadata errors and reported a mode of 0, which the callers could not tell apart from a real (if implausible) mode: the zip archiver stored entries with no permission bits, and Filesystem::copy chmod-ed the target to 0. It now returns Result<u32, io::Error> so both callers propagate the failure. The switch to u32 also drops the casts around the mode arithmetic and ZipArchive's entry attributes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10feat(xdebug): switch Xdebug off in the PHP workernsfisis
Composer restarts itself with Xdebug unloaded because Xdebug makes PHP several times slower. Xdebug is never loaded into this process, so what needs dealing with is the PHP worker: it is spawned with `-d xdebug.mode=off` and `XDEBUG_MODE=off` (Xdebug reads the environment variable first and lets it override every ini setting), which makes its module init return before it installs any executor, compile, error or opcode hook. Rewriting the ini files and re-executing, the way xdebug-handler does, would additionally cover Xdebug 2, which hooks unconditionally and has no equivalent setting. That is not worth its machinery here: Xdebug 2 caps out at PHP 7.4, while every PHP version Composer supports can run Xdebug 3. What remains of XdebugHandler is small enough to live beside the worker it governs, so its crate is gone and its callers inline it. isXdebugActive answers false without asking PHP whenever the worker is switched off, so a command that needs no PHP does not spawn one just for the Xdebug warning; diagnose reports what the worker measures instead, which still surfaces an Xdebug that ignores the setting. PlatformRepository has no unloaded extension to restore, since switching the mode off leaves it loaded. COMPOSER_ORIGINAL_INIS is neither written nor read: it exists so a restarted process can name the ini files it replaced, and IniHelper can report the worker's own. IniHelperTest injects through that variable, so none of its cases are ported. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09fix(autoload): embed ClassLoader.php and the LICENSE at build timensfisis
They were copied at run time from a path built out of CARGO_MANIFEST_DIR, so dumping the autoloader failed wherever the build tree was gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09feat(plugin): let a Rust-implemented plugin cross into the workernsfisis
PluginManager::getPluginCapability hands the plugin itself to the capability constructor. Only a PHP-implemented plugin had an entity the child could receive, so a Rust-implemented one bailed out; it now crosses as a handle to an R-table entity behind Shirabe\RustPluginStub, or its Capable flavour, since `$plugin instanceof Capable` is what decides whether Composer asks a plugin for capabilities at all. This is what the two capability tests of PluginInstallerTest were waiting on: the mocked Capable plugin is Rust-side, and one of them asserts the identity of the plugin read back out of $capability->args.
2026-08-09feat(path-downloader): restrict a mirrored path package to its archivable filesnsfisis
PathDownloader builds an ArchivableFilesFinder and hands it to Filesystem::mirror as the iterator that selects what to copy, so a path package installed by mirroring drops what .gitattributes marks export-ignore, what .gitignore excludes and the VCS directories. The port dropped the argument, and mirrored the source tree whole. Every method mirror() calls on the entries it walks is derived from the pathname, so the iterator is modeled as a list of pathnames rather than as a Traversable of SplFileInfo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09feat(symfony-filesystem): finish the Filesystem port and trim its APInsfisis
doRemove() renames a directory to a random hidden name before emptying it, and undoes that rename when the final rmdir fails, so a concurrent process cannot recreate the path mid-removal. It also walks one level at a time through FilesystemIterator instead of flattening the whole tree, and lets an inner rmdir failure pass, both as upstream does. copy() keeps the mode fopen($targetFile, 'w') would have left rather than the origin's, symlink() and mirror() call readlink() and getLinkTarget() where upstream does, and a directory iterator that cannot be opened propagates its UnexpectedValueException instead of being swallowed or flattened into an IOException. Error message text is out of scope per docs/known-incompatibilities.md, so the three TODO(phase-c) markers that only tracked wording are gone, along with linkException()'s Windows-only branch. So are the arguments no caller varies -- symlink()'s copyOnWindows, mirror()'s iterator and options, copy()'s overwriteNewerFiles -- which removes the last TODO(phase-c) in the file. New shim functions: readlink, filesystem_iterator, stream_is_local, strrev and SplFileInfo::getLinkTarget. base64_encode takes bytes so random_bytes() can feed it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(cargo): drop the now-empty shirabe-external-packages cratensfisis
Every package it held now lives in its own crate, so the umbrella crate has nothing left in it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(xdebug-handler): extract composer/xdebug_handler into the ↵nsfisis
shirabe-xdebug-handler crate Move `Composer\XdebugHandler` out of shirabe-external-packages and into its own crate, so the path is `shirabe_xdebug_handler::XdebugHandler` instead of `shirabe_external_packages::composer::xdebug_handler::XdebugHandler`. Standing alone, the stub no longer sits in a crate that shirabe-php-rpc depends on, so drop the dependency-cycle rationale from the comments that explain why callers reach for shirabe_php_rpc directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(ca-bundle): extract composer/ca_bundle into the shirabe-ca-bundle cratensfisis
Move `Composer\CaBundle` out of shirabe-external-packages and into its own crate, so the path is `shirabe_ca_bundle::CaBundle` instead of `shirabe_external_packages::composer::ca_bundle::CaBundle`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(seld-signal): extract seld/signal into the shirabe-seld-signal cratensfisis
Move `Seld\Signal` out of shirabe-external-packages and into its own crate, so the path is `shirabe_seld_signal::SignalHandler` instead of `shirabe_external_packages::seld::signal::SignalHandler`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(seld-json-lint): extract seld/json_lint into the ↵nsfisis
shirabe-seld-json-lint crate Move `Seld\JsonLint` out of shirabe-external-packages and into its own crate, so the path is `shirabe_seld_json_lint::ParsingException` instead of `shirabe_external_packages::seld::json_lint::ParsingException`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(symfony-console): extract symfony/console into the ↵nsfisis
shirabe-symfony-console crate Move `Symfony\Component\Console` out of shirabe-external-packages and into its own crate, so the path is `shirabe_symfony_console::application::Application` instead of `shirabe_external_packages::symfony::console::application::Application`. The `delegate_to_inner!` and `delegate_command_trait_impls_to_inner!` macros move with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(symfony-finder): extract symfony/finder into the ↵nsfisis
shirabe-symfony-finder crate Move `Symfony\Component\Finder` out of shirabe-external-packages and into its own crate, so the path is `shirabe_symfony_finder::Finder` instead of `shirabe_external_packages::symfony::finder::Finder`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(symfony-filesystem): extract symfony/filesystem into the ↵nsfisis
shirabe-symfony-filesystem crate Move `Symfony\Component\Filesystem` out of shirabe-external-packages and into its own crate, so the path is `shirabe_symfony_filesystem::Filesystem` instead of `shirabe_external_packages::symfony::filesystem::Filesystem`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(symfony-process): extract symfony/process into the ↵nsfisis
shirabe-symfony-process crate Move `Symfony\Component\Process` out of shirabe-external-packages and into its own crate, so the path is `shirabe_symfony_process::Process` instead of `shirabe_external_packages::symfony::process::Process`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(pcre): extract composer/pcre into the shirabe-pcre cratensfisis
Move `Composer\Pcre` out of shirabe-external-packages and into its own crate, so the path is `shirabe_pcre::preg::Preg` instead of `shirabe_external_packages::composer::pcre::preg::Preg`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(io): move LogLevel into base_ionsfisis
LogLevel has no user outside BaseIO, so it lives next to its only consumer and the psr crate tree is dropped. The log() level parameter becomes &str now that the constants are compared directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09test(cli): drop the CLI smoke testsnsfisis
These smoke tests only checked that `<cmd> --help` exited cleanly and that running each command did not panic. The Composer-derived test suite now covers the same commands with real assertions, so the smoke tests no longer add anything. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09feat(php-shim): replace error_get_last with the failing call's io::Errornsfisis
The shim raises no PHP-level errors, so error_get_last() always returned None and every message built from it lost its trailing reason. Now that the fs mutators and php_strip_whitespace carry an io::Error, take the reason from the call that actually failed instead of a global last-error slot. Filesystem::unlinkImplementation returns that error rather than a bool so ensureDirectoryExists, unlink and rmdir can report it, and PhpFileParser appends it to the "following message may be helpful" hint. The wording is Rust's io::Error text, not PHP's warning text, for the same reason noted in symfony/filesystem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(php-shim): make the fs mutators return Resultnsfisis
mkdir, rmdir, unlink and symlink each had a bool version and a _result twin returning the io::Error, which left two names for one call. Keep only the Result form and let the callers that want a boolean spell out .is_ok(). Call sites that discard the outcome, as their PHP originals do, are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>