This is the expected behavior.  Tap needs to discover tasks 'somehow'.  Loading all the files under a lib directory arbitrarily is obviously not acceptable so solutions that use live code don't work.  You could make a separate manifest file but that's new overhead.  I decided to use a comment-based marker to indicate when a .rb file has a task to-be-discovered.
That marker is '::manifest' (it's a Lazydoc constant attribute).
In your example here, Goodnight could be used by tap but tap doesn't know it exists because there isn't a '::manifest' comment.  Yeah, it is overhead but I think it's the least amount of overhead possible and it allows you to define tasks that are by design not picked up by 'tap' (like base classes, for instance).
[lib/goodnight.rb]
# ::manifest <this is the summary string on the command line>
class Goodnight < Tap::Task
  config :message, 'goodnight'           # a goodnight message
  def process(name)
    puts "#{message} #{name}"
  end
end
That should solve it!