Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

site-facts

Fail the build when your site states a number it can no longer defend.

Zero dependencies. One JSON file. Runs in any CI.

node check-site-facts.js
checked 64 files against site-facts.json (as of 2026-07-16)

❌ 1 problem:

  llms.txt
    states "45.3x" — superseded for portfolio-leverage (current: 42.6× / 42.6x / 42.6 倍)
    → historical use is allowed only in: fte-methodology.html, competitive-reports/

Why this exists

We audited our own marketing site and found the same number stated four different ways. Commits were ~3,800 in one paragraph and ~3,960 in another — on the same page. Our leverage figure was 46.9× in a report, 45.3× in the constitution, and ~30–56× on two other pages. A capability count said 112 on the site and 114 in the source file.

The uncomfortable part: none of them were wrong. Each was true on the day it was written. They conflicted because every one had been hardcoded into prose with no date attached, and the site kept moving.

We wrote a rule — every number carries a date — and broke it four times within a day, on the very round that prompted it. Human discipline is not a control. So the rule became a file, and the file became CI.

grep cannot help here, because a stale number and a historical one look identical. Only a declaration can tell them apart. That is the whole idea.

Install

Copy two files into your repo:

tools/check-site-facts.js
tools/site-facts.json

No npm install. It is deliberately dependency-free, so an unrelated package upgrade can never take the guard offline.

The facts file

{
  "as_of": "2026-07-16",

  "scan": {
    "root": "../public",              // resolved relative to this file
    "extensions": [".html", ".txt"],
    "ignore": ["private-decks"],
    "css": ["styles.css"]
  },

  "facts": [
    {
      "id": "portfolio-leverage",
      "value": ["42.6×", "42.6x", "42.6 倍"],   // every spelling you actually publish
      "superseded": ["45.3×", "46.9×"],         // must not appear...
      "historical_ok": ["methodology.html"]     // ...except here
    }
  ],

  "agreement": [
    {
      "id": "leverage-agrees",
      "note": "Every page quoting the headline must quote the same one.",
      "pattern": "\\b4[0-9]\\.[0-9]\\s*(?:×|x|倍)",
      "strip": "[\\s×x倍]",                      // normalise units before comparing
      "files": ["index.html", "llms.txt"],
      "expect": "42.6"
    }
  ],

  "structure": {
    "faq_schema_must_match_visible": true,
    "jsonld_must_parse": true,
    "no_ch_unit_in_headings": true,
    "no_internal_html_links": true
  }
}

Updating for a new measurement

  1. Move the old value into superseded
  2. Set the new value, bump as_of
  3. Run it, follow the failures

One file, then do what it tells you.

historical_ok is the important field

Dated artifacts — a report from July 4th, a trajectory table showing how a number moved — should contain old values. That is what they are for. Allowlist them.

Everything else is undated prose, and undated prose is the only thing that can go stale. Getting this distinction wrong in either direction makes the tool useless: too strict and you delete your own history, too loose and you ship contradictions.

What it checks

Check Caught in the wild
Superseded values outside their allowlist llms.txt still said 45.3x after the whole site moved to 42.6×
Pages quoting one headline must agree Two pages built their tables from different measurement rounds
FAQPage schema matches the visible FAQ Shipped 3× — schema had 4 questions, page showed 6 different ones
JSON-LD parses One syntax error drops the entire block, silently
ch in heading max-width max-width: 11ch squeezed every heading into a 270px ribbon
Internal .html links 21 links each costing a redirect hop

The last two are not about numbers, but they are the same disease: a rule written for one context, quietly wrong in another. ch is the width of the glyph 0 — it describes Latin digits and nothing else, so the same CSS produced a different column in Thai and Chinese.

What it does not do

  • It checks only what you declare. A number you never wrote down is a number nobody is watching.
  • It cannot read images. Our hero PNG had 40–70× baked into it, directly above text reading 42.6×. The page contradicted itself on one screen and no script could ever have seen it.
  • It verifies consistency, not truth. It knows your site agrees with your facts file. It does not know your facts file is right. Proving a number still means counting it from source.

That third limit is the real one. This tool stops you from drifting. It cannot stop you from being wrong on purpose.

In production

Runs on every push at dnafinity.com — 64 files, 11 facts, across Thai, English and Chinese. The methodology page it guards (why our own headline number is softer than it looks) is the reason the tool exists.

License

MIT

About

Fail the build when your site states a number it can no longer defend. Zero deps, one JSON file, any CI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages