# `error-codes` _(no submodules)_ ## Purpose `@suaveplan/linter/error-codes` owns the stable error-code registry and the typed `LinterError` class that every other module in the package throws. The registry is expressed through `defineCodes("LINTER", [...])` from `@suaveplan/error` so each code carries the `LINTER.` namespace prefix and survives across module boundaries, worker threads, RPC envelopes, and serialised error transports without losing its identity. Consumers of the linter — pre-commit hooks, CI gates, IDE integrations — discriminate on `error.code` to make routing decisions (retry, suppress, surface to the user) without parsing free-form messages, so the registry is a public contract, not an implementation detail. ## Features - Closed code set declared once via `defineCodes` — typo-safe, no string literals at call sites. - `LinterError extends SuaveplanError` — interoperable with every observability adapter shipped in `@suaveplan/error` (OTel span events, redaction, i18n hooks). - Optional `details` payload preserved on the instance and on the underlying `SuaveplanError.context` field so structured diagnostics travel with the error. - Optional `cause` forwarded to the native `Error.cause` chain — preserves the underlying failure (e.g. an `EACCES` from `fs.writeFile`) for debugging. ## Usage: Basic ```ts import { LINTER_CODES, LinterError } from "@suaveplan/linter/error-codes"; throw new LinterError(LINTER_CODES.NO_TARGETS_FOUND, "no .md files matched", { details: { pattern: "docs/**/*.md" }, }); ``` ## Usage: Advanced Route errors by code in a CI gate: ```ts import { LinterError } from "@suaveplan/linter/error-codes"; import { runUnifiedLint } from "@suaveplan/linter/unified-lint"; try { const report = await runUnifiedLint({ fix: false }); if (!report.success) { process.exitCode = 1; } } catch (err) { if (err instanceof LinterError) { switch (err.code) { case "LINTER.BIOME_UNAVAILABLE": console.warn("biome not installed — skipping JS/TS lane"); break; case "LINTER.NO_TARGETS_FOUND": process.exitCode = 0; // empty repo, not a failure break; default: console.error(err.code, err.message, err.details); process.exitCode = 2; } } else { throw err; } } ``` ## API Reference ### `LINTER_CODES` A frozen object whose values are the registered `LINTER.` strings. Members: `INVALID_OPTIONS`, `FILE_NOT_FOUND`, `NO_TARGETS_FOUND`, `READ_FAILED`, `WRITE_FAILED`, `MARKDOWN_PARSE_FAILED`, `BIOME_UNAVAILABLE`, `BIOME_FAILED`, `RUNNER_FAILED`, `UNKNOWN_FLAG`. ### `type LinterCode` Union of every value in `LINTER_CODES`. ### `class LinterError extends SuaveplanError` Constructor: `new LinterError(code, message, options?)`. - `code: LinterCode` — one of the registered codes. - `message: string` — human-readable summary. - `options.cause?: unknown` — forwarded to `Error.cause`. - `options.details?: Record` — stored as both `instance.details` AND `instance.context` (via `SuaveplanError`). `instance.name` is set to `"LinterError"`. ## Implementation Notes - The registry is enumerated by tests (`Object.values(LINTER_CODES).sort()`) to guarantee the public set matches the spec delta exactly. Adding a code without updating the test fails the gate. - `details` defaults to `{}` rather than `undefined` so consumers can safely spread or read fields without a null-check; the spec scenario "defaults details to empty record when omitted" pins this contract. - `SuaveplanError` accepts `cause` only when defined — passing `undefined` would set `cause: undefined` on the chain, which differs from "no cause." The constructor guards this with the `options?.cause === undefined ? {} : { cause: options.cause }` ternary. - The class extends `SuaveplanError` rather than `SuaveplanError` so the `code` getter is narrowed to the linter's registered union at the type level — IDE autocompletion lists exactly the ten codes.