Why and how does MWPM work for non-CSS codes?
Matchability has nothing to do with CSS-ness. CSS-ness isn't necessary (e.g. you can decode the honeycomb code using matching) and it also isn't sufficient (e.g. color codes are CSS but aren't matchable). It's unrelated.
Anyways the whole notion of a code "being CSS" flies out the window once you start decoding circuit noise. Consider that the CSS and XZZX surface codes compile to the exact same circuit (if you target CZ gates and merge single qubit rotations). Is the resulting circuit both a CSS code and not a CSS code?
The only property required by matching is that all errors are graphlike. All errors must produce at most two detection events. What you might have used the CSS property for is to decompose Y errors, which produce more than 2 detection events, into separate X and Z errors. But that's not necessary; software can just do it automatically by looking at the structure of the errors.
the two graphs would be highly connected. Or is a single graph built from both X and Z stabilizers together?
The graphs don't end up highly connected if you do it right.
Look at the subset of errors that are graphlike. They form a graph. If you started from a surface code, inspecting the graph will reveal the two subgraphs. Finish the job by decomposing the non-graphlike errors into the available graphlike errors.
Note that the subgraphs are actually slightly connected. At the corners of the patch, all three errors produce graphlike symptoms. One of them links the two subgraphs. You can try to find those and cut them if you want. Either way it's a correct decoding graph.
Does PyMatching work for non-CSS codes?
Pymatching's mwpm implementation has no concept of "CSS". It doesn't care if your code is CSS or not; that's irrelevant.
Pymatching's mwpm implementing does have a concept of errors being graphlike or not. It will refuse if you ask it to decode undecomposed ungraphlike errors.
See the pymatching 2.0 paper: https://arxiv.org/abs/2303.15933
One way you can configure pymatching is by compiling your code into a noisy stim circuit and using
circuit.detector_error_model(decompose_errors=True) to get a dem and then turn that into a pymatching matching using
pymatching. Matching.from_detector_error_model. For example this will work fine on an XZZX surface code circuit.