Avoid case classes, use sbt-datatype

Prefer method overloading over default parameter values

Default parameter values are effectively code generation,
which makes them difficult to maintain.

Other public API matters

Here are other guidelines about the sbt public API.

Avoid Stringly-typed programming

Define datatypes.

Avoid overuse of def apply

def apply should be reserved for factory methods
in a companion object that returns type T.

Use specific datatypes (Vector, List, or Array), rather than Seq

scala.Seq is scala.collection.Seq, which is not immutable.
Default to Vector. Use List if constant prepending is needed.
Use Array if Java interoperability is needed.
Note that using mutable collections is perfectly fine within the implementation.

Avoid calling toSeq or anything with side-effects on Set

Set is fine if you stick to set operations, like contains and subsetOf.
More often than not, toSeq is called explicitly or implicitly,
or some side-effecting method is called from map.
This introduces non-determinism to the code.

Avoid calling toSeq on Map

Same as above. This will introduce non-determinism.

Avoid functions and tuples in the signature, if Java interoperability is needed

Instead of functions and tuples, turn them into a trait.
This applies where interoperability is a concern, like implementing
incremental compilation.

Implicit conversions for syntax (enrich-my-library pattern) should be imported

Suppose the IO module introduces a URL enrichment called RichURI,
and LibraryManagement introduces a String enrichment called GroupID (for ModuleID syntax).
These implicit conversions should be defined in an object named syntax in the respective package: