# Code Style

This document describes the conventions used in `@ironarachne/made-up-names`.
Most formatting is enforced automatically by [Biome](https://biomejs.dev/); the
rest is convention that reviewers will look for.

Run the automated checks before opening a pull request:

```bash
npm run check     # lint + typecheck + build + test
npm run lint:fix  # apply formatting and safe lint fixes
```

## Tooling

Biome is the single source of truth for formatting and linting. There is no
second formatter — do not add Prettier, dprint, or EditorConfig rules that
compete with `biome.json`. If a rule needs to change, change it in `biome.json`
so every contributor picks it up.

TypeScript is compiled with `strict: true` and `module: "NodeNext"`. Both
settings are deliberate; code that needs them relaxed should be reworked
instead. `tsconfig.json` builds `src/` alone; `tsconfig.tools.json` typechecks
everything else without emitting.

## Formatting

- Two-space indentation, spaces only.
- Double quotes for strings.
- Semicolons are required.
- Trailing commas in multi-line literals, parameter lists, and argument lists.
- Imports are organized by Biome's `organizeImports` assist. Do not hand-sort
  them.

## Modules and imports

- Every source file is an ES module. There is no CommonJS in this package.
- Relative imports **must** carry a `.js` extension, even though the source is
  TypeScript. `module: "NodeNext"` requires the emitted specifier:

  ```typescript
  import { scoreNameVariety } from "../tools/name-variety-score.js";
  ```

- JSON imports carry an import attribute:

  ```typescript
  import fantasy from "./cultures/fantasy.json" with { type: "json" };
  ```

- Import types with `import type`, and re-export them with `export type`, so
  type-only imports are erased from the emitted JavaScript.
- `src/index.ts` is the only public entry point. Anything outside `src/` is
  development tooling and may change without a major version bump.

## File layout

| Path            | Responsibility                                              |
| --------------- | ----------------------------------------------------------- |
| `src/index.ts`  | The public surface: types, `BaseNameGenerator`, and the `get*NameGenerator` factories. |
| `src/cultures/` | One JSON file per culture. Data only, never logic.           |
| `src/research/` | Corpus JSON plus a `.md` documenting each corpus's sources.  |
| `tools/`        | Scoring and reporting logic, importable and testable.        |
| `scripts/`      | Thin command-line wrappers over `tools/`. Argument parsing and printing only. |
| `tests/`        | Vitest suite.                                                |

Scoring logic belongs in `tools/`, not in `scripts/`. A script should parse
arguments, call a tool, and print — so the behavior stays testable.

## Naming

- `camelCase` for functions, methods, variables, and parameters.
- `PascalCase` for classes, interfaces, and type aliases.
- `SCREAMING_SNAKE_CASE` for module-level constants.
- Generator factories read `get<Thing>NameGenerator`, and the `name` they set
  on the generator is `snake_case`: `getStarNationNameGenerator` produces a
  generator named `star_nation`.
- Culture JSON files are `snake_case` (`forest_dweller.json`); their research
  corpora are `kebab-case` (`forest-dweller-corpus.json`). The slug is the
  culture name lowercased with spaces turned into hyphens.

## Types

- Annotate the return type of every exported function and public method, even
  when inference would get it right. It is part of the published contract.
- Interface members that are not meant to be mutated are marked `readonly`.
- `any` is not used in this codebase. Reach for a generic or a union first.

## Documentation comments

Every exported symbol — class, method, function, type, and type member —
carries a TSDoc block. These are the source for the published API docs
generated by TypeDoc, so they are not optional.

```typescript
/**
 * Returns a name generator for planets.
 *
 * @param rng - The random number generator to use.
 * @returns A name generator.
 */
export function getPlanetNameGenerator(rng: RNG = new RNG(Date.now())): NameGenerator {
```

- Document every parameter with `@param` and every non-void return with
  `@returns`.
- Document thrown errors with `@throws`, describing each condition that throws.
- Write in the third person, present tense: "Returns a name generator", not
  "Return" or "This will return".
- Inline `//` comments explain *why*, not *what*.

## Culture data

Culture JSON files are data, not code. Keep them that way.

- Every culture defines all six categories: `culture`, `country`, `family`,
  `female`, `male`, and `town`. A category is either a bare array of patterns
  or a `PatternSet` with `patterns` and optional `combinations`.
- New cultures need a matching corpus in `src/research/`: a
  `<slug>-corpus.json` and a `<slug>-corpus.md` explaining where the corpus
  came from and what it is meant to evoke. Patterns invented without a corpus
  cannot be scored.
- Before and after editing patterns, run:

  ```bash
  npm run report:culture-quality -- <culture>
  ```

  Aim for balance between variety and structure. A `structure_heavy` label
  means the names hew closely to the corpus at the cost of feeling samey;
  `variety_heavy` means the opposite. Put both runs in the pull request.

## Errors

Validate arguments up front, before doing any work, and throw a plain `Error`.
Messages describe what was not understood and quote the offending value:

```typescript
throw new Error(`Unknown culture name pattern set: ${name}`);
```

Do not silently clamp, coerce, or return a fallback value for invalid input.

## Determinism

This is the constraint that matters most in this package.

- All randomness flows through the `RNG` passed into a generator. Never call
  `Math.random()` in `src/`.
- Every generator factory takes an optional `rng` argument defaulting to
  `new RNG(Date.now())`. Keep that argument — it is the only way callers get
  reproducible output.
- A given seed must always produce the same names. Editing a culture's
  patterns, or changing how many times a generator draws from the RNG, changes
  every downstream result and is a **breaking change** requiring a major
  version bump. Adding a new culture or generator is not.

## Tests

Tests live in `tests/` and run under [Vitest](https://vitest.dev/).

- Seed the RNG explicitly so assertions are deterministic. Do not assert on the
  exact output of a generator constructed without a seed.
- Cover the boundaries: zero and one name requested, unknown culture names,
  empty pattern sets, and every documented `@throws` condition.
- Scoring functions in `tools/` are pure and should be tested directly against
  fixed inputs rather than through a script.

## Dependencies

This package's only runtime dependencies are `@ironarachne/rng` and
`@ironarachne/word-generator`, and that is meant to stay true. New runtime
dependencies need a strong justification in the pull request. Dev dependencies
should earn their place too — a tool that overlaps with one already here should
replace it rather than sit alongside it.
