class-graduation
Flags a folder created for a class that bundles no private companions.
Agent checkAdvises by default
Why this rule
File vs Folder — The Graduation Rule
Classes follow the same graduation rule as everything else (see architecture-decisions.md):
- A class starts as a single file —
RateLimiter.tswith its test beside it; non-exported helpers may co-locate. - A class graduates to a folder —
HttpClient/— only when it needs private companions (bundled utils, types, or constants that serve only it). Companions live undercommon/by category (utils/,types/,constants/), and callers import the class from its own file —HttpClient/HttpClient.ts. - Do NOT create a folder for a class with no companions — that is ceremony, not structure.
Examples
The agent flags code like the incorrect example and accepts code like the correct one.
Incorrect
interface ConstructorParams {tokens: number;}// A folder and a barrel around a class that bundles nothing — ceremony, not// structure. `RateLimiter.ts` beside its test is the whole module.export class RateLimiter {private remaining: number;constructor({ tokens }: ConstructorParams) {this.remaining = tokens;}take(): boolean {const allowed = this.remaining > 0;if (allowed) {this.remaining -= 1;}return allowed;}}
Correct
interface ConstructorParams {tokens: number;}// No companions, so no folder: the file is the module and the compiler enforces// its boundary for free.export class RateLimiter {private remaining: number;constructor({ tokens }: ConstructorParams) {this.remaining = tokens;}take(): boolean {const allowed = this.remaining > 0;if (allowed) {this.remaining -= 1;}return allowed;}}
Configure
- Block
"blocking"Stops a run when a file the run changed breaks the rule. - AdviseDefault
"advisory"Reports it and hands it to the refactor agent. Never stops a run. - Off
"off"Not checked. Use it when your own linter already enforces the rule.
Add this to your lightsout.config.json, then change the value.
{"standards-checks": {"class-graduation": "advisory"}}