This is not done over regular lisp lists, but over scheme syntax objects that retain the original source info.
Those syntax objects are also the basis of the hygienic macro systems in many schemes (at least ones using syntax-case) so that macros also benefit from that information.
The lisp source representation is already an AST, so these kinds of transformations are trivial.
Just having support for a textual representation of the intermediate languages that is easily human readable would probably be good enough. It would let you inspect the intermediate steps. The source info that's tracked could be omitted in the printed representation, even though it still exists in the in-memory representation.
This is why optimisations aren't usually source-to-source - they need to include in the output extra information that isn't normally representable in source code. Another reason is that compiling to a lower-level representation gives you more power - when I partially evaluate I gain extra information such as that an add operator will not overflow, and I want to include that valuable information in my output even if there is no no-overflow add operator in the source language.