---
summary: format-parity lint rule enforces format/structuredContent coverage at startup — sentinel injection, 16 new tests, example tools updated
breaking: false
---

# 0.5.2 — 2026-04-20

Closes a recurring class of silent bugs by enforcing format/structuredContent parity at lint time. Every terminal field in a tool's `output` schema must now be rendered by `format()` — since most LLM clients (Claude Code, VS Code Copilot, Cursor, Windsurf) only forward `content[]` to the model, a field that exists in `structuredContent` but never appears in `format()`'s rendered text is invisible to the LLM. The check runs inside `validateDefinitions()` so it's picked up automatically by `createApp()` startup, `bun run lint:mcp`, and `bun run devcheck`.

## Added

- **`format-parity` lint rule** (`src/linter/rules/format-parity-rules.ts`) — verifies `format()` output covers every terminal path in `def.output`. Mechanism: walks the output schema, builds a synthetic value where every leaf is a distinctive sentinel (unique string, large prime-range number, first enum variant, etc.), invokes `def.format(synthetic)` once, and asserts each sentinel or the field's key name appears in the rendered text. Works across all `ContentBlock` variants — text, image, audio, resource — by recursively collecting every primitive value reachable inside the content array (so image `data`/`mimeType` and resource `uri`/`text` are checked the same as text-block `text`). Emits one **error** per missing path — blocks startup. Deterministic, dependency-free, no fuzzing required. Strict match for strings/numbers; permissive (sentinel or key-name) match for booleans/enums/literals. Skipped when `format` is absent (default JSON formatter renders everything) or when the output schema fails `schema-serializable`. Graceful degradation: if `format()` throws on the synthetic sample, emits a `format-parity-threw` warning instead of failing hard, prompting the author to make `format()` total.
- **16 new test cases** in `tests/unit/linter/format-parity-rules.test.ts` — happy path (top-level + nested array-of-objects), missing fields (top-level, deep nested, multiple separate errors), wrappers (optional treated as present), permissive matching (boolean via key-name label, enum via first variant), non-text content blocks (image, resource), graceful degradation (format throws, non-object output), and integration through `lintToolDefinition`.

## Changed

- **`src/linter/rules/tool-rules.ts`** — `lintToolDefinition` now invokes `lintFormatParity` when the output schema is a valid ZodObject and a `format` function is present. Skipped when the schema fails `schema-serializable` (synthetic sample would misbehave).
- **`examples/mcp-server/tools/definitions/template-async-countdown.tool.ts`** — rewrote `format()` to unconditionally render all seven output fields (`success`, `message`, `wasCancelled`, `progress`, `duration`, `startedAt`, `completedAt`). Previously branched on `result.success`, hiding `wasCancelled` and `progress` in one branch and `startedAt`/`completedAt` in the other — which the new lint rule correctly flagged as parity violations.
- **`examples/mcp-server/tools/definitions/template-echo-message.tool.ts`** — `format()` now renders `originalMessage` and `formattedMessage` (previously only rendered `mode`, `repeatCount`, and a preview of `repeatedMessage`). Smoke test updated to assert the new fields.
- **`examples/mcp-server/tools/definitions/template-code-review-sampling.tool.ts`** — `format()` now renders `code`, `language`, and `tokenUsage.requested`/`tokenUsage.actual` in addition to `focus` and `review`. The reviewed code block is appended as a fenced code block so the LLM can reason about what was reviewed, not just the review text. Smoke test updated.
- **`CLAUDE.md` / `AGENTS.md`** — updated the `format` guidance in the Adding a Tool section to state that parity is now enforced at lint time.
- **`templates/CLAUDE.md` / `templates/AGENTS.md`** — added the lint-enforcement note to the scaffolded tool snippet so new servers internalize the contract from the first tool they write.
- **`README.md`** — expanded the Definition linter feature bullet to name format-parity as one of the enforced checks.
- **`skills/add-tool/SKILL.md`** (v1.4 → v1.5) — updated the `format()` checklist item to reference the lint rule, and added the enforcement comment to the inline template snippet.
- **`skills/field-test/SKILL.md`** (v1.1 → v1.2) — reframed the `structuredContent` parity check now that the lint rule covers the mechanical case. Field testing now focuses on what lint can't: real-data accuracy, conditional-render branches under specific values, and human-readable quality of the model's view.

## Removed

- **`skills/devcheck/`** — deleted. The skill was a thin restatement of CLAUDE.md's Commands table plus "run `bun run devcheck`, read output, fix, re-run" — no non-obvious content. The command itself prints a self-documenting summary with per-check output, and CLAUDE.md already flags it as "use often." Removing the skill trims downstream consumer sync noise. References to the command throughout other skills remain as plain `bun run devcheck` mentions.
- **`skills/walkthrough-init/`** — deleted. Internal debug skill (`audience: internal`, `type: debug`) that documented a one-off "trace the onboarding chain and report broken links" audit. Not something run regularly — the same audit can be performed ad-hoc by reading `CLAUDE.md` and following the chain manually. Removing cuts dead weight from the skills directory.

## Fixed

- **`tests/helpers/matchers.ts`** — removed an unused `biome-ignore lint/suspicious/noExplicitAny` suppression that Biome now flags as no-op (the surrounding JSDoc block already documents why `any` is required to mirror Vitest's `Assertion<T = any>`).

## Tests

- Full suite: **2264 passed** / 10 skipped, 0 regressions (2248 pre-change + 16 new parity tests, 2 existing smoke tests updated for expanded template `format()` output). Integration: **98 passed**. `bun run devcheck` green across all 8 checks (lint-mcp, biome, typescript, depcheck, audit, outdated).
