Foundation

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
Why this helps LLMs

Large contexts degrade both human review and model reliability. Hard limits force decomposition before a file or folder becomes too dense to understand coherently.

Inspired by Cleanroom-style structural discipline.

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.

Rejected ts
// Invoice.lll.ts
export class BillingInvoice {
}
Allowed ts
// 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.

Why this helps LLMs

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.

Inspired by Haxe.

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.

Rejected ts
export class Invoice {
}

export class InvoiceFormatter {
}
Allowed ts
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.

Why this helps LLMs

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.

Inspired by Java.

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.

Rejected ts
const cache = new Map<string, User>();

function loadUser(id: string): User {
  return new User(id);
}
Allowed ts
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.

Why this helps LLMs

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.

Inspired by Java.

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.

Rejected ts
export class InvoiceService {
  create(invoice: DraftInvoice): Invoice {
    return new Invoice(invoice);
  }
}
Allowed ts
@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.

Why this helps LLMs

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.