Code Smells
Heuristics that point at a possible design problem, each with the case where it is genuinely fine — because a smell is a question, not a verdict.
A named pattern that raises a question about the design. Roughly half the time the honest answer is "this is fine", and a smell that cannot say when is just taste with a job title.
One type with a huge API, a dozen dependencies and a dozen unrelated reasons to change. The finding is the reason count, not the line count — and the fix is rarely a six-way split.
One requirement, seven modules, none of which is about that requirement. The code is not badly written — the knowledge has no owner, so every consumer had to learn it.
One module, many unrelated reasons to change. The exact dual of shotgun surgery: there the knowledge had no home, here one home holds knowledge that does not belong together.
A function that reaches into another module's data far more than its own. Sometimes the behaviour is in the wrong place; sometimes the other module is a value type and this is exactly right.
Money as `number`, email as `string`, a user id as `int`. Meaningful types move a class of mistake from runtime to compile time — and not every string needs a wrapper.
Eight arguments, four of them booleans. Bundling them into a parameter object makes the call site tidier and changes nothing — the finding is usually a concept that has no name.
`utils.ts` is not a module, it is the absence of one. Its contents are the pieces of the domain nobody could find a home for, and it grows because it never says no.
Two identical blocks may not be the same concept, and two blocks that look nothing alike may encode the same rule. Textual similarity is the wrong test, and it is the one everybody uses.