I'm unsure that substituting analyze with your own is desirable. Obviously you can do everything this way but the drawback is that you are on your own.

For analysis purposes it often seems easier to work from a fully expanded tree (what the AST provides) – driving macroexpansion yourself seems brittle. In my opinion, if you need to record extra information, the analyzer should call your code and not the other way round.

On the syntax front, metadata could be the answer: either on the ns or on individual forms (forms can be grouped with 'do).

In what I envision a namespace register (through a aptly named register-compiler-hook function) a compielr extension associated with a keyword and then any ns which requires this ns can use this extension simply by adding metadata on form or by adding metadata at the ns-form level (won't work as is). However it's just the surface.

The reading/macroexpansion/compilation/running model is slightly a lie in Clojure: macroexpansion and compilation are intertwined, at no time you get a fully macro-expanded form that gets passed to the compiler (I'd like that but that's not the CLJ or CLJS compiler design).

The true model is reading/analyze/compilation/running and macroexpansion is part of analyze.

Strictlty speaking post-analyze hooks/AST transformers are as powerful as macros (you could do the same thing with macros but it would more brittle: you have to replicate all the macroexpansion/code-walking of the compiler) – it's just a saner way to implement such macros.

If we have an analyze function and a reform function (which outputs forms from an AST) then

would be functionally equivalent to a post-analyzer hook.

Metadata tricks or ns declarations are just sugar on top of this functionality. This functionality being in the same class as macros there is no reason not to offer them at the form level.

For example I envision post-analyzers to be used to perform specific aggressive optimizations and I definitely don't want to apply them on a whole namespace or to reorganize all my namespaces to isolate the functions to optimize.

To me the ns-level is too coarse but I'm more than willing to make sure the resulting model is understandable and its behavior well scoped.

For example to "transientize" an expression: most expressions can't be converted and, really, it's not the kind of optimization I want to unleash on a whole ns. (Transients are going to be phased out but a t least transientization is an actual aggressive (possibly breaking) optimization.)