Skip to content

refactor: dry-kiss-solid-review-fixes - #14

Merged
jopmiddelkamp merged 21 commits into
developfrom
refactor/dry-kiss-solid-review-fixes
Jul 29, 2026
Merged

refactor: dry-kiss-solid-review-fixes#14
jopmiddelkamp merged 21 commits into
developfrom
refactor/dry-kiss-solid-review-fixes

Conversation

@jopmiddelkamp

@jopmiddelkamp jopmiddelkamp commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Resolves all findings from the DRY/KISS/SOLID review of bflow (baseline 0e29a7b). Every finding was independently re-verified against the code before fixing; all of them held up, so nothing was skipped. One logical fix per commit (20 commits), cargo build + cargo test green after each. Final state: 237 tests, 0 warnings (baseline 222 tests, 11 warnings).

Fixed

High

  • H1 — branch names now come from SemVer::release_branch()/hotfix_branch() everywhere (finish_hotfix, finish_work, FinishState::source_branch); no hand-concatenation left.
  • H2 — work-branch taxonomy has one source of truth: a WORK_TYPES table in git/branch.rs consumed by parse, with a compile-checked inverse work_kind() (no wildcard — a new variant fails compilation until decided). commit_type derives from it; detect_parent_branch classifies via BranchType instead of a local string array (the silent-failure site). decisions.md gained a "new work-branch type" recipe.
  • H3 — new Prompter port (one select method); flows no longer call menu::show_select. MenuPrompter is the real adapter, MockPrompter is scripted in tests. The previously-unreachable parent-branch candidate logic is now tested: distance-ascending order, develop-then-alphabetical tie-break, child-branch exclusion. The worktree wizard still uses menu directly (it is interface layer).
  • H4GitCli::list_branches_matching sorts + dedups instead of draining a HashSet, honoring the deterministic-order contract the mock already implied (three .first() consumers were nondeterministic with 2+ open release/hotfix branches).

Medium

  • M1 — dead Git::stash_push/stash_pop removed from trait, GitCli, and MockGit (stash policy is stash-by-message only); rev_parse demoted to a private GitCli method. stash_test.rsmock_contract_test.rs for the surviving test.
  • M2 — idempotent finish-step helpers (merge_into, push_if_needed, tag_if_missing, push_tag_if_missing, delete_source_branch) extracted into flows/mod.rs; commit messages and conflict guidance stay caller-supplied. Recorded mock call sequences verified byte-identical — the exact-sequence tests passed unmodified. The two flows were deliberately not unified (RC gate / propagation / messaging genuinely differ), and the release→main merge stays inline so the RC gate keeps its resume-skip semantics.
  • M3 — dead !starts_with("release-fix/") clause (never true after starts_with("release/")) removed via one branches_with_prefix helper; finish_hotfix keeps its documented sort+dedup at the call site.
  • M4latest_rc helper in finish_release.rs; each caller keeps its distinct error message.
  • M5DevelopOption/WorkBranchOption (taxonomy restated twice + unreachable!() arms) deleted; menus build from work_kinds(). ReleaseOption and the typed clap StartKind variants stay.
  • M6Action + validate_branch_name moved to src/action.rs; cli.rs no longer imports the terminal-UI module. Mechanical.
  • M7 — PR templates are resolved at the composition root, anchored to git.repo_root(), and passed into the flows as a parameter — flows no longer probe the CWD filesystem, and resolution works from subdirectories.

Low

  • L1Git::pullGit::ff_merge (it always ran git merge --ff-only). Deliberate test-expectation diff: recorded strings pull:ff_merge:; git commands unchanged.
  • L2 — RAII TerminalGuard replaces 7 hand-written cursor::Show + disable_raw_mode cleanups; the "no raw-mode leak" invariant is now structural.
  • L3 — GitHub adapter only treats gh's "no pull requests found" as "no existing PR"; auth/network failures now propagate (matching the Azure DevOps adapter's semantics) with the next command to run.
  • L4 — one effective_no_checkout derivation in the flows (the in-flow rule is test-pinned); run_flow's shadowing param renamed to skip_current_branch_sync.
  • L5 — one editor_disabled() rule in worktree.rs.
  • L6 — shared resolve_body_file precedence in hosting/mod.rs; per-provider path lists stay local (documented as intentional).
  • L7write_state_for_action derives its identity from finish_identity instead of re-encoding the mapping.
  • L8 — shared tmp_dir test helper (src/test_support.rs, #[cfg(test)]); the integration-test crate keeps its own copy in tests/common (separate compilation unit).

Skipped

None — every finding was confirmed on inspection.

Documented decisions changed (decisions.md)

  • DC1 (explicit amendment): "Orchestration (ordering, stash, state lifecycle) stays in main.rs, not the library" is amended. The lifecycle now lives in lifecycle::run (library, all-&dyn ports) so the reject → stash → write-state → dispatch contract and the three-way stash-pop policy are mock-tested; main.rs keeps adapter construction, preflight, and the bflow worktree dispatch. New tests/lifecycle_test.rs pins: crash mid-flow leaves a resumable state file, success clears it, dirty finish rejected before any side effect, dirty start stashes before the first mutation and pops on success, failed resume keeps state, abort discards state without touching the repo even mid-merge (+ the two resolve_action_with_state regression tests moved from main.rs). Rationale recorded in the decisions.md entry itself: the old boundary made the state-before-mutation invariant untestable, and a resume/--base bug had already slipped through this layer.
  • Smaller decisions.md updates: work-branch-type extension recipe (H2), Prompter plug-in row (H3), mock_contract_test.rs reference (M1).

Not changed

  • README.md and the bflow skill: no user-visible CLI behavior changed (commands, flags, branch model, tag strategy identical). Only narration tweak: release tagging prints Tagging: vX.Y.0 instead of Tagging main: vX.Y.0.
  • Dependency budget untouched (clap + crossterm, zero dev-deps); error model stays Result<T, String> and every new error names the next command.

🤖 Generated with Claude Code

jopmiddelkamp and others added 20 commits July 29, 2026 11:20
The '!starts_with("release-fix/")' guard was dead in all four copies:
a branch starting with 'release-fix/' can never start with 'release/'
('release-' != 'release/'), and the same holds for the hotfix analog.
Replace the four enumeration sites with one branches_with_prefix helper
in flows/mod.rs and drop the dead clause. finish_hotfix keeps its
sort+dedup at the call site (documented as deliberate in decisions.md).
Recorded mock call sequences unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The HashSet dedup returned branches in arbitrary order while MockGit
returns its Vec verbatim — the mock guaranteed an ordering the real
adapter didn't (LSP). Three .first() consumers in flows/start.rs were
nondeterministic with 2+ open release/hotfix branches, a state
finish_hotfix explicitly supports. Sort + dedup before returning, per
decisions.md's own 'sorted, deduped for deterministic replay' rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
decisions.md's own rule: branch/tag names are always generated from
SemVer methods, never string-concatenated. finish_hotfix built the
branch name by hand one line before constructing the SemVer;
finish_release_fix/finish_hotfix_fix and FinishState::source_branch
re-implemented the same formats. All four now delegate to
release_branch()/hotfix_branch(). Output strings are identical, so
recorded mock call sequences are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'latest RC for this release line' rule (parse tags, filter to
vX.Y.0-rc.N, take max) was computed twice, in bump_version and
finish_release. One helper now owns the rule; each caller keeps its
own error message (per-command UX, deliberately different). Recorded
mock call sequences unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Git::stash_push / Git::stash_pop had zero production callers — the
documented stash policy is 'stash by message, never blind pop', served
by stash_push_with_message / find_stash_by_message / stash_pop_ref.
Only tests/stash_test.rs pinned the mock's recording of them (ISP:
every implementor carried methods no client uses). Removed from the
trait, GitCli, and MockGit; the two pinning tests are deleted and the
file is renamed to mock_contract_test.rs for its surviving test
(decisions.md reference updated). rev_parse is only used internally by
GitCli::is_pushed, so it is now a private inherent method.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'editor value blank or none means skip opening' rule was written
out twice (open_worktree and show_status). One helper owns it now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'bflow template > first existing native template > empty body'
chain was duplicated in github.rs and devops.rs. resolve_body_file now
owns the precedence; the native path lists stay local to each provider
(documented as intentionally not shared). Two inline tests pin the
precedence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Any pr-view failure — including auth expiry or network errors — was
treated as 'no existing PR', while the Azure DevOps adapter hard-errors
in the same situation (divergent failure semantics behind one trait).
Only gh's 'no pull requests found' is now treated as 'no PR'; every
other failure propagates with the next command to run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The method has always run 'git merge --ff-only', never 'git pull' (no
network I/O — bflow fetches once up front). The name misled readers of
both the trait and the recorded test scripts. Deliberate test-expectation
diff: recorded call strings change from 'pull:' to 'ff_merge:'; the
underlying git command sequence is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(L2)

show_select and read_raw_line repeated the cursor::Show +
disable_raw_mode cleanup at seven exit points; missing one on a future
edit would leak raw mode. A small Drop guard now enforces the documented
'no raw-mode leak' invariant structurally — every return path, including
Ctrl-C/Esc aborts and mid-render errors, restores the terminal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The guarded merge-into-target, push-if-needed, tag-if-missing,
push-tag-if-missing, and source-branch-cleanup blocks were near-verbatim
duplicates across finish_release and finish_hotfix (the cleanup block
was identical including its comment). Small named helpers now own the
guard-skip-act shape; commit messages and conflict guidance stay
caller-supplied. The two flows are deliberately NOT unified into a
template — RC gate, propagation, and messaging genuinely differ — and
the release→main merge stays inline so the RC gate keeps running inside
the not-yet-merged branch.

Recorded mock call sequences verified unchanged: all exact-sequence
tests pass unmodified. Only narration changed: release tagging now
prints 'Tagging: vX.Y.0' instead of 'Tagging main: vX.Y.0'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The feature/fix/chore/docs/refactor taxonomy was restated in ~10 places;
the dangerous copy was finish_work's parent-branch detection, where a
string array could silently drift from BranchType (wrong PR base, no
compile error). Now:

- git/branch.rs owns a WORK_TYPES table (kind → constructor) consumed by
  parse(); work_kind() is its compile-checked inverse (no wildcard, so a
  new BranchType variant fails compilation until its kind is decided);
  commit_type() derives from the kind ('feat' special case only);
  is_work_branch() derives from work_kind().
- detect_parent_branch classifies candidates via BranchType::parse
  instead of a local prefix list.
- menu.rs builds its develop/work-branch menus from work_kinds();
  DevelopOption and WorkBranchOption (each restating the taxonomy and
  carrying unreachable!() branch_prefix arms) are deleted. ReleaseOption
  stays — its variants map 1:1 to distinct actions. Clap's StartKind
  variants stay typed on purpose (compile-enforced sites).
- decisions.md gains a 'new work-branch type' extension recipe.

Menu labels, actions, and recorded call sequences are unchanged; two new
unit tests pin the table/inverse round-trip and the commit-type rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Action enum is the single currency both interfaces resolve into,
but it lived inside menu.rs — forcing cli.rs (and main) to import the
terminal-UI module. It now has its own module alongside the shared
validate_branch_name rule. Purely mechanical: no behavior change, only
import paths (including in tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
write_state_for_action re-encoded the branch->finish-identity mapping
that finish_identity already owns. It now derives the identity from
that one mapping and only decides whether the action warrants writing
state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rule was written out independently in all three start flows; they
now share one effective_no_checkout helper (the in-flow derivation
itself is test-pinned: worktree mode must force the no-checkout path
even when no_checkout=false is passed). run_flow's parameter — which
shadowed the per-action no_checkout fields while actually gating the
pre-dispatch branch sync — is renamed to skip_current_branch_sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
template::resolve probed CWD-relative paths from inside finish_work —
tests implicitly depended on the process working directory, and running
bflow from a subdirectory silently skipped templates. Resolution now
happens in main.rs (composition root), anchored to git.repo_root(), and
flows receive the resolved path as a parameter (DIP: flows no longer
touch the filesystem). Deliberate signature change: the finish_work
flow fns gain a template parameter; existing tests pass None, and two
new tests pin repo-root anchoring and verbatim pass-through to the
hosting platform. Recorded git call sequences unchanged (repo_root is
called in main, outside the tested flows).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
start.rs and finish_work.rs called menu::show_select directly, coupling
flows to the terminal UI (DIP/SRP; architecture rules 1, 8, 9) and
making every prompting code path unreachable in tests. A minimal
Prompter port (one select method) is now passed alongside git/hosting;
menu::MenuPrompter is the real adapter, wired in main.rs, and a
scripted MockPrompter joins the test mocks. finish_release_fix and
finish_hotfix_fix never prompt, so they deliberately do not take one.

The parent-branch candidate logic — previously untestable because
reaching it raw-moded the terminal — is now covered: distance-ascending
menu order, develop-then-alphabetical tie-break, child-branch
exclusion with single-candidate auto-select. MockGit gained keyed
merge_base/rev_list_count knobs to express per-candidate topology.
The worktree wizard keeps calling menu directly — it IS interface layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The unique-temp-dir mechanics (pid + atomic counter) were duplicated in
state.rs and hosting/template.rs test modules. A #[cfg(test)]
test_support module owns the mechanics; each test module keeps a
one-line prefix wrapper. Test infrastructure is the one thing DAMP says
to DRY.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
decisions.md placed the stash → state-write → dispatch ordering and the
three-way stash-pop policy in main.rs, which tests/ cannot link — the
system's most safety-critical invariant ('state saved before the first
mutating git call') was enforced by a comment, and a resume/--base bug
already slipped through this layer. The lifecycle body of run() now
lives in lifecycle::run, taking &dyn Git / &dyn HostingPlatform /
&dyn Prompter / &dyn Editor; main.rs keeps adapter construction,
preflight, and the bflow worktree dispatch (which must stay ahead of
auth machinery).

This deliberately amends the documented decision 'orchestration stays
in main.rs, not the library' — the decisions.md entry is updated with
the new boundary and rationale.

New tests (tests/lifecycle_test.rs) pin the previously-untestable
contract: a crash mid-flow leaves a resumable state file; success
clears it; a dirty finish is rejected before any stash/state/mutation;
a dirty start stashes before the first mutation and pops on success; a
failed resume keeps state; abort discards state without touching the
repo even mid-merge; plus the two resolve_action_with_state regression
tests moved out of main.rs. MockGit gained a working_tree_clean knob.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each integration-test crate compiles its own copy of tests/common and
rarely uses every mock, so cargo test emitted 11 (now 17, with the new
MockPrompter/knobs) dead-code warnings that mean nothing. Module-wide
allow(dead_code) — the standard treatment for shared test-helper
modules — takes the suite to zero warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR applies a broad DRY/KISS/SOLID refactor to the bflow Rust codebase, primarily by extracting cross-cutting orchestration into a testable lifecycle module, introducing a Prompter port to decouple flows from the TTY menu implementation, and consolidating gitflow branch taxonomy/behavior behind shared helpers.

Changes:

  • Introduces lifecycle::run (library-side orchestration) plus new integration tests that pin ordering/state/stash behavior.
  • Adds a Prompter trait with MenuPrompter + MockPrompter, and updates flows/tests to use the port instead of calling menu::show_select directly.
  • Refactors gitflow branch handling (work-kind taxonomy table, SemVer-derived branch names) and improves deterministic behavior (sorted/deduped branch listing).

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/stash_test.rs Removes stash push/pop recording tests after trait cleanup.
tests/start_test.rs Updates start-flow tests to pass a MockPrompter into start_release.
tests/mock_contract_test.rs Adds a focused test to pin the mock call-recording contract.
tests/lifecycle_test.rs Adds integration tests that pin lifecycle ordering, crash-safety state, and stash policy.
tests/finish_work_test.rs Updates finish-work tests for Prompter + template plumbing; adds parent-candidate ordering tests.
tests/finish_release_test.rs Updates expected call strings from pull to ff_merge.
tests/finish_hotfix_test.rs Updates expected call strings from pull to ff_merge.
tests/common/mod.rs Extends mocks (Prompter, per-merge-base counts, working-tree cleanliness) and removes unused Git methods.
tests/cli_test.rs Updates Action import to new src/action.rs module.
tests/action_test.rs Updates Action import to new src/action.rs module.
src/worktree.rs Centralizes “editor disabled” rule via editor_disabled().
src/test_support.rs Adds shared tmp_dir helper for unit tests (cfg(test)).
src/state.rs Uses SemVer helpers for source-branch naming; reuses shared tmp-dir helper in tests.
src/prompt.rs Adds Prompter trait port for interactive selection.
src/menu.rs Adds MenuPrompter adapter; introduces TerminalGuard RAII for raw-mode cleanup; simplifies menus via work_kinds().
src/main.rs Shrinks to composition root: constructs adapters and delegates to lifecycle::run.
src/lifecycle.rs New library module implementing orchestration (resume/action resolution, stash/state lifecycle, dispatch).
src/lib.rs Exposes new modules (action, lifecycle, prompt) and test_support for unit tests.
src/hosting/template.rs Anchors bflow template resolution to repo root; updates tests accordingly.
src/hosting/mod.rs Centralizes PR body-file precedence (resolve_body_file) and adds unit tests.
src/hosting/github.rs Tightens error handling: only swallows “no PR found”, propagates auth/network failures with guidance.
src/hosting/devops.rs Uses shared PR body precedence helper.
src/git/mod.rs Renames pullff_merge, removes stash push/pop from port, makes branch matching deterministic (sort+dedup), demotes rev_parse.
src/git/branch.rs Adds WORK_TYPES single source of truth + compile-checked inverse (work_kind); derives commit type and work-kind list.
src/flows/start.rs Adds effective_no_checkout, introduces Prompter for release-type prompting, refactors branch listing via helper.
src/flows/mod.rs Adds shared branch-filter helper and shared idempotent finish-step helpers (merge/push/tag/delete).
src/flows/finish_work.rs Routes prompting through Prompter, resolves template at composition boundary, adjusts parent-branch detection inputs.
src/flows/finish_release.rs Extracts latest_rc helper; adopts shared idempotent finish-step helpers; switches to ff_merge.
src/flows/finish_hotfix.rs Adopts shared idempotent finish-step helpers; uses SemVer-derived hotfix branch name; keeps deterministic release propagation.
src/cli.rs Moves Action + validate_branch_name import to src/action.rs.
src/action.rs New shared Action enum + shared validate_branch_name.
.claude/skills/architecture/decisions.md Updates architecture/testing decisions to reflect new ports and lifecycle refactor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/flows/finish_work.rs
Comment on lines +39 to 43
let parsed = BranchType::parse(branch);
if parsed != BranchType::Develop && !parsed.is_work_branch() {
continue;
}
let base = match git.merge_base(current, branch) {
Comment thread tests/finish_work_test.rs
Comment on lines +292 to +295
add_candidate(&mut git, "feature/parent", "develop", "base-d", 4, 0);
// feature/stacked has MORE commits since divergence than we do — it
// branched from us, so it must not be offered as a PR target.
add_candidate(&mut git, "feature/parent", "feature/stacked", "base-s", 1, 6);
@claude

claude Bot commented Jul 29, 2026

Copy link
Copy Markdown

Code review

Checked for bugs and CLAUDE.md compliance. 1 issue found.

CLAUDE.md — README.md Architecture section is stale

This PR adds three new public library modules — src/action.rs, src/lifecycle.rs, src/prompt.rs — all registered as pub mod in src/lib.rs, and explicitly moves dispatch responsibility out of main.rs into the new lifecycle.rs (per the new file's own doc comment: "Lives in the library — not main.rs... main.rs keeps only adapter construction and preflight").

However, README.md lines 567–591 was not updated:

  • Line 571 still describes main.rs as "Entry point, preflight checks, dispatch" — dispatch now lives in lifecycle.rs.
  • The src/ tree omits action.rs, lifecycle.rs, and prompt.rs entirely, despite otherwise enumerating every top-level src/ file (main.rs, lib.rs, state.rs, version.rs, menu.rs, editor.rs, worktree.rs).

The root CLAUDE.md Documentation Sync rule is unconditional:

With every change you make make sure to always update the README.md.

The PR body's justification ("no user-visible CLI behavior changed... README... no changes needed") covers the CLI-facing surface, but README.md has a dedicated Architecture section that documents internal module structure, and this PR's own architecture skill entry (.claude/skills/architecture/decisions.md) was correctly amended for exactly this same structural change — showing README.md's omission was a gap, not an intentional exemption.

Suggested fix: Update the ## Architecture tree in README.md to (a) add entries for action.rs, lifecycle.rs, and prompt.rs, and (b) change main.rs's description to reflect that it is now only a composition root (adapter construction + preflight; dispatch is in lifecycle.rs).

Address PR #14 review: the README Architecture tree omitted action.rs,
cli.rs, lifecycle.rs, prompt.rs, and test_support.rs, and still credited
main.rs with dispatch. The architecture skill's Layer Map and port list
had the same gap (Prompter, lifecycle.rs).

Also promote the sorted+deduped ordering guarantee of
list_branches_matching from an impl comment to the Git trait contract,
and explain why finish_hotfix still re-sorts locally (crash-safety
invariant enforced in the flow, pinned by a deliberately unsorted mock).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jopmiddelkamp

Copy link
Copy Markdown
Contributor Author

Addressed the review finding in ed43cff5be2c748edbe958d21bafb46be3778288.

1. README Architecture section

Updated the src/ tree: added action.rs, cli.rs, lifecycle.rs, prompt.rs, and test_support.rs, and changed the main.rs line to "Composition root: builds adapters, preflight, hands off to lifecycle". The closing sentence now names all four ports (Git, HostingPlatform, Editor, Prompter).

https://github.com/Beans-BV/beans-gitflow/blob/ed43cff5be2c748edbe958d21bafb46be3778288/README.md#L567-L599

2. Same staleness in the architecture skill

While fixing the README I found the same gap in .claude/skills/architecture/SKILL.md: principle 1 listed three ports instead of four, and the Layer Map still credited main.rs with the cross-cutting lifecycle. Both now match decisions.md.

3. Ordering contract made explicit

Related cleanup from the same review pass: the sorted+deduped guarantee of list_branches_matching was documented only on the GitCli impl. It is now part of the Git trait contract. The local re-sort in finish_hotfix.rs stays on purpose: deterministic replay on resume is a crash-safety invariant, so the flow enforces it instead of trusting the adapter, and the test pins this with a deliberately unsorted mock. The comment there now explains this so the re-sort does not read as dead code.

https://github.com/Beans-BV/beans-gitflow/blob/ed43cff5be2c748edbe958d21bafb46be3778288/src/flows/finish_hotfix.rs#L24-L28

Build and tests are green: 237 passed, 0 warnings.

@jopmiddelkamp
jopmiddelkamp merged commit f16100c into develop Jul 29, 2026
7 checks passed
@jopmiddelkamp
jopmiddelkamp deleted the refactor/dry-kiss-solid-review-fixes branch July 29, 2026 05:24
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.

2 participants