I tried your code to do a side-by-side but:
Debugger entered--Lisp error: (wrong-number-of-arguments read-directory-name 6)
(read-directory-name prompt nil nil nil nil (let ((ps (mapcar #'(lambda (p) (expand-file-name ...)) project--list))) #'(lambda (dir) (catch 'ball (let ((tail ps)) (while tail (let ... ... ...)))))))
In any case, my approach produces all legitimate possibilities a user could want to clear projects under any common prefix, including "/" (which means forget everything), common prefixes for remote directories, common roots for multiple projects at the same level and common roots up the tree for projects that are in a deeper hierarchy. This is effectively what I think people would want, and I assume you agree. If your code produces the same and is more efficient, that's great.
-Stephane