Skip to content

feat(magos-modificus): implement Phase 1 Profiles library - #17

Merged
ModifAmorphic merged 6 commits into
mainfrom
magos-modificus/profiles
Jul 1, 2026
Merged

feat(magos-modificus): implement Phase 1 Profiles library#17
ModifAmorphic merged 6 commits into
mainfrom
magos-modificus/profiles

Conversation

@ModifAmorphic

Copy link
Copy Markdown
Owner

What

Phase 1 Profiles library for Magos Modificus — the profile data model, persistence, mods.lst generation, and config integration. The foundation Enginseer-client (Phase 1's capstone) and the UI (Phase 3) build on. Minimal scope: no shared-mod storage, no version policy, no auto-sort, no dependency resolution, no mod download (those are Phase 2 / Integrations / later).

Spec: docs/architecture/MAGOS-MODIFICUS.md → Profiles + the mods.lst contract in docs/architecture/MOD_LOADER-DMF.md.

What's in it

  • Data model: Profile (Id/Name/CreatedAt/Mods), ProfileSummary (Id/Name), ModListEntry (Name/Enabled/Order — immutable record, init-only).
  • IProfileService: CRUD (create/get/list/rename/delete), mod-list management (GetModList/SetModOrder/SetModEnabled/AddMod/RemoveMod), and PrepareModRoot(id) → string (the --mod-path seam — ensures the mod root + writes mods.lst).
  • Persistence: <ProfilesBaseFolder>/<guid>/{profile.json, mods/}; auto-creates dirs on first run (closes the Phase-0 gap).
  • mods.lst: enabled mods in Order, one per line, UTF-8 without BOM (correctness-critical — a BOM prefixes the first mod name in the Lua loader), trailing newline; empty/all-disabled → 0-byte file; faithful to stored order (no auto-sort, no DMF-first enforcement — that's a higher layer).
  • 41 tests (black-box, via the AddProfiles() DI path; persistence proven across service instances).

Verification trail

  • coder — implemented to spec; 49 tests pass (41 Profiles + 8 General); ~98% line coverage on ProfileService; no new NuGet deps (in-box System.Text.Json); versions all latest-stable.
  • qa → PASS — all 7 acceptance criteria met; all 7 deviations sound + tested; mods.lst no-BOM independently byte-level-verified + the rationale confirmed against the Lua loader code path (file.lua %s trim doesn't strip a BOM → it would prefix the first mod name); version pins latest-stable (coverlet 10.0.1 — Phase-0 miss resolved).
  • code-review → APPROVE WITH NITS — independently traced the loader's mods.lst consumption path (confirmed contract faithfulness); confirmed the Phase 2 storage-swap seam holds (no path/shared-vs-local leakage through the interface or data types); all deviations sound. All nits folded in: ModListEntry → immutable record (was a mutable class with a misleading immutability doc claim); ListProfiles sorted by Name; storage-agnostic RemoveMod doc; tests/ doc listing updated.
  • CI — gates Win + Linux on this PR (the workflow is PR-gated against main); Linux build/test green locally (49/49).

Phase 2 storage-swap seam (by design)

IProfileService is shaped so Phase 2 swaps storage (per-profile dirs → shared-first + staging) without changing the interface: PrepareModRoot(id) → string abstracts "give me the --mod-path"; all storage paths stay private to ProfileService; ModListEntry will grow fields (version policy, source) but stays immutable. Phase 2 watch-items already flagged for that rewrite: atomic profile.json writes, concurrency, mod-name case-canonicalization for the Windows FS.

Notes

Replaces the Phase-0 stub with the minimal Profiles library: the data

model (Profile, ProfileSummary, ModListEntry), IProfileService, and a

filesystem-backed ProfileService. Each profile persists under

<ProfilesBaseFolder>/<guid>/ as profile.json + a mods/ mod root;

PrepareModRoot writes mods.lst (enabled mods in Order, UTF-8 no BOM,

trailing newline) and returns the --mod-path.

IProfileService is shaped for a Phase 2 storage swap (per-profile dirs

-> shared-first + staging) without interface change: PrepareModRoot

abstracts the --mod-path and no storage detail leaks through. Storage

paths are private to ProfileService.

Registered as a singleton via AddProfiles (stateless over the FS;

MagosConfig is itself a singleton). No new NuGet deps — JSON uses the

in-BCL System.Text.Json (the csproj only adds the config ProjectReference).
40 tests across CRUD round-trip, mod-list management, PrepareModRoot /

mods.lst generation (enabled-only, Order, disabled omitted, empty file,

UTF-8 no BOM, faithful duplicate handling, idempotency), first-run dir

creation, and the unknown-id / missing-mod-dir edges. Tests resolve

IProfileService through the real AddProfiles DI path against a per-test

temp ProfilesBaseFolder (black-box against the interface).

ProfileService.cs at ~98% line / 90% branch coverage; the remaining gap

is a defensive IO-error catch that needs a filesystem mock to hit.

Test deps mirror General.Tests (versions verified latest-stable on NuGet).
AGENTS.md, README.md, and magos-modificus/README.md still labelled the

Profiles library a 'stub' after Phase 1 landed — stale per the repo's

doc-currency rule. Soften those references to show Profiles is

implemented; the other four libraries (integrations, steam,

enginseer-client, launcher) remain 'stub' until their Phase 1 tracks

land. No code changes.
…tate)

ModListEntry.Enabled and .Order had public setters, so a consumer could

mutate an entry returned from GetModList (silently non-persisting — the

service always read-fresh/mutate-fresh/write). That contradicted the

Profile.Mods doc claim of immutability.

Convert ModListEntry to a sealed record with init-only properties;

SetModEnabled/SetModOrder now rebuild the changed entries via 'with'

expressions, and AddMod rebuilds via Append. RemoveMod already filtered

(no entry mutation), unchanged. Tighten Profile.Mods + ModListEntry doc

comments to state the now-true immutability. Record (vs class-with-init)

keeps the rebuilds one-line as Phase 2 adds version/source fields.
…c RemoveMod doc

ListProfiles returned filesystem-enumeration order (unspecified). Sort

by Name with StringComparer.Ordinal (stable) so the UI profile picker

gets a predictable order; add a test asserting the sort (also pinning

Ordinal vs OrdinalIgnoreCase behavior).

Reword RemoveMod's doc to be storage-agnostic ('the mod's local files,

if any') instead of naming the per-profile mod root, so the doc holds

under Phase 2's shared-first storage (where the dir may be a shared

reference that must not be deleted). Signature unchanged.
AGENTS.md and magos-modificus/README.md listed only Magos.Modificus.General.Tests/

under tests/; add Magos.Modificus.Profiles.Tests/ to both, matching the

existing listing style. No other library status touched.
@ModifAmorphic
ModifAmorphic merged commit f355ceb into main Jul 1, 2026
2 checks passed
@ModifAmorphic
ModifAmorphic deleted the magos-modificus/profiles branch July 1, 2026 00:55
ModifAmorphic added a commit that referenced this pull request Jul 1, 2026
## What

Phase 1 Steam library for Magos Modificus — discover everything needed
to launch Darktide modded (Steam install, Darktide install, compatdata,
Proton version) + an escape hatch (report missing pieces for the UI to
prompt) + game-running detection. Enginseer-client (Phase 1 capstone)
consumes the discoveries to set the Proton env vars + invoke the
launcher; the UI (Phase 3) consumes the discovery result + game-running
state. **Steam discovers; Enginseer-client acts.**

Spec: `_local/phase1-steam-spec.md` (approved). Contract:
`docs/architecture/MAGOS-MODIFICUS.md` (Launch → Linux).

## What's in it

- **`ISteamService.Discover() → DiscoveryResult`** +
**`IsGameRunning()`**. `DiscoveryResult` is a flat record of nullables
(`SteamInstallPath`, `DarktideGameBinaryPath`, `CompatdataPath`,
`ProtonBinaryPath`, `ProtonVersion`) + `Status`
(Complete/Partial/Failed) + `Warnings` — the null fields drive the
escape-hatch prompt.
- **Linux discovery:** Steam install (default `~/.local/share/Steam` →
Flatpak fallback), libraries via `libraryfolders.vdf`, Darktide install,
compatdata (`steamapps/compatdata/1361210/`), Proton (`Proton -
Experimental` → highest-versioned real Proton → `compatibilitytools.d/`
→ null/escape-hatch).
- **Windows discovery:** registry `HKCU\Software\Valve\Steam\SteamPath`
→ default; same VDF/Darktide search; no compatdata/Proton (native).
- **Testability seams:** `SteamDiscoveryOptions` (injectable roots +
`DiscoveryPlatform`), `ISteamRegistryReader`, `IProcessLookup` — Windows
logic runs on Linux CI; game-running is mockable.
- **Arch-doc fix folded in:** Launch → Linux now states Magos sets
**both** `STEAM_COMPAT_DATA_PATH` and `STEAM_COMPAT_CLIENT_INSTALL_PATH`
(the live-validated finding — the working invocation set both).
- **38 tests** (synthetic-layout discovery, VDF parsing, Proton
selection, Flatpak, Windows-via-abstraction, game-running, escape-hatch
fidelity).

## Notable bug caught during impl

The coder caught a subtle real bug: a literal "highest-versioned `Proton
X.Y` in `steamapps/common`" would parse the **Darktide game dir itself**
(`Warhammer 40,000 DARKTIDE`) as "Proton 40.0" and pick it over every
real Proton. Fixed by requiring an actual `proton` script in the dir
(the defining trait of a Proton install) + a regression test
(`Darktide_game_dir_is_not_mistaken_for_a_proton_build`). qa
independently verified the bug + the fix.

## Verification trail

- **coder** — implemented to spec; 87 tests pass (38 Steam + 49
existing); ~90%+ coverage on the testable surface; **no new NuGet deps**
(initially added `Microsoft.Win32.Registry`, removed it as
NU1510-redundant — the type is framework-provided on net10.0,
`OperatingSystem.IsWindows()`-gated, no-ops on Linux).
- **qa → PASS** — all 8 acceptance criteria met; all 4 deviations sound;
**independently verified the Darktide-Proton bug is real + the fix
covers it**; no `Microsoft.Win32.Registry` dep; all package pins
latest-stable.
- **code-review → APPROVE WITH NITS (merge as-is)** — confirmed the
Proton-script gate robust against other `steamapps/common` tooling, the
version parser correct (hand-traced), the VDF parser sound, the
escape-hatch honest, the Windows registry path sound, the testability
seams clean. All findings are optional nits; reviewer recommends merging
as-is + tracking follow-ups.
- **CI** — gates Win + Linux on this PR; Linux build/test green locally
(87/87).

## Tracked follow-ups (not blockers)

- **`IsGameRunning` Linux/Proton accuracy** —
`Process.GetProcessesByName("Darktide")` may false-negative under
Proton; **fix before Phase 3 UI consumes it** (a false "not running"
could permit a double-launch). Informational only for Phase 1.
- Legacy pre-2019 `LibraryFolders` VDF format not parsed (graceful
fallback; doc note warranted).
- Windows library dedup is `Ordinal` (registry lowercase/forward-slash
vs VDF backslash) → Steam root double-counted (harmless redundant probe
+ misleading count).
- `Failed` status carries empty `Warnings` (diagnostic in log only) —
Phase 3 UI would want a human-readable warning.
- Flatpak + `compatibilitytools.d` edge (escape-hatch-covered; proper
fix = discover Flatpak's own compat-tools dir).

## Notes

- Builds on merged #16 (scaffold) + #17 (Profiles). Steam is independent
of Profiles (parallel Phase 1 track).
- Enginseer-client (next track) consumes: `SteamInstallPath` +
`CompatdataPath` → env vars; `ProtonBinaryPath` → `proton run`;
`DarktideGameBinaryPath` → `--game-binary` (Z:\-translated).
ModifAmorphic added a commit that referenced this pull request Jul 2, 2026
…de (#20)

## What

Phase 1 Enginseer-client — the launch façade (the **Phase 1 capstone**).
`IEnginseerLaunchService.Launch(profileId)` resolves the profile +
discovery internally, then invokes `magos_launcher.exe`: **Windows
directly**, **Linux via `proton run`** with both `STEAM_COMPAT_*` env
vars + `Z:\`-translated paths. Returns `LaunchResult` (`Launched` /
`DiscoveryIncomplete` + missing fields / `Error`). Consumes Profiles
(`PrepareModRoot` → `--mod-path`) + Steam (`DiscoveryResult` → env vars
+ proton + game-binary).

**The launch smoke test is a USER-machine validation** (no
Darktide/Windows/Proton in CI). The PR provides a **CLI smoke harness**
(`dotnet run -- discover/list/launch`) for the user to run the real
launch on their Win + Linux boxes — the underlying path was already
live-validated manually.

Spec: `_local/phase1-enginseer-client-spec.md` (approved).

## What's in it

- **`IEnginseerLaunchService.Launch(Guid profileId) → LaunchResult`** —
resolves profile (`IProfileService.PrepareModRoot`) + discovery
(`ISteamService.Discover`) internally; never throws.
- **Windows path:** `Process.Start(launcher, args)` directly — no
Proton, no translation. Args: `--game-binary`, `--mod-path` (from
`PrepareModRoot`), `--log-file`.
- **Linux path:** sets `STEAM_COMPAT_DATA_PATH` +
`STEAM_COMPAT_CLIENT_INSTALL_PATH` from `DiscoveryResult`;
`Z:\`-translates `--game-binary` + `--mod-path` + `--log-file`; invokes
`<ProtonBinaryPath> run <launcher.exe> <args>`.
- **`DiscoveryIncomplete`:** derived from the null required fields (≡
Steam's `Status != Complete` by construction — verified equivalent
per-platform) + returned with the missing field names (`nameof`-based)
for the Phase 3 UI's escape-hatch prompt.
- **`IProcessLauncher` seam** — mockable; real `ProcessLauncher` uses
`ProcessStartInfo.ArgumentList` (argv-correct, no shell —
paths-with-spaces safe, no injection surface).
- **CLI smoke harness** — dual-purpose test project (`dotnet test` =
xUnit; `dotnet run -- discover/list/launch` = real-composition harness);
README + instructions embedded.
- **28 tests** (Windows/Linux arg assembly, `Z:\` translation, both env
vars, `proton run` invocation, `DiscoveryIncomplete` per-platform,
profile integration, error cases, DI).

## Verification trail

- **coder** — implemented to spec (resumed cleanly after a session-stall
abort); 144 tests pass (28 EnginseerClient + 116 existing); no new NuGet
deps; all pins latest-stable. Caught + the lead folded two real fixes
(below).
- **qa → PASS** — all 10 acceptance criteria met; all 5 deviations
sound; **the smoke harness executes** (`dotnet run -- discover` → real
composition → DiscoveryIncomplete/exit 2 in this env, correct);
`DiscoveryIncomplete`↔`Status` equivalence verified; no new deps.
- **code-review → REQUEST CHANGES on one should-fix (now fixed):** the
`--log-level` vocabulary mismatch — Magos forwarded its Serilog level
name to the shell, but the shell only recognizes
`error`/`warn`/`info`/`debug`/`trace` → `Warning` silently became `info`
(more noise), etc. **Fixed by omitting `--log-level` entirely** (rely on
the shell's `info` default; decouple the two logs — they serve different
purposes; consistent with the `--steam-app-id` deviation). The reviewer
verified everything else sound (the `Status` equivalence, `Z:\`, `proton
run`, both env vars, the `IProcessLauncher` seam, the smoke-harness
design). Fixing this was the condition to flip to APPROVE.
- **CI** — gates Win + Linux on this PR; Linux build/test green locally
(144/144).

## Tracked follow-ups (not blockers)

- `#2` dead `DarktideSteamAppId` constant — documented placeholder for a
future `--steam-app-id` config-override field.
- `#3` generic `Error` message on process-start failure — the specific
exception lands in the log, not `LaunchResult.Message`. Thread it out
when Phase 3 UI consumes `Error` + wants to show why.
- `--steam-app-id` + a dedicated shell-log-level config field — add
if/when a knob is needed (Phase 1 relies on the launcher's defaults).

## After this merges

Phase 1 (core domain libraries) is **complete** — Profiles + Steam +
Integrations + Enginseer-client all in main. → **Phase 2: shared-mod
storage** (the version-policy allocation model + staging). The
`IEnginseerLaunchService` interface is designed so Phase 3 (UI) consumes
`Launch` + handles `DiscoveryIncomplete` (escape-hatch prompt) cleanly,
with a future `Launch(profileId, DiscoveryResult)` overload for cached
discovery.

Builds on merged #16 (scaffold) + #17 (Profiles) + #18 (Steam) + #19
(Integrations).
ModifAmorphic added a commit that referenced this pull request Jul 2, 2026
## What

Phase 2 shared-mod storage — the shared-first allocation + symlink
staging model from the arch doc. A profile uses the global shared copy
of a mod when version policies are compatible; a profile-local
`diverged/` copy when they diverge. `PrepareModRoot` now **stages**
(symlinks shared/diverged mod dirs into a `staged/` dir + writes
`mods.lst`), replacing Phase 1's per-profile `mods/` dir. **Symlinks,
not copies** (download once, store once — copying defeats the purpose).
Built on Profiles (Phase 1); `IProfileService` contract preserved
(signature unchanged; impl swapped).

Spec: `_local/phase2-shared-mod-storage-spec.md` (approved). Phase plan:
`_local/v1-phase-plan.md`.

## What's in it

- **New `Magos.Modificus.SharedMods` project** — `ModVersionPolicy`
(`Pinned(version)` / `Latest`, JSON-polymorphic via `$kind`),
`AllocationResolver` (the 4-case share/diverge rule, by policy intent
not current version), `SharedModEntry`, `ISharedModStore`
(manifest-backed list/get/add-upsert/remove).
- **`IProfileService` evolution** — `ModListEntry.Policy` added
(additive, default Latest); `AddMod(.., policy)` + `SetModPolicy(..)`;
`PrepareModRoot` signature unchanged (impl → symlink staging).
- **Staging** — for each enabled mod, resolve share/diverge; symlink
shared → `<SharedModsFolder>/<mod>` or diverged →
`<profile>/diverged/<mod>` into `<profile>/staged/`; write `mods.lst`
from successfully-staged mods; regenerate each launch. Missing
`diverged/` copy (Phase 4 pending) → skip + warn (no crash).
- **Divergence transitions** — `SetModPolicy` share→diverge (metadata;
Phase 4 acquires the local copy), diverge→share (drops `diverged/`).
- **On-disk layout:** `<SharedModsFolder>/{shared-manifest.json,
<mod>/}` + `<ProfilesBaseFolder>/<guid>/{profile.json, diverged/<mod>/,
staged/<symlinks + mods.lst>}`.
- **185 tests** (22 new SharedMods + 59 Profiles [updated + new
StagingTests] + 104 existing).

## Verification trail

- **coder** — implemented to spec; caught + handled a real **data-safety
risk**: clearing `staged/` for regeneration could, with a naive
`Directory.Delete(recursive)`, follow a directory symlink into the
shared store + nuke shared files. Fixed with symlink-aware cleanup
(deletes the link, never follows it) + a dedicated test. 185 tests, no
new deps.
- **qa → PASS** — all 8 acceptance criteria met; all 6 deviations sound;
**the data-safety claim rigorously verified** (`ClearStagedDir` checks
`ReparsePoint` before `Directory`; the test exercises a real dir-symlink
into the shared store + asserts shared files survive); allocation
resolution (4 cases + policy-intent); symlink staging (no copies);
divergence transitions; manifest persistence.
- **code-review → APPROVE WITH NITS** — **the data-safety invariant
independently verified** (the reviewer wrote a throwaway .NET 10 probe:
dir-symlink → `File.Delete` → target survived; dangling symlinks handled
via lstat semantics); `PrepareModRoot` signature byte-identical; Phase 1
backward-compat confirmed. Should-fix (Phase 4 `Version` equality quirk)
+ one nit folded in (see below).
- **CI** — gates Win + Linux on this PR; Linux build/test green locally
(185/185).

## Folded in from review

- **`// TODO(phase4)`** near the pinned-version comparison in
`AllocationResolver` — `Version` equality is component-count-sensitive
(`Version(1,0) != Version(1,0,0)`); dormant in Phase 2 but a Phase 4
trap when acquisition parses user pins / release tags. Marker so the
seam isn't lost.
- **`TryAddSingleton` for `ISharedModStore`** — consistency with the
`SymlinkCreator` seam; lets Phase 3 UI tests mock the store without
`AddProfiles()` clobbering it.

## Tracked forward-looking nits (not blockers; Phase 3/4)

- `ClearStagedDir` external-tamper defense-in-depth (assert `staged`
itself isn't a symlink before enumerating — only reachable via external
tampering, not the service's operations).
- `$kind` forward-compat: an unknown future policy variant deserialized
by an older build throws (SharedModStore catches it → empty;
ProfileService.ReadProfileFile doesn't → that profile unreadable on
older builds). Acceptable for single-dev rollout; revisit if
multi-version coexistence matters.
- `diverge→share` drops `diverged/` unconditionally (by spec) — Phase 3
UI should confirm when a populated `diverged/` copy is about to be
dropped.

## After this merges

Phase 2 complete → **Phase 3 (UI build-out)**: profile management UI,
mod-list UI (surfacing the policy model), Launch button wired to
Enginseer-client. This is when the deferred launch smoke test (create a
profile → launch) becomes runnable + when the app becomes user-usable.

Builds on merged #16 (scaffold) + #17#21 (Phase 1).
ModifAmorphic added a commit that referenced this pull request Jul 8, 2026
🤖 I have created a release *beep* *boop*
---


## 0.1.0 (2026-07-08)


### Features

* **component-a:** Hybrid Rust+C discovery + shell + launcher
([#1](#1))
([491e5d1](491e5d1))
* **magos-modificus:** implement Phase 1 Enginseer-client launch façade
([#20](#20))
([7950ed1](7950ed1))
* **magos-modificus:** implement Phase 1 Integrations (GitHub Releases
client)
([#19](#19))
([781d65c](781d65c))
* **magos-modificus:** implement Phase 1 Profiles library
([#17](#17))
([f355ceb](f355ceb))
* **magos-modificus:** implement Phase 1 Steam discovery library
([#18](#18))
([8f6ec00](8f6ec00))
* **magos-modificus:** implement Phase 2 shared-first mod storage
([#22](#22))
([cf2af80](cf2af80))
* **magos-modificus:** Phase 3 Track B mod-list, import, source model
([#29](#29))
([5075cee](5075cee))
* **magos-modificus:** Phase 3 Track C launch + Settings + escape-hatch
+ base-folder mod loading
([#32](#32))
([c595700](c595700))
* **magos-modificus:** Phase 4 Stage 1 nxm scheme handler + IPC
([#34](#34))
([0017529](0017529))
* **magos-modificus:** Phase 4 Stage 2 Nexus auth + Integrations dialog
([#35](#35))
([0790a8e](0790a8e))
* **magos-modificus:** Phase 4 Stage 3 Nexus mod acquisition
([#36](#36))
([d01105f](d01105f))
* **magos-modificus:** Phase 4 Stage 4 Nexus update-check service
([#39](#39))
([1749e76](1749e76))
* **magos-modificus:** Phase 4 Stage 5 mod-list update badges + per-mod
update
([#43](#43))
([423e146](423e146))
* **magos-modificus:** Phase 4 Stage 6 DMF new-profile/auth prompt
([#44](#44))
([994b4f8](994b4f8))
* **magos-modificus:** scaffold .NET 10 + Avalonia 12 app + libraries
([#16](#16))
([d1fac91](d1fac91))
* **mod-loader:** own the load-order contract (mods.lst), drop DMF
prepend
([#14](#14))
([1ccb891](1ccb891))
* **release:** add Curator release pipeline
([#49](#49))
([01517e4](01517e4))
* **runtime:** engine-context proven — trampoline, Enginseer v1,
launcher fail-fast
([#4](#4))
([4565ba8](4565ba8))
* **runtime:** Enginseer v2 — mod loader + launcher config + logging
([#5](#5))
([1e65b3f](1e65b3f))
* **runtime:** package Enginseer with the runtime; relocate build files
to runtime/
([#6](#6))
([7221bdb](7221bdb))
* **ui:** Phase 3 Track A — app shell + profile management
([#27](#27))
([f7f8250](f7f8250))
* **ui:** Phase 3 Track D — Preferences + i18n + custom title bars +
icon
([#28](#28))
([c921650](c921650))


### Bug Fixes

* **enginseer:** DMF integration fixes — IO re-root, load timing,
destroy
([#7](#7))
([401759c](401759c))
* **magos-modificus:** multi-format archive import (zip + 7z + rar)
([#41](#41))
([ee4f5c6](ee4f5c6))
* **magos-modificus:** search all Steam libraries for the compatdata
prefix
([#21](#21))
([895fa2b](895fa2b))
* **steam:** detect running Darktide via /proc argv[0] under Proton
([#23](#23))
([c5f38c4](c5f38c4))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ModifAmorphic <86930443+ModifAmorphic@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant