One of our packages generates itself from a table of named exceptions and its own source code
https://livesql.oracle.com/apex/livesql/file/content_CCDPAOR8IXKYVUT9YHMCZ7W3U.html This way it is easy to have a central place to define named exceptions.
And each object has in its header comment the version number in a form like CM#102. So if you want to check the object versions you can simply ask the database
SELECT s1.name "Name"
,s1.type "Typ"
,SUBSTR(REGEXP_SUBSTR(s1.text,'CM#[[:digit:]]*'),4) "Version"
FROM user_source s1
WHERE s1.line = ( SELECT MAX(s2.line)
FROM user_source s2
WHERE INSTR(s2.text,'CM#') > 0
AND s2.name = s1.name
AND s2.type = s1.type
)
ORDER BY s1.name
,s1.type
Name Typ Version
-----------------------------------
ScanFolder JAVA SOURCE 1
SEPIS$SENDMAIL PACKAGE 6
SEPIS$SENDMAIL PACKAGE BODY 9
...
Or if you want to search in your code for occurrences of <searchstring>
Or if you lost the source code of a procedure you can simply spool it into a file
Or ...