You could use code to read the LISP file and report the user defined subfunctions found - similar to how code formatting programs work.
I think this is the safest option, because running the program would have to be sure that all sub-functions are called. I'm thinking I'd have to run each option with several very different examples and yet would never be sure that all subfunctions have been called. The problem is to make a syntax for analyzer to open each LSP file, read each line, parse the word that comes after each parenthesis, see if it is a vocabulary ALISP / VLISP word .... It would also have to skip the lines of comments, literal strings, etc, etc, etc, ufffff .... It's a good idea, but I see it difficult to put into practice.
I do something similar, using unique prefixes for each program:
(setq f "nw_")
(foreach x (atoms-family 1)
(if (= (substr x 1 (strlen f)) (strcase f))
(eval (list 'trace (read x)))
(eval (list 'untrace (read x)))))
Using "f" like a list of prefixes and / or suffixes names of mys defuns ... and instead (substr x 1 (strlen f)) (strcase f)) for a search & comparation string function, type (wcmatch string pattern) is probably the most practical method ...Almost all of my functions are in the name one o two groups of 5 letters characteristic. I can have a little luck.
Thank you all for so many good ideas. Happy New Year to all from Madrid.