Make em-dash-overuse an advisory rule with browser parity

Em-dashes are used legitimately by humans, so em-dash-overuse fired far too
often. Reclassify it as the first advisory-tier rule: detected, but never a
failure.

Engine
- Add `advisory: true` to the rule metadata schema (em-dash-overuse is the
  first). findings.mjs stamps `advisory: true` on advisory findings so every
  consumer can partition without a registry lookup. Rule count stays 58.
- Raise the firing threshold from a flat 5 dashes to two gates: an absolute
  floor of 8 and a density of about one dash per 500 characters of body text.
  A long article that uses a few em-dashes no longer trips; a short,
  dash-per-clause page still does. Entity decoding (mdash, numeric, hex) is
  unchanged. Thresholds live in shared/constants.mjs so every engine agrees.

Browser parity
- The browser bundle carried a registry entry but no logic, so the overlay and
  extension could never flag it. Add checkEmDashOveruse / checkEmDashOveruseDOM
  in rules/checks.mjs (reads rendered text, no entity decoding needed), wire it
  into the injected page-level pass, and carry the advisory flag through
  serializeFindings so the overlay/extension can render it with the mildest
  affordance.

CLI
- Advisory findings print under a separate dimmed "Advisory" section, are
  excluded from the failure count, and never change the exit code (an
  advisory-only scan exits 0). JSON keeps them with `"advisory": true`.
  `--no-advisory` suppresses them entirely.

Hook
- Advisory rules are skipped by default in both the per-edit and Stop deep-pass
  hooks, so the hook never nags about them. Opt in with
  `.impeccable/config.json` -> `detector.advisoryRules: "include"`.

Tests
- Fixture + threshold + browser-adapter coverage; advisory-skip default and
  opt-in for the hook; formatFindings partitioning. The em-dash-overuse stand
  for a deferred copy rule in the tier tests is swapped to marketing-buzzword.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-22 12:23:06 -07:00
co-authored by Claude Fable 5
parent e409bec7b5
commit 270f4d20aa
12 changed files with 411 additions and 57 deletions
+15
View File
@@ -1223,6 +1223,10 @@ if (IS_BROWSER) {
type: f.type || f.id,
category: ap ? ap.category : 'quality',
severity: f.severity || ap?.severity || 'warning',
// Advisory findings (em-dash overuse, etc.) are surfaced but never
// treated as failures; carry the flag so the overlay/extension can
// render them with the mildest affordance and consumers can filter.
advisory: (ap && ap.advisory === true) || f.advisory === true,
detail: f.detail || f.snippet,
ignoreValue: f.ignoreValue || f.value || '',
name: ap ? ap.name : (f.type || f.id),
@@ -1541,6 +1545,17 @@ if (IS_BROWSER) {
addBrowserFindings(groupMap, document.body, repeatedTextFindings);
}
// Em-dash overuse (advisory): browser parity with the static/regex path.
// Reads rendered body text so it catches dashes written as HTML entities.
// serializeFindings stamps the advisory flag from the registry.
const emDashFindings = checkEmDashOveruseDOM()
.map(f => ({ type: f.id, detail: f.snippet }))
.filter(f => _ruleOk(f.type));
if (emDashFindings.length > 0) {
pageLevelFindings.push(...emDashFindings);
addBrowserFindings(groupMap, document.body, emDashFindings);
}
const layoutFindings = checkLayout().filter(f => _ruleOk(f.type));
for (const f of layoutFindings) {
const el = f.el || document.body;