aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
AgeCommit message (Collapse)Author
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(symfony-string): extract symfony/string into the ↵nsfisis
shirabe-symfony-string crate Move `Symfony\Component\String` out of shirabe-external-packages and into its own crate, so the path is `shirabe_symfony_string::ByteString` instead of `shirabe_external_packages::symfony::string::ByteString`. 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>
2026-08-09feat(symfony-filesystem): append the failure reason to IOExceptionsnsfisis
Symfony wraps its native filesystem calls in box(), which captures the warning text of a failed call into self::$lastError and appends it to the IOException message. The port dropped that text because the shim's mkdir, rmdir, unlink and symlink only return PHP's bare false. Give each of those a Result-returning variant carrying the io::Error, and use it to restore the appended reason. That also makes two conditions portable: doRemove()'s "Permission denied" test, and linkException()'s Windows error code 1314 special case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(installed-versions): drop the Rust port, which has no readersnsfisis
InstalledVersions is a runtime API for plugins and project code; Composer itself never reads it. Its consumers run in the PHP worker against the copy FilesystemRepository dumps to vendor/composer/InstalledVersions.php, whose static state is already kept in sync by __shirabe_installed_versions_reload. Nothing in Rust read the mirrored statics, so reload() and the reflection setters were no-ops. The tests covered only the Rust port, not the PHP class the worker loads, so they assert nothing about compatibility; they are left as todo!() skeletons. This also removes the shim functions method_exists, php_dir and require_php_file, whose only caller was the deleted module. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(installer): drop the GC control callsnsfisis
Composer turns the cycle collector off around the dependency solver, but Rust has no GC, so the gc_collect_cycles/gc_disable/gc_enable shims were no-ops. Remove the call sites and the now-callerless shim functions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(installed-versions): merge the name lists without call_user_func_arraynsfisis
The only live caller passed the constant 'array_merge', so the list concatenation is written out in place, as ClassLoader::get_prefixes already does. That leaves the shim function without callers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(php-shim): remove call_user_func, which has no callersnsfisis
Its doc-by-reference sibling call_user_func_array now carries the full reason text. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(php-shim): remove shim functions with no callersnsfisis
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09fix(dependency-resolver): query the PHP runtime for the php versionnsfisis
Problem reported the platform php version from the shim's compile-time PHP_VERSION constant, and the shim's phpversion() hit todo!() for any extension. Go through the RPC bridge instead, as PHP's Composer\Platform\Runtime does, and drop the shim function that now has no callers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09test(platform): drop HHVM-only paths from HhvmDetector testnsfisis
shirabe never runs on HHVM, so the HHVM_VERSION_ID branches are dead. Dropping them leaves the shim's unimplemented `constant()` without a caller, so remove it as well. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09feat(archiver): preserve file permissions in zip archivesnsfisis
The shim reported setExternalAttributesName as missing, so ZipArchiver took the branch for libzip below 0.11.2 and every archived entry got the zip crate's default mode. The attributes now travel as arguments to add_file and add_empty_dir, because the crate fixes an entry's external attributes when the entry is started and offers no way to amend one already written. unix_permissions keeps only the low 9 mode bits, on top of which the crate restores S_IFREG and S_IFDIR, so setuid/setgid/sticky bits and other file types are still dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09refactor(env): split and join PATH lists with std::envnsfisis
The shim's PATH_SEPARATOR was hardcoded to ":", so every PATH list was split and joined on the wrong character off Unix. std::env::split_paths and join_paths use the platform's separator, which also lets the bin-dir membership test in EventDispatcher compare list entries instead of regex-matching the raw PATH string. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(path): build paths with Path::joinnsfisis
Concatenating a directory, MAIN_SEPARATOR and a component reimplements what Path::join already does. Joining directly also drops the separator from the intermediate values it used to be baked into, such as ClassLoader's $pathEnd. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(external-packages): test for Windows with cfg!(windows)nsfisis
MAIN_SEPARATOR is the path separator; PHP's `'\\' === DIRECTORY_SEPARATOR` uses it as an OS test only because PHP has no dedicated one. cfg!(windows) says what the branch actually selects on, and leaves MAIN_SEPARATOR to the sites that really join or split paths. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09refactor(php-shim): use std::path::MAIN_SEPARATOR over a shim constantnsfisis
The shim's DIRECTORY_SEPARATOR was hardcoded to "/", so every ported `'\\' === DIRECTORY_SEPARATOR` check compared against a constant that does not track the target platform. std::path::MAIN_SEPARATOR and MAIN_SEPARATOR_STR carry the same meaning as PHP's constant and resolve per platform, so the Windows branches are selected on Windows targets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09test(console): cover the dev build warning branchnsfisis
The deadline is a constant baked in at build time, so PHP's define() of COMPOSER_DEV_WARNING_TIME cannot make Application take that branch. Application now holds the deadline in a field that __set_dev_warning_time overrides, letting testDevWarning run instead of staying ignored. The define() shim, whose only caller was that test, goes away with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09fix(json): let an \Error out of JsonFile::read untouchednsfisis
The port's fall-through stood in for PHP's `catch (\Exception $e)`, which does not catch an `\Error`; a TypeError reaching JsonFile::read would come back out as a `\RuntimeException` saying the file could not be read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09fix(archiver): narrow PharArchiver's catch to \UnexpectedValueExceptionnsfisis
PharArchiver::archive catches only `\UnexpectedValueException` and wraps it in a `\RuntimeException` naming the target and sources; anything else leaves the method as it was thrown. The port wrapped every error, so the two `\RuntimeException('Can not compress to %s format')` raised inside the `try` came back out with a second message around them and their code replaced. Reading the message and code off the caught exception rather than off whatever arrived also drops the guesswork the old `map_or(0, ..)` had to do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09fix(exception): carry PHP's $previous through the ported throw sitesnsfisis
Composer hands the exception it caught to the one it throws in its place, so `getPrevious()` reaches the cause and Application's renderer prints the whole chain. Every ported site dropped it, because the flat exception structs had nowhere to put one. `AnyThrowable::into_previous` turns the caught error into that argument, and the 13 sites now pass it. `getCode()` came along for the ride at the four sites that derive the new exception's code from the caught one (PharArchiver, ArrayLoader x2), and ComposerRepository's message now names the caught exception's class instead of the literal "Exception". GitHubDriver::attemptCloneFallback took the previous exception's message and appended it to its own, which no `\RuntimeException('Fallback to git driver disabled')` in Composer ever says; it now chains it instead. Git::syncMirror restores what PHP's `finally` does to an exception in flight: the `git remote set-url` that scrubs credentials back out of the URL runs in a `finally`, and when it fails PHP propagates *its* exception over the one already leaving, chaining the displaced one as previous. The port discarded the finally's result, so a failure to scrub the URL was reported as a successful mirror sync. `AnyThrowable::set_previous` models the engine-level chaining. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08feat(php-shim): give ported exceptions PHP's class hierarchynsfisis
Ported exceptions were flat structs reached with `downcast_ref`, so Composer's `catch (\RuntimeException $e)` only matched the exact leaf type and `get_class($e)` had nothing to report. Each exception now embeds an instance of the class it extends and travels inside an `AnyThrowable`; `Catch::catch`/`catch_mut` walk that chain, and `PhpClass::php_class_name` yields the PHP FQCN. Dropping the `std::error::Error` impls from the exception types leaves `AnyThrowable` as the only route into an `anyhow::Error`, so the walk cannot be bypassed. A `no_exception_downcast` linter catches the `downcast::<X>()` calls that would now silently answer `None`. Three sites change behavior as a result: the `TransportException` exit-code override reaches `MaxFileSizeExceededException`, the `catch (\LogicException)` in findSimilar() reaches its subclasses, and rendered exception titles carry the real class name rather than a guess. `get_class_err()` is no longer a `todo!()`, which re-enables FilesystemRepositoryTest::testCorruptedRepositoryFile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08feat(composer): bake the dev build warning deadline in at build timensfisis
Composer defines COMPOSER_DEV_WARNING_TIME from its phar stub when the compiled version is a commit hash rather than a tag, so the value exists only in the build artifact. It was a todo!() in the PHP shim, leaving the warning branch in Application::do_run unreachable. A build script now derives it the way Compiler does, from git describe and the HEAD commit date, and composer::COMPOSER_DEV_WARNING_TIME holds the result as a Rust constant instead of a runtime-defined one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08refactor(php-shim): dispatch spl_object_hash on Rc and referencesnsfisis
Getting a rule's identity meant spl_object_hash(&*rule.borrow()), so the solver borrowed a RefCell just to read an address, and a second function spl_object_hash_process existed because one generic fn cannot tell Rc<T> from &T. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08refactor(semver): replace Constraint's OP_*/STR_OP_* with CmpOpnsfisis
PHP's version_compare takes its operator as a string, so the shim's port did too, and Constraint carried two families of operator constants plus translation tables to convert between the string form and its own int codes. Five copies of those tables had accumulated across Constraint, CompilingMatcher and the plugin value bridge. version_compare now takes a CmpOp, which makes an invalid operator unrepresentable and removes the tables' reason to exist. Constraint stores a CmpOp and keeps only the string parsing its constructor needs; getOperator, compile and CompilingMatcher::match speak CmpOp as well. PHP's OP_* numbering stays observable: a plugin reads the raw integer off the Constraint object over RPC, so get_operator_constant and its new inverse hold that 0..5 mapping. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08chore: remove unused functionsnsfisis
2026-08-07feat(installer): dispatch package eventsnsfisis
InstallationManager left both PRE_PACKAGE_* and POST_PACKAGE_* as empty stubs, so a subscriber never ran at all and the difference from upstream was silent rather than an explicit error. Operations cross the boundary as R-table entities with generated proxy stubs. Materializing them the way a Link crosses is not possible: a materialized value is revived by unserialize() on the child side, so its properties never pass through the wire decoder and a nested handle descriptor would not come back as a stub — and an operation always holds a PackageInterface. execute() now shares one Rc per operation through the whole batch pipeline, so a plugin sees one object for both the pre- and the post-event of an operation, as it does in PHP. POST_PACKAGE_* also moves out of the operation's promise chain into the post-exec callback list PHP runs after waitOnPromises(). The stub generator materializes non-public class constants verbatim now, which the operation classes need for their `protected const TYPE`: a constant has no entity behind it, so a copy in the worker cannot diverge, and keeping the declared visibility exposes nothing the real class hides. The E2E fixture added here compares the recorded events against upstream Composer. It also surfaced that upstream starts an operation's chain where it is built (a null prepare() becomes an already-fulfilled React promise whose handlers run through the immediately drained queue) while this port only drives its futures in wait_on_promises, so the repository state a pre-event observes differs; that half of the comparison is a separate `#[ignore]`d test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07fix(plugin): make proxy stub property access an explicit errornsfisis
A proxy stub declares none of the real class's instance properties, and the `__get`/`__set` forwarders were emitted only for classes that declare a public one. Every other property access therefore got PHP's own answer for an undeclared property — null on a read, a dynamic property on a write — so plugin code reading state the entity holds ran on with null and failed somewhere else entirely, or not at all. Emit the forwarders on every root stub, add `__isset`/`__unset` alongside them so `isset()` cannot answer false silently either, and serve all four from one dispatcher that answers the state the Rust-side entity exposes and rejects every other name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07fix(autoload-generator): survive listeners that re-enter dump()nsfisis
AutoloadGenerator::dump dispatches PRE_AUTOLOAD_DUMP / POST_AUTOLOAD_DUMP, and EventDispatcher::make_autoloader answers those by walking back into the Composer graph -- the local repository, the installation manager, and the generator itself. Installer::run held mutable borrows of all three across the dump call, so any plugin subscribing to either event panicked with "RefCell already borrowed" before its listener ran. dump() and build_package_map() now take the local repository, the installation manager and the locker as shared handles instead of &mut dyn, and the borrow is taken where it is used. The generator itself is re-entered, not just borrowed twice: PHP listeners reach it through Composer::getAutoloadGenerator() and call setDevMode() while dump() is running, and make_autoloader asks the same object for a package map. That is not expressible behind &mut self, so the mutable state moves into Cell/RefCell fields and the whole AutoloadGeneratorInterface takes &self. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07feat(plugin): cross alias packages over RPC as proxy stubsnsfisis
AliasPackage, CompleteAliasPackage and RootAliasPackage now have generated proxy stubs, so a package handed to a plugin no longer has to be a real package: it crosses as the stub matching its concrete variant, answers getAliasOf / setRootPackageAlias / isRootPackageAlias / hasSelfVersionRequires, and can be constructed from plugin code. The setters RootPackageInterface declares are routed through that interface for every root package instead of through the base Package state. Only the alias variant needs it -- RootAliasPackage overrides all nine to write through to the package it aliases -- but a real RootPackage delegates to the same base state either way, so both take one path. An alias of an alias has no representation here, so narrowing the constructor argument to a real package is an explicit error rather than a silent demotion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07refactor(platform-repository): fetch the PHP runtime in one RPC callnsfisis
The RuntimeInterface seam asked the worker one question at a time: a round trip per loaded extension, per ReflectionExtension::info() output and per constant, so a single `show --platform` cost 70 to 100 of them. A `platform` dispatch entry now answers all of it as one PHP array, which shirabe-php-rpc decodes into a OnceLock-cached PlatformInfo, the way the diagnose command already works. Composer\Platform\Runtime therefore has no Rust counterpart any more. Its work belongs to the running interpreter, and invoke()/construct() could only be ported as a whitelist that panicked on anything unlisted; it is ported as PHP into the worker instead, and PlatformRepository reads the answers off PlatformInfo. Accessors panic on a name the payload does not carry, so the worker and its consumers cannot drift apart unnoticed. The tests describe the runtime as payload data where they used to mock the seam, with the datasets unchanged. The one loss is the call-count assertion of test_inet_pton_regression: the payload reports the result of `@inet_pton('::')` rather than answering a call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07refactor: merge split inherent impl blocks into one per typensfisis
Enable clippy::multiple_inherent_impl and fix the 21 sites it reports. Types whose inherent methods were spread across two or three impl blocks now keep them in a single block; only the impl headers move, no method bodies change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07chore: use shirabe_php_shim::impl_php_classnsfisis
2026-08-07test: port the tests left as todo!() stubsnsfisis
Replace the todo!() bodies with real ports. Four autoload-generator tests now run for real; the rest stay #[ignore]d, but each ignore reason now names the concrete missing symbol instead of a vague subsystem. Production additions the ports need: the deprecated AuthHelper::addAuthenticationHeader wrapper, EventDispatcher::__set_dispatch_script_override as the seam for PHPUnit onlyMethods(['dispatchScript']), and a define() stub in the shim. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07feat(php-rpc): cross materialized values as PHP object recordsnsfisis
A materialized value used to cross as a constructor call: the class name, the arguments, and any post-construction setter. Describing a real instance that way needed a ReflectionProperty read for every field the class exposes no getter for, and state no constructor takes (an unset pretty string, a Link built without a pretty constraint) had no faithful call to describe it at all. The value now crosses as the object record serialize() writes for it, which unserialize() revives without running a constructor, so both sides transfer the state itself instead of a recipe for rebuilding it. The PHP half keeps only the class list (also the allowed_classes list of every frame payload) and the UTC rebasing of dates; describe(), build() and the reflection are gone. The wire codec gains O: records (PluginValue::PhpObject) and r: back references, whose resolution reproduces PHP's numbering of every value in a payload; a cyclic object graph and a PHP reference (R:) are rejected. Two behaviours change with it: a date crosses carrying timezone_type 3 "UTC" rather than a +00:00 offset, which is what ArrayLoader builds a release date as, and a Link subclass crosses as a P-table entity instead of being silently downgraded to a plain Link. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06fix(php-rpc): carry the worker channel over a socketpairnsfisis
The transport bound an AF_UNIX path under TMPDIR and waited for the child to connect. That path has to fit in sun_path (108 bytes), so a deep TMPDIR made every worker spawn fail, and the bind(2) itself is denied under a sandbox. The parent now keeps one end of a socketpair and installs the other on descriptor 3 before exec, which the worker opens as php://fd/3. The pair is connected from the start, so the accept poll and its ten-second deadline are gone; a child that dies before reading surfaces as EOF on the first call, where worker_state already attaches its exit status. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06fix(platform-repository): report the icu and imagick librariesnsfisis
PlatformRepository probes ResourceBundle, IntlChar and Imagick to derive lib-icu-cldr, lib-icu-unicode and lib-imagick-imagemagick. Those probes ran against the shim's hard-coded class_exists allowlist, which never names them, so the packages were silently missing: on a machine with intl, `show --platform` listed fewer libraries than upstream Composer does. The runtime seam now asks the real PHP: hasClass over RPC, and construct / invoke through the worker for the three classes PlatformRepository reaches. A live PHP object has no PhpMixed counterpart, so the seam answers with the entries the caller reads off it. The seam's own callers read those entries instead of returning null and the empty string. Two addLibrary calls also had replaces and provides swapped, dropping `lib-libxslt replaces lib-xsl` and `lib-zip-libzip replaces lib-zip`. `show --platform` now matches upstream Composer byte for byte, and all 59 provideLibraryTestCases datasets pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06fix(repository-manager): fail explicitly on an unknown repository classnsfisis
createRepository instantiates `new $class(...)` from the name registered for the type. The port dispatches over the classes it implements, and the remaining arm was a todo!(). setRepositoryClass is public API, so a plugin registering a class of its own reached it and panicked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06fix(download-manager): name the downloader class in its LogicExceptionnsfisis
getDownloaderForPackage reports get_class($downloader) when the resolved downloader's installation source does not match. Rust has no runtime class name, so the message was built from a shim stub that panicked instead — the error could never be returned. DownloaderInterface now requires PhpClass, the trait already used for the same purpose on Command, and each downloader states the name PHP reports. That leaves get_class_obj without callers, so it is gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06test(version-selector): drop four stale #[ignore] attributesnsfisis
The reason string blamed shirabe_php_shim::runtime::constant(), which PlatformRepository stopped reaching once its constant lookups went through the RuntimeInterface seam. All four pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06feat(plugin): let links and release dates cross the RPC boundarynsfisis
The link getters and setters and the release date accessors were explicit errors for every non-empty value, because an immutable value has no entity to point a handle at. They now cross as materialized values: the descriptor names the real class and the constructor arguments, and each side builds a genuine instance of its own. The semver constraint a link holds is encoded structurally rather than re-parsed from its string form, so the pretty strings and the conjunctive flag survive the crossing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>