# codesmell — full specification > The complete, machine-readable contract for codesmell: rule catalog, request and response shapes, scoring, limits, and runnable examples. This is the long form of [llms.txt](https://codesmell.net/llms.txt). Everything here is enforced by the project's test suite: the rule catalog matches the engine's `RULES` constant, the thresholds match `DEFAULT_THRESHOLDS`, and the counts match the code. ## Guarantees - Deterministic: the same input always produces the same findings, stats and score. No clock, no randomness, no network call, no LLM in the detection path. - Pure: the engine performs no I/O and has zero runtime dependencies. - Portable: the same engine runs in the browser, in a serverless function, and in the test suite. - Heuristic: string literals are not modeled, so findings are guidance, not proof. ## Languages JavaScript, TypeScript, Python, Go, Rust, Java, C#, C, C++, Ruby, PHP — plus an `unknown` fallback. Detection order: explicit `language` → filename extension → content markers → `unknown`. ## Rule catalog | Rule id | Severity | Threshold | Default | | --- | --- | --- | --- | | `god-file` | error | `maxFileLines`, `maxFileFunctions` | 400, 20 | | `empty-catch` | error | — | — | | `long-function` | warning | `maxFunctionLines` | 50 | | `long-parameter-list` | warning | `maxParams` | 4 | | `deep-nesting` | warning | `maxNesting` | 4 | | `duplicated-code` | warning | `duplicateBlockLines` | 4 | | `nested-ternary` | warning | — | — | | `high-complexity` | warning | `maxComplexity` | 20 | | `long-line` | info | `maxLineLength` | 120 | | `magic-number` | info | — | — | | `console-statement` | info | — | — | | `todo-comment` | info | — | — | | `commented-out-code` | info | — | — | Rule notes: - At most 20 findings per rule per analysis, to keep responses bounded. - `god-file` fires when either threshold is exceeded. - `magic-number` ignores trivial values in any spelling (`0`, `0.0`, `1`, `1_000`, `0x0`), declaration lines, and named property values such as `maxFunctionLines: 50,`. - `nested-ternary` counts only real ternary operators — `??` (nullish coalescing) and `?.` (optional chaining) are not ternaries — and applies to ternary-capable languages only (JavaScript, TypeScript, Java, C#, C, C++, PHP). - `empty-catch` also recognises Python `except:` followed by `pass`. ## Engine API ```ts import { analyze } from "codesmell"; const result = analyze({ code, filename: "service.ts" }); ``` Request — `AnalysisRequest`: | Field | Type | Required | Meaning | | --- | --- | --- | --- | | `code` | `string` | yes | Source to scan; split on `\r?\n`. | | `filename` | `string` | no | Extension drives language detection. | | `language` | `Language` | no | Overrides filename/content detection. | | `thresholds` | `Partial` | no | Per-call threshold overrides. | | `profile` | `strict` \| `default` \| `lenient` | no | Named budget preset; explicit thresholds win over it. | Profiles: | Threshold | `strict` | `default` | `lenient` | | --- | --- | --- | --- | | `maxFunctionLines` | 30 | 50 | 100 | | `maxParams` | 3 | 4 | 6 | | `maxNesting` | 2 | 4 | 6 | | `maxLineLength` | 100 | 120 | 200 | | `duplicateBlockLines` | 3 | 4 | 8 | | `maxFileLines` | 200 | 400 | 800 | | `maxFileFunctions` | 10 | 20 | 40 | | `maxComplexity` | 10 | 20 | 40 | Response — `AnalysisResult`: | Field | Type | Meaning | | --- | --- | --- | | `language` | `Language` | Detected or forced language. | | `smells` | `Smell[]` | Findings with rule, severity, line, endLine?, message, snippet, symbol?. | | `stats` | `FileStats` | totalLines, codeLines, commentLines, blankLines, maxLineLength, functionCount, longestFunctionLines. | | `score` | `number` | Health score, 0–100 (100 = clean). | | `grade` | `string` | A+ / A / B / C / D / F. | ## Scoring ``` penalty = Σ (6 × errors + 3 × warnings + 1 × infos) score = clamp(round(100 − penalty × 100 / max(codeLines, 100)), 0, 100) ``` Grades: A+ ≥ 97, A ≥ 90, B ≥ 80, C ≥ 65, D ≥ 50, otherwise F. Empty input scores 100 / A+. ## REST API ### POST /api/analyze ```bash curl -s https://codesmell.net/api/analyze \ -H 'content-type: application/json' \ -d '{"code":"function f(a,b,c,d,e) { try { a(); } catch (e) {} }","filename":"f.ts"}' ``` Response `200`: `{ "language": "typescript", "smells": [], "stats": {}, "score": 100, "grade": "A+" }`. | Status | Body | When | | --- | --- | --- | | `200` | `AnalysisResult` | Success. | | `204` | — | CORS preflight (`OPTIONS`). | | `400` | `{ "error": string }` | Invalid JSON, missing `code`, unknown `language`, body over 512 KB. | | `405` | `{ "error": string }` | Method other than `POST` or `OPTIONS`. | | `429` | `{ "error": string }` | Rate limit exceeded. | CORS: `access-control-allow-origin: *`, methods `POST, OPTIONS`, headers `content-type, accept`, exposed headers `x-ratelimit-limit, x-ratelimit-remaining`. ### GET /api/health `200` → `{ "status": "ok", "service": "codesmell", "version": "0.1.0", "timestamp": string }`. Never rate-limited. ### Rate limiting Per-IP fixed window, 60 requests/minute by default (`RATE_LIMIT_PER_MINUTE`, `RATE_LIMIT_DISABLED=1` to bypass in local dev). Every response carries `x-ratelimit-limit` and `x-ratelimit-remaining`; a `429` also carries `retry-after` and `x-ratelimit-reset` in seconds. ## MCP server Endpoint: `https://codesmell.net/api/mcp` — Streamable HTTP, stateless (the initialize handshake succeeds without session headers). Clients should send `accept: application/json, text/event-stream`; the server may answer with raw JSON or SSE `data:` frames. ### Tool `detect` Input: `{ code: string, filename?: string, language?: Language, thresholds?: Partial }`. Result: a single text block holding JSON — `{ "summary": "3 smell(s), score 78/100 (B), language typescript", "language": "typescript", "smells": [], "stats": {}, "score": 78, "grade": "B" }`. ### Tool `rules` Input `{}`. Result: a single text block holding JSON — `{ "rules": [], "defaults": {} }`. Each rule carries a `remedy` object: `{ "refactoring": "Extract Function", "action": "Split the body into named helpers — one job per function.", "url": "https://refactoring.com/catalog/extractFunction.html" }`. The action is the one-line fix; the URL is Martin Fowler's catalog entry for the refactoring. ### Tool `examples` Input (all optional): `{ language?: string, id?: string, includeCode?: boolean }`. Result: a single text block holding JSON — `{ "count": 14, "samples": [{ "id", "title", "filename", "language", "blurb", "demonstrates", "code", "expected": { "score", "grade", "smells": [] } }] }`. The catalogue holds canonical snippets in 11 languages, each written around one failure mode. `expected` is computed by running the engine on the sample at call time, so it is never a stale fixture. Use it to show what a rule looks like in real code, or to self-check a client against known-good output. ### Errors - Unknown tool → JSON-RPC error object. - Exception inside a tool handler → MCP error result (`isError`). - Rate limit → HTTP 429 with a JSON-RPC error (`code: -32000`). ### Browser access `/api/mcp` answers `OPTIONS` with `204` and the CORS headers above, so a browser or an in-browser agent can call it from any origin. The preflight is answered before the rate limiter and costs no quota. ## Limits - Request body: 512 KB of source code. - Findings: 20 per rule. - Rate: 60 requests/minute per IP (analyze and MCP only). ## Worked example Request: ```bash curl -s https://codesmell.net/api/analyze \ -H 'content-type: application/json' \ -d '{"code":"function f(a,b,c,d,e) { try { a(); } catch (e) {} }","filename":"f.ts"}' ``` Response (abridged): ```json { "language": "typescript", "smells": [ { "rule": "empty-catch", "severity": "error", "line": 1, "message": "catch block swallows the error silently. Log or rethrow.", "snippet": "function f(a,b,c,d,e) { try { a(); } catch (e) {} }" }, { "rule": "long-parameter-list", "severity": "warning", "line": 1, "message": "Function \"f\" takes 5 parameters (budget 4). Introduce a parameter object.", "symbol": "f" } ], "stats": { "totalLines": 1, "codeLines": 1, "functionCount": 1 }, "score": 91, "grade": "A" } ``` The site's [examples gallery](https://codesmell.net/#examples) holds 15 snippets across 11 languages, each tied to the rules it demonstrates, with the findings computed live in the browser. ## Examples ```bash # Engine API, offline bun examples/analyze.ts # REST handler, driven in-process bun examples/rest-analyze.ts # The whole sample catalogue with its findings, offline bun examples/samples.ts # MCP client against a live server BASE_URL=https://codesmell.net bun examples/mcp-detect.ts ``` ## Links - [Homepage](https://codesmell.net/): the playground and the rule catalog. - [FAQ](https://codesmell.net/#faq): plain-language answers. - [Summary](https://codesmell.net/llms.txt): the short form of this document.