Voice

The foghorn voice

The voice is a set of habits. Each habit has a reason. Each has an example from a real foghorn draft or from a checker test. The checker enforces the habits that code can enforce.

foghorn icon: a red-orange bell buoy on dark blue water

Say what it did, then what it does

First the old behavior, then the new one. Two short paragraphs. The reader sees the difference without reading code.

Previously the check flagged every number in an arithmetic expression, even when the expression is the initial value of a const that names the result. The check also flagged a number inside an as const table, although the table is the named constant.

With this release, the check skips a number in the arithmetic of a const initializer and a number inside an as const literal.

Show the output

An entry that changes behavior shows the output. The blocks carry the labels Original code, Old output and New output. The checker fails an entry that says "previously" or "now" and shows nothing.

ts-patterns: 3 mechanical violation(s) in magic.ts:
  L1:26  no-magic-numbers  No magic number: 16.  → SKILL.md Core Rule 9 — extract to a named const
  L1:31  no-magic-numbers  No magic number: 1024.  → SKILL.md Core Rule 9 — extract to a named const
  L1:38  no-magic-numbers  No magic number: 1024.  → SKILL.md Core Rule 9 — extract to a named const

That block is the old output of the entry above. The new output is "none".

Say who is affected and how to check

When a change can break someone, the entry adds two lines. One names the people. One gives a command that tells them if they are one of them. The checker warns when a risky entry has neither line.

Affects: anyone who runs the plugin on an existing TypeScript codebase. The PostToolUse hook exits with code 2 when it finds a violation. An edit to a file that already contains one of these patterns now goes back to Claude for a fix.

To check: pipe a hook payload to the engine, as in echo '{"tool_input":{"file_path":"src/a.ts"}}' | node "${CLAUDE_PLUGIN_ROOT}/engine/dist/check.js".

Credit the people who helped

Credit goes to the person who reported the change and the person who fixed it. It goes by handle, in one sentence. The checker compares each handle with the people the API named. It does not credit the owner of the repository, unless the voice sets creditOwner.

Thanks to @alice for reporting this issue. This fix was contributed by @bob.

Say what you do not know

A hedge that marks a real doubt stays in the note. The checker never flags one. A limit goes in the entry, where the reader needs it.

Each check is syntactic only, because the engine has no type information, so each one matches a narrow shape:

Say less

No emoji, no summary table and no marketing words. The checker warns on a banned word, on an entry that runs long and on a paragraph that is a wall of text. Plain facts are enough.

The check skill and the review agent gain the rules that the audit found. No mechanical check covers them.

Presets

A voice is a JSON file. A preset is a voice that ships with the plugin.

  • foghorn is the default. It uses the first person. It adds the Affects: and To check: lines.
  • neutral uses the third person. The project is the subject. Credits read "Reported by @x. Fixed by @y."
  • esbuild is a calibration preset. The next section explains it.

What a voice sets

  • person: first or third.
  • creditStyle: how credits read.
  • creditOwner: false by default. foghorn credits only the people who wrote or reported a change.
  • prefaceRules: the bold line and the pinning advice of a breaking release.
  • exampleStyle: the three labels of an example.
  • affectsLine: whether the voice allows an Affects: line.
  • maxEntryChars: the length limit of an entry.
  • openers and bannedWords: the opening phrases, and the marketing words that the checker warns about.
  • severity: a voice can change the level of any check. A preset can turn one hard check into a warning.
{
  "severity": {
    "entry-no-link-or-credit": "warn"
  }
}

Where the voice comes from

Evan Wallace writes the release notes of esbuild. They inspired the foghorn voice. They name the change, show the old and the new output, and say when he is not sure.

Hopefully this release is uneventful

Source: esbuild v0.26.0

The regression was introduced in version 0.18.7 of esbuild.

Source: esbuild v0.23.1

Those notes seldom say who is affected. foghorn adds that. foghorn is not affiliated with esbuild. esbuild has not endorsed it.

The preset esbuild is the preset calibrated on esbuild's own release notes. We ran the checker on the 30 latest of those releases, v0.22.0 to v0.28.2. Under esbuild, 2 of 30 break a hard check. Under foghorn, 8 of 30 do. The gap is the link-or-credit check. Some of those entries have neither. The esbuild preset makes that a warning. The foghorn voice keeps it hard, because a reader needs a place to look.