Introduction
The core idea
LLL is the concept. LLLTS is its TypeScript implementation.
LLLPY and LLLHX are on the way.
The core rules in LLLTS enforce one structural discipline:
Make the shape of the program obvious before anyone reads the implementation.
The guardrail profile focuses on dangerous expressions and hidden runtime behavior. The core rules sit one layer above that. They make files, declarations, and project structure predictable enough that a human reviewer or an LLM can orient quickly and reason locally.
They are also one branch of the broader concept simplification strategy. Wherever TypeScript offers several overlapping structural paths,
LLLTS tries to keep one canonical default so attention stays on the problem domain rather
than on syntax selection.
Size limits protect local reasoning
The compiler enforces structural limits: file length, method length, files per folder, and subfolders per folder. These are not style points. They are caps on complexity density.
| Limit | Maximum |
|---|---|
| Lines per file | 800 |
| Lines per method | 200 |
| Files per folder | 12 |
| Subfolders per folder | 8 |
Large contexts degrade both human review and model reliability. Hard limits force decomposition before a file or folder becomes too dense to understand coherently.
Names must line up across the project
The exported class or type must match the filename stem. That sounds simple because it should be.
If the file is called Invoice.lll.ts, the primary thing inside it is
Invoice.
// Invoice.lll.ts
export class BillingInvoice {
}// Invoice.lll.ts
export class Invoice {
}This gives the codebase a stable lookup rule. You do not have to remember naming folklore or guess whether a file contains a close synonym.
Filename-to-symbol consistency makes navigation more mechanical. The model can infer where a definition belongs and where a change should land without inventing alternate naming schemes.
Each file should have one job
In ordinary source files, LLLTS expects exactly one primary top-level entity. If the file
is named after a class, it defines that class. If it is named after a type, it defines that type. The
file stops being a junk drawer. Extra top-level classes, types, and interfaces are forbidden even when
they are not exported.
export class Invoice {
}
export class InvoiceFormatter {
}export class Invoice {
}Pure barrel files are the deliberate exception. A file that only re-exports other modules can stay structural and does not need its own primary entity.
The model can treat the file as one concept instead of several loosely related definitions. That reduces drift, makes retrieval cleaner, and lowers the chance of editing the wrong symbol. It is concept simplification in practice: one file, one primary concept, one obvious lookup path. It also prevents a common duplication failure: when a hidden non-exported helper lives beside the primary entity, an LLM scanning the project can miss it and create the same concept somewhere else. If every primary concept has its own exported, filename-matched file, project scans are much less likely to skip important definitions.
Top-level code should be almost empty
JavaScript encourages executable code at the top level. LLLTS does not. Ordinary files
cannot turn into mini scripts full of free functions, loose variables, or setup logic.
const cache = new Map<string, User>();
function loadUser(id: string): User {
return new User(id);
}export class UserRepository {
static readonly cache = new Map<string, User>();
@Spec("Loads a user by id.")
static loadUser(id: string): User {
return new User(id);
}
}
Allowed runtime top-level statements are intentionally narrow: one final if, or in a
primary file, one final new ClassName() that instantiates the exported class.
This is not only about tidiness. It deliberately removes several overlapping top-level ways to express runtime behavior and shared state. Instead of choosing between free functions, loose constants, helper namespaces, and ad hoc setup scripts, the language keeps one dominant place for runtime logic: the class body.
Limiting top-level execution reduces hidden initialization paths and scattered state by forcing them into explicit, controlled places. The model can reason through a class or type definition without also simulating an arbitrary script around it.
Every behavior needs a stated purpose
Every class and every non-constructor method must carry @Spec("..."). Constructors
cannot be decorated in TypeScript, so they use Spec("...") as the first statement when
needed.
export class InvoiceService {
create(invoice: DraftInvoice): Invoice {
return new Invoice(invoice);
}
}@Spec("Coordinates invoice creation.")
export class InvoiceService {
@Spec("Creates an invoice from a draft.")
create(invoice: DraftInvoice): Invoice {
return new Invoice(invoice);
}
}This turns intent into part of the source, not just a comment someone may or may not add later.
LLMs write better code when every behavior states its purpose before the body begins. The short spec acts like a small thinking step: the model has to name the intent in words, then generate code that matches that stated purpose. In practice, that improves local reasoning and reduces drift.
The broader pattern
Across all of these rules, the compiler keeps asking the same question: can someone identify the unit, purpose, contract, and size of this code without hunting?
That is why the core rules pair so well with the guardrail profile. The guardrail profile removes risky language shortcuts. The core rules remove structural ambiguity. Together, they make code easier to navigate, review, and generate without guesswork. The cross-cutting explanation is documented in Concept Simplification.