I'm in a third category, where I have many small projects, and come back to each one infrequently, so that I'm more or less starting from scratch each time. Small codebases, rarely accessed, poorly understood.
(I noticed with dismay that with some AI assistance, I no longer understood many parts of my code. Then laughed darkly when I realized, yeah, I had that problem before AI too...)
I've been testing various techniques to remedy this. One of them was the Feynman Technique: explain (a narrow slice of) the codebase in my own words. The issue here is that it doesn't necessarily force contact with reality.
For example, one time I investigated my game's bullet netcode, and then explained it until I was satisfied I had understood it correctly. Except, my explanation turned out to be completely wrong, because it was based on an assumption I hadn't made explicit (and therefore hadn't verified).
So it's a good start -- and a great habit to get into, I think, explaining things in your own words lets you "inspect the objects of your own mind", to see if they are sound -- but a forcing function, it is not! It doesn't check your work.
I found one I like, though: modding. Making a change to a codebase. This forces your mental model into contact with reality. (It's also more fun, in my opinion. Most programming is archaeology, but archaeology in service of building something new is a lot more satisfying, at least to me!)