aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/package/locker.rs
AgeCommit message (Collapse)Author
2026-08-23fix(fs): carry file_get_contents results as bytesnsfisis
file_get_contents() and file_get_contents_with_max_length() return Vec<u8> instead of a from_utf8_lossy'd String. Call sites whose consumer takes a &str still convert lossily and are marked TODO(bytes). file_get_contents_with_max_length() now reads at most the requested number of bytes instead of the whole file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22feat(cli): tell the user to run `shirabe`, not `composer`nsfisis
Messages that instruct the user to run a command named the Composer binary. Shirabe ships as its own binary, so the hints now name it. File names (composer.json, composer.lock), the "composer" repository type and prose about Composer itself are untouched. The installer and functional integration fixtures live in the Composer submodule and cannot be edited, so their expected output is normalized on load.
2026-08-18fix: propagate ported exceptions instead of flattening themnsfisis
`ProcessExecutor::execute_args` existed only to turn `execute`'s `anyhow::Result` into an exit code of 1, so every one of its ~87 call sites silently took the "command failed" branch on an error PHP would have thrown. It is gone; callers use `execute` and propagate with `?`. Where the enclosing function had no `Result` to propagate into, its signature grew one, up to and including `Git::get_version`, `Svn::binary_version`, `GitHub`/`GitLab`/`Bitbucket::authorize_oauth`, `InitCommand::get_git_config` and `DiagnoseCommand::check_git`. The VCS drivers had the same problem in the other direction: their `get_contents` returned `Result<Response, Box<TransportException>>`, a type too narrow for the PHP method, which lets any Throwable out of the `catch (TransportException $e)` block. Every non-transport error was therefore rewritten into a `TransportException` with code 0, which the callers switch on. They now return `anyhow::Result<Result<Response, Box<TransportException>>>`: the outer `Result` carries what PHP does not catch, the inner one the exception the drivers handle. That signature also restores `GitLabDriver::getContents`: the 400/401 `TransportException`s it raises to force authentication are thrown inside its own `try` block and handled by its own `catch`, but the port returned them straight to the caller, so the authentication flow behind them never ran. `impl_php_exception!` gains `From<Box<$ty>> for anyhow::Error` so a caught exception can be re-propagated with `?`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18fix(process-executor): model commands as a CommandLine enumnsfisis
Commands were carried as `PhpMixed`, whose `String`/`List` variants do not tell a shell command line apart from an argv list at the type level. `Perforce::execute_command` and `Git::run_command` therefore funnelled string commands through `execute_args`, spawning `p4 set` or `git command` as a single argument instead of running it through a shell as PHP does; their tests were written against that shape. Introduce `CommandLine::{Shell, Args}` and use it for every `ProcessExecutor` entry point, the mock expectation queue and the `Git::run_command` callables. The unreachable "Invalid command type" branches disappear with it, and the affected tests go back to the string expectations the PHP suite uses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18refactor(function-exists): drop checks for always-present capabilitiesnsfisis
function_exists() returns false in PHP when a function is blocked by disable_functions, when its extension is not compiled in, or when the PHP version predates it. None of those apply to a native binary.
2026-08-18refactor(preg): add preg_is_match for existence-only call sitesnsfisis
The capture groups were discarded at 162 of the preg_match call sites, which only tested the Option. They now call preg_is_match, which lets the regex engine skip capture tracking. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18refactor(preg): drop the offset argument from preg_matchnsfisis
Every call site but one passed offset 0. The remaining one, the UTF-8 chunking loop in Application, slices the subject instead: its pattern has no anchor or lookaround, so matching a suffix is equivalent to starting the search at that offset. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18refactor(pcre): inline Preg into its call sites and drop the cratensfisis
Preg had shed everything it owned: after the last few rounds its methods were one-line forwards to the shim's preg_*(), differing only in a default argument or a wrapper the caller unwrapped anyway. The 460 call sites now name the shim function, and shirabe-pcre is gone from the workspace along with its LICENSE entry. The forwards expand as they read: isMatch becomes preg_match2(.., 0).is_some() (is_none() where PHP negates it), isMatch3 and match3 drop the .is_some(), matchAll counts through preg_match_all2(..).occurrence_count(), and replace4/replace5 spell out the limit and count arguments preg_replace2 takes. Callbacks are the one place the shapes differ: preg_replace_callback carries an error out of the callback, so the fourteen infallible closures wrap their result in Ok() and expect() it back. Config::process() is the fifteenth, and it drops the `error` cell it captured to smuggle a failure past a closure that could only return a String. The `?` in the closure now carries it, which is what the PHP does -- a throw from the callback leaves preg_replace_callback at the failing match rather than running the remaining replacements and reporting the last error. The module doc that explained why composer/pcre's exceptions and *StrictGroups() variants have no counterpart moves to the shim's preg module, where the functions it describes live. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18refactor(preg): split PregMatches reads into get() and name()nsfisis
PregMatches keyed both forms of a capture group through CaptureKey, so every read built one: a usize wrapped in an enum, or worse, a String allocated to name a group that regex::Captures can look up from a &str. It now mirrors regex::Captures instead -- get() takes the group number, name() the group name -- and the enum drops out of the type entirely. That is 285 call sites across 59 files, and the named ones carry most of the win: `matches.get(&CaptureKey::ByName("host".to_string()))` reads as `matches.name("host")`. ProcessExecutor loses a `user_key` binding that existed only to build the key once. CaptureKey stays as the key type of PregMatchesAll and PregMatchesAllWithOffsets, where numbered and named entries share one IndexMap and a key type is the point. Five files still name it. Also retargets the two preg_match_all comments that described the occurrence count through `matches[&CaptureKey::ByIndex(0)].len()`, an Index impl these types no longer carry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18refactor(pcre): hand back the match instead of copying it outnsfisis
Preg::match4 and Preg::replace_callback gave callers a PregMatchedGroups: an IndexMap rebuilt from the match with an owned String per group, plus a second String for a named group's name key. That is the copy PregMatches shed when it started wrapping regex::Captures, reinstated one layer up -- and nearly every regex call in the tree goes through Preg rather than the shim's preg_* directly, so almost nothing saw the borrow. PregMatchedGroups existed only to drop the null (unmatched) groups the old PregMatches held as Option<String> values. PregMatches::get reports a non-participating group as None on its own, so the two read alike and the type collapses into it. Call sites still reach groups through get(&CaptureKey::ByIndex(N)); what changes is that the value arrives as a &str borrowed from the subject, which the signatures now carry as a lifetime. Three places needed the borrow reckoned with rather than a mechanical rewrite: PhpFileCleaner::clean and Problem::get_messages read their groups out before mutating what the match borrows, and Git::get_authentication_failure names the lifetime of its url argument, which the result borrows instead of self. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(pcre): return the Preg $matches instead of filling an out-paramnsfisis
`Composer\Pcre\Preg` fills `$matches` through a by-ref parameter, and the port mirrored that with a `&mut` (or `Option<&mut>`) out-param plus a bool or count return. Callers had to declare an empty map one line ahead of the call, and the type never said the map is only meaningful when the call matched. Return the matches instead: - match3/match4/is_match3/is_match4 -> Option<PregMatchedGroups> - is_match_named -> Option<PregNamedGroups> - match_all2/is_match_all -> PregMatchesAll - is_match_all_with_offsets3 -> PregMatchesAllWithOffsets Nothing is lost: the bool is `Option::is_some()`, and the occurrence count is the length of any one column of a PREG_PATTERN_ORDER map, now spelled `PregMatchesAll::occurrence_count()`. is_match() still answers the bool question directly for callers that want no groups. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17refactor(preg): wrap the preg_* $matches maps in newtypesnsfisis
The five IndexMap shapes that the preg_* functions and Preg fill in are now distinct types generated by preg_match_map!, so a matches map no longer interchanges with any other map of the same key and value type. Index<usize> is kept alongside Index<&Q> because call sites such as config_command and event_dispatcher reach for a group by its position in the map rather than by its capture key.
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(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-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-06refactor: replace literal-list in_array_strict with matches!nsfisis
Call sites whose haystack was an inline array of literals (or a local built solely to feed one) had to wrap both sides in PhpMixed just to compare, allocating a String per element on every call. matches! does the same test against the underlying &str/i64/Option directly, so the PhpMixed round trip and its .to_string()/.clone()/.iter().map() conversions are gone. Sites whose haystack is a runtime value or a named constant array are left on in_array_strict: inlining a named constant would duplicate its contents at the call site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06refactor(php-shim): split in_array into strict and loose variantsnsfisis
2026-08-06chore: drop @param/@return tags that only restate Rust typesnsfisis
The ported docblocks copied @param and @return straight from the PHP source. When such a tag carries nothing but a type and an argument name, the Rust signature already states it, so the line is noise. Tags whose text adds prose beyond the type are kept. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06refactor(php-shim): take filesystem paths as impl AsRef<Path>nsfisis
The shim's filesystem entry points took `&str` even though each one resolves to a local path through `std::fs` or a syscall, so callers holding a `PathBuf` had to stringify it at the call site. They now take `impl AsRef<Path>`, the form `file_exists`, `is_dir`, `unlink` and the rest of the already-converted set use. `Phar`, `PharData` and `ZipArchive` keep their archive path as a `PathBuf`. `PharData::compress` names the compressed sibling by appending the suffix to the file name rather than formatting the path into a `String`. Arguments PHP resolves through a stream wrapper (`fopen`, `file_put_contents`, `include`) still take `&str`, as do the byte-string operations (`dirname`, `basename`, `pathinfo`) and archive-internal entry names, which are `/`-joined logical names rather than OS paths. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02fix(json): propagate JsonFile::encode errors instead of unwrappingnsfisis
PHP's JsonFile::encode throws a RuntimeException when json_encode fails; the port swallowed that into an .unwrap() marked TODO(phase-c). Return anyhow::Result from encode/encode_with_options and propagate at every call site (print_table and list_repositories become Result-returning to carry it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25refactor: replace redundant clones with movesnsfisis
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25feat(tracing): init tracing subscriber from $SHIRABE_TRACINGnsfisis
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-24fix(locker): return stability-flags as int, not stringnsfisis
Locker::get_stability_flags returned IndexMap<String, String>, converting each value via PhpMixed::as_string(), which only matches the String variant. composer.lock's "stability-flags" values are always JSON integers (BasePackage::STABILITIES), so every flag silently decoded to "" and installer.rs's downstream .parse::<i64>() defaulted it to 0 (stable). This made any locked package pinned via a non-stable stability-flags entry look "unacceptable" during `composer install`, silently dropping it from the solver's pool instead of fixing/requiring it — turning a real dependency conflict into a spurious "lock file needs changes" result. Return i64 directly, matching RootPackageInterface::get_stability_flags and set_lock_data's existing convention for this same PHP array shape.
2026-07-18perf(regex): eliminate per-call clone overhead in preg_* dispatchnsfisis
regex::Regex::clone() does not share the underlying meta engine's search-cache pool, so every fresh clone pays a ~10us warmup cost on its first use. Two changes together eliminate this across nearly all preg_* call sites: - A php_regex! macro resolves PHP-style patterns to a per-call-site &'static regex::Regex (via regex-macro's LazyLock), applied at the majority of call sites throughout the codebase. - Call sites still passing dynamic pattern strings go through PATTERN_CACHE, which now stores Arc<(Regex, bool)> and hands out Arc::clone()s instead of cloning the Regex itself. PregPattern::resolve() returns a ResolvedPattern enum (Arc or 'static reference) rather than an owned Regex, so neither path ever clones the Regex proper. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-29chore(lint): ban bare `use anyhow::Result` and fully qualify itnsfisis
Add a no_banned_use linter that forbids importing anyhow::Result, and update all call sites to reference it via its fully-qualified path so it is never confused with std::result::Result. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28refactor: add linternsfisis
2026-06-27fix(json): encode empty stdClass fields as {} not []nsfisis
Empty PhpMixed::Array now serializes as [], so the several places that build a PHP `new \stdClass` via an empty Array were emitting [] where Composer writes {}. Use PhpMixed::Object for those empty-object cases: - InitCommand: require / require-dev - Locker::fixupJsonDataType: stability-flags / platform / platform-dev - JsonConfigSource fallback: require/config keys that must stay objects Align the affected test expectations with Composer: JsonFile::read() decodes with assoc=true, collapsing the on-disk {} back to [], so reads expect []. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27refactor(composer): hold managers behind *Interface traitsnsfisis
Composer/PartialComposer exposed its RepositoryManager, InstallationManager, EventDispatcher, Locker, DownloadManager, AutoloadGenerator and ArchiveManager as concrete types, but Composer's public setters (setDownloadManager() etc.) let plugins swap in subclasses. Introduce a *Interface trait per manager and store each as Rc<RefCell<dyn ...Interface>> so a replacement is honored. Only Composer's slots and the sinks fed from its accessors become trait objects; managers injected concretely at construction keep their concrete references, matching PHP semantics. Fluent setters on the affected classes now return () and Locker::update_hash is de-generified to a boxed FnOnce so the traits stay object-safe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27fix(locker): build InstalledRepository with locked and root reposnsfisis
getMissingRequirementInfo created an empty InstalledRepository (both repos were left commented out as a stub), so every locked requirement was reported as "not present in the lock file". Pass [lockedRepo, rootRepo] to the constructor as the PHP does. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27refactor: fix compiler warnings and clippy warningsnsfisis
2026-06-24feat(process): wire execute output handling to a callback modelnsfisis
Replace the Option<&mut PhpMixed> output plumbing with the IntoExecOutput trait modelling each PHP `$output` case (forward, capture-to-buffer, discard, callback). This lets do_execute pass a real output handler to Process::run, captures output back via get_output, and lets Svn pass its streaming filter handler through execute instead of skipping it.
2026-06-24chore: unwrap meaningless PhpMixed::String()nsfisis
2026-06-21refactor(php-shim): drop reset()/reset_first()/end_arr() array helpersnsfisis
These slice/map wrappers mirrored PHP's internal array pointer and have no clean Rust equivalent. Remove them and replace the lone caller with direct first-element access; the rest were unused imports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20fix(path): propagate PathBufnsfisis
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20refactor(php-shim): drop Box wrapping from PhpMixed List/Arraynsfisis
The List and Array variants of PhpMixed boxed their elements unnecessarily. Store PhpMixed values directly and update all callers accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20refactor: auto-fix clippy warningsnsfisis
2026-06-14refactor(pcre): drop Result from Preg method return typesnsfisis
The Preg methods panic on PCRE failure (per the file header rationale), so their anyhow::Result wrappers never carried an Err. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14refactor(pcre): drop strict-groups Preg variantsnsfisis
Rust's type system already distinguishes participating from non-participating capture groups via Option, so the *StrictGroups methods add no safety here. Remove them and switch callers to the plain variants.
2026-06-12refactor(php-shim): replace literal sprintf calls with format!nsfisis
Convert every sprintf() call with a compile-time literal format string to format!, implementing Display for PhpMixed (delegating to php_to_string) so PhpMixed values render with PHP string semantics through {}. Also merge the format!-wrapped and conditional-literal dynamic sites into single format! calls. Genuinely runtime format strings (table styles, configurable error messages, command synopsis, progress-bar modifiers, regex-built messages) still go through sprintf. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10feat(phase-c): resolve exception-handling phase-b TODOsnsfisis
* Catch specific exception types instead of broad/placeholder handling. * Drop the shim Countable trait.
2026-06-09feat(datetime): resolve datetime TODOsnsfisis
Introduce shim functions and constants, replacing the ad-hoc chrono format strings and parse helpers used as phase-b placeholders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08feat(phase-c): resolve PhpMixed-conversion phase-b TODOsnsfisis
Implement the foundational PhpMixed conversion infrastructure (From<bool|i64|f64|String>, order-sensitive PartialEq matching PHP ===) and resolve the category-G phase-b TODOs that depend on it: - Fix VCS driver cache paths that discarded parsed JSON or diverged on null caches (svn/forgejo/gitlab/git-bitbucket/github). - Wire up real conversions previously stubbed or dropped: suggests platform config, audit ignore-severities, composer_repository search and ProviderInfo, class_loader prefix/classmap merges, locker lock diff comparison, advisory JSON serialization, SPDX license fields. - Make GenericRule take a typed ReasonData; populate RULE_ROOT_REQUIRE with the constraint and convert PhpMixed at the call sites.
2026-06-07feat(phase-c): resolve dynamic-dispatch phase-b TODOsnsfisis
Replace the PHP `call_user_func([$obj, $method], ...)` / `$obj->{'get'.$x}()` dynamic dispatches (category E) with static Rust dispatch. - json_config_source: inject the clean-update as a typed `FnOnce(&mut JsonManipulator) -> Result<bool>` closure instead of a method-name string, dropping the call_user_func_array round-trip through PhpMixed args. The auth-config method override moves into the add/remove_config_setting closures. - config_command: dispatch addConfigSetting/addProperty on the concrete JsonConfigSource via match. - locker: select getRequires/getDevRequires and getReplaces/getProvides via match on the existing handle getters (previously stubbed to empty Vec). - create_project_command: reuse the existing get_links_for_type helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06fix(array-dumper): emit package links and require Link pretty constraintnsfisis
Resolve the phase-b TODO that left the supported-link-types loop as dead code (links were always an empty Vec), so requires/conflicts/provides/ replaces/require-dev are dumped again via PackageInterface::get_links_for_type, matching the PHP magic-call loop. Every Link in production is constructed with a pretty constraint (all ArrayLoader/AliasPackage/PlatformRepository/InstalledRepository sites pass one), so make Link::pretty_constraint a required String instead of Option<String>. get_pretty_constraint() now returns &str directly rather than anyhow::Result<&str>, dropping the unreachable UnexpectedValueException guard, and all call sites are updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06refactor(repository): make read methods fallible and take &mut selfnsfisis
Change RepositoryInterface and WritableRepositoryInterface read methods (find_package, find_packages, get_packages, load_packages, search, get_providers, get_canonical_packages) to take &mut self and return anyhow::Result, so lazy-loading repositories such as ComposerRepository can perform fallible I/O and mutate internal state on access. Update all implementors and call sites to propagate the Result and pass mutable references. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05refactor(json): model JsonFile encode/write options as a typed structnsfisis
Replace the i64 bitmask + encode_with_indent split with a JsonEncodeOptions struct (Default = JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE, default indent). encode/write each get a default form plus an explicit encode_with_options/write_with_options variant, mirroring PHP's optional $options argument. write_with_options always encodes with self.indent, matching PHP write(). Also reconcile call sites with the PHP sources: most ported sites passed 0 where Composer omits the argument (= default flags), so JsonManipulator/ShowCommand and JsonConfigSource now use the default options; only ComposerRepository and Locker genuinely pass 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04feat(locker): wire get_locked_repository via LockArrayRepositorynsfisis
Implement LockArrayRepository::new/add_package and CanonicalPackagesTrait::get_packages (delegating to inner ArrayRepository), expose add_package on LockArrayRepositoryHandle, and add set_root_package_alias on CompleteAliasPackageHandle. With these in place, Locker::get_locked_repository is fully wired: load each locked package, register it (plus its aliasOf for AliasPackages) in package_by_name, then build CompleteAliasPackage handles for lock-file aliases via as_complete_package() narrowing. Drop the unused RepositoryInterface::clone_box default and the stale inherent LockArrayRepository::clone_box (no callers). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03feat(php-shim): align fwrite/touch2/PHP_OS_FAMILY/PHP_URL_SCHEME with PHPnsfisis
Adjust fwrite to return int|false equivalent (Option<i64>), add touch2 with mtime, implement PHP_OS_FAMILY via std::env::consts::OS, and swap the PHP_URL_HOST placeholders for the now-available PHP_URL_SCHEME. Wire these into locker, filesystem, platform, and remote_filesystem. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01fix(package): port every PHP clone operator to handle dup()nsfisis
2026-05-29refactor(repository): introduce typed LockArrayRepositoryHandlensfisis
Share LockArrayRepository via Rc<RefCell<_>> while preserving the PHP ?LockArrayRepository type strength: Request.locked_repository, Locker ::get_locked_repository, and the installer create_* helpers now thread the typed handle instead of an owned LockArrayRepository, and pool builder identity check becomes a strict ptr_eq via widening into RepositoryInterfaceHandle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>