I struggled with the same problem. My solution was to wrap my “entry point” to the program in a setup-call-cleanup that throws if there is a choice point after the first success, and test very often during development so that I immediately notice any problems.
The predicate itself:
goal_is_det(Goal) :-
setup_call_cleanup(true, Goal, Det = true),
( Det == true
-> true
; !,
throw(error(mode_error(notdet),_))
).
It seems to provide similar functionality as library(rdet). (But not sure about that).
And of course it only helps if you run your “integration tests” or “end-to-end tests” or whatever you prefer to call them often enough so you notice the problem when you introduce it.