Enforce Writing Rules Twice: a Lint Gate and a Sanitizer

A writing rules document you keep only in your head is a set of rules you’ll break at midnight, tired and just wanting the post out. I have a style guide with dozens of rules — keyword placement, table wrapping, minified schema, no stray emoji in headings — and for a while it lived in a file I was supposed to remember. I didn’t, reliably. The fix wasn’t discipline. It was enforcing the rules in code, twice: once as a gate that blocks a publish, and again as a sanitizer that re-checks every live edit. Here’s how both halves work and why one on its own isn’t enough.

Where writing rules actually get broken

Rules get broken at two distinct moments, and they need different defenses. The first is at the point of writing, when a draft is being assembled and something slips: a table without its mobile wrapper, a keyword missing from the title, a schema block left pretty-printed. The second is later, at the point of editing, when a post that was already correct gets changed and the change quietly violates a rule the original satisfied.

Most people only defend the first moment. They add a checklist or a script that runs before publishing, feel covered, and move on. But a live site is edited constantly — a link swapped, a paragraph added, a price updated — and every one of those edits is a fresh chance to break a rule with no gate watching. The post that was perfect on launch day is not the post that’s live three months later.

So the design goal is to cover both moments with the same rules. Not two different standards, but one set of rules enforced twice: before anything is published, and again whenever anything published is touched. Same rules, two checkpoints.

The lint gate: enforcing writing rules before publish

The first checkpoint is a lint gate: a script that reads a finished draft and refuses to let it publish if it violates any rule. The word “refuses” is the important part. A warning you can ignore is a rule you will ignore. So the gate distinguishes hard failures, which block the publish entirely, from soft warnings, which it prints but lets through after I’ve read them.

Mechanically it’s boring, which is the point. The script takes a post folder, reads the HTML and the metadata, and runs each rule as a small check. If any check returns a failure, the script exits with an error and nothing ships. There’s no “publish anyway” button, because the whole value is that it can’t be skipped when I’m tired. It runs as part of the content pipeline I run, right before anything is scheduled through the WordPress REST API.

The reason a machine enforces this instead of a checklist is that a checklist depends on the one thing I can’t guarantee: that I’ll run it carefully every single time, including the times I’m rushing. A gate has no such weakness. It runs identically at nine in the morning and eleven at night, and it does not get bored on the fortieth post.

Flow diagram enforcing writing rules twice, with a lint gate blocking publish and a live sanitizer re-checking edits.
The same writing rules enforced at two checkpoints: a lint gate that blocks a publish, and a sanitizer that re-checks every live edit. Diagram by Nuriforge (AI-assisted).

What the pre-publish check actually enforces

Rules only help if they map to real failures. Every check in my gate exists because something once went wrong without it, so the list reads like a scar map rather than a style opinion. A handful of the checks that block a publish give the flavor of it.

The table check exists because of a real incident, which I describe in The Mobile Table Trap That Breaks WordPress on Phones (Coming soon).

Link hygiene: no tracking parameters in any URL, no editor or localhost links, and internal links must use final trailing-slash URLs rather than paths that redirect. Structure: every table must carry its responsive wrapper, no stray horizontal-rule tags, and no emoji inside headings. SEO placement: the focus keyword has to appear in the title, description, permalink, first paragraph, and H1, and the permalink must not contain a year. Schema and voice: there has to be a valid FAQ block, and a small stop-list of overused phrases I’ve caught myself reusing gets flagged so the writing doesn’t start sounding like itself. That stop-list is only the cheap half of the problem; the full mechanism is a running log of every opening, anecdote and comparison I have already used, which I cover in Anti-Repetition for an AI Writer: a Log That Never Repeats.

Lint gate (pre-publish) Live sanitizer (on edit)
When it runs Before a post is scheduled Whenever a live post is changed
What it reads The finished draft on disk The post’s stored live markup
On failure Blocks the publish outright Re-applies the rule or refuses the push
Mainly catches Mistakes made while drafting Rules broken by a later edit

The point of listing them isn’t the specific rules, which are mine and yours will differ. It’s that each one is a binary a machine can check without judgment. “Is every table wrapped?” has a yes-or-no answer. Anything that needs taste stays with the human reviewer; anything mechanical becomes a check the gate owns.

The live sanitizer: enforcing the rules a second time

The second checkpoint is where most setups have a hole. Once a post is live, editing it usually means opening the editor and changing something directly, with no gate in the path. The pre-publish check already ran and passed weeks ago; it has no idea you just pasted a new section with a pretty-printed schema block in it. So I enforce the same rules again at the moment of any live change, as a sanitizer.

In practice that means I don’t hand-edit a live post in the dashboard. I fetch the post’s current stored markup, apply the change in code, run the same rule checks against the result, and only then push it back. If the edit would break a rule — re-injecting whitespace into schema, dropping a table wrapper, adding a redirect-chained link — the sanitizer either repairs it or refuses the push. The rules that guarded the front door now also guard every later change.

This also solves a subtler problem: the disk copy of a post drifts from the live version over time, because live edits and platform quirks change the stored markup. Re-running the rules against what’s actually live, rather than against an old local file, means the second checkpoint is checking reality. It’s the same principle as the first gate — enforce in code, not from memory — applied to the moment memory is most likely to fail.

Terminal screenshot of a pre-publish lint gate enforcing writing rules, showing style warnings and a final zero-failure ready-to-publish summary.
The pre-publish lint gate clearing this very post: two style warnings print, nothing fails, and the run ends at “0 FAIL — ready to publish”. Screenshot from my own pipeline (terminal prompt masked).

Why one gate on its own isn’t enough

You could argue this is overkill — surely one good pre-publish check covers you. It doesn’t, and the reason is timing. A pre-publish gate sees a post exactly once, at the instant it goes out. Everything that happens to that post afterward is unguarded. And “afterward” is where a lot of rule-breaking actually happens, because editing an existing post feels low-risk in a way that publishing a new one doesn’t.

The edits that slip through are almost always small and well-intentioned. You add a comparison table to an old post and forget the wrapper. You paste an updated schema block from a source file that was pretty-printed. You swap a link and the new one carries a tracking parameter. None of these feel like the kind of change that needs a gate, which is exactly why they get through. A single pre-publish check has already clocked out by the time any of them happen.

Enforcing twice closes that gap without doubling the work, because it’s the same rules running in two places. I write a check once and it protects both the first publish and every edit after. The cost is small; the payoff is that a post can’t rot into a rule violation months after it launched, when I’ve long stopped thinking about it.

How to build a gate like this yourself

You don’t need my exact setup to get the benefit. Start by writing your rules down as a list of yes-or-no questions, then turn the three or four that keep catching you into a script that reads a draft and exits with an error if any of them fails. That alone — a check you cannot skip — is most of the value. Wire it in right before whatever step publishes your post, so passing the gate is the only path to going live.

For the rules themselves, borrow from where the failures are expensive. Keep structured data valid and minified, force responsive wrappers on tables, and verify keyword placement, because those are the mistakes that are invisible in the browser and costly at scale. Then, once the gate exists, reuse the same checks at edit time by fetching the live markup, applying changes in code, and re-running the checks before you push. One rule set, two checkpoints, no reliance on remembering anything.

FAQ

Why enforce writing rules in code instead of using a checklist?

Because a checklist depends on you running it carefully every time, including the times you’re rushing at midnight. A script runs identically no matter how tired you are and never skips a step. Rules that live only in your head are the ones you break under pressure.

What’s the difference between the lint gate and the sanitizer?

The lint gate runs before a post is published and blocks it if any rule fails. The sanitizer re-runs the same rules whenever a live post is edited, because a pre-publish check never sees later changes. Same rules, two moments: first publish and every edit after.

What should a pre-publish lint gate actually check?

Anything mechanical with a yes-or-no answer: link hygiene, responsive table wrappers, no stray horizontal rules or heading emoji, keyword placement, and valid minified schema. Leave anything that needs taste to a human reviewer. The gate owns the binary checks; the person owns judgment.

Should a failed rule block the publish or just warn?

Split them. Hard rules that cause real damage should block the publish outright, with no override. Softer, stylistic issues can print as warnings you read and decide on. A warning you can ignore is a rule you will eventually ignore, so keep the important ones as blockers.

Why re-check rules on a live edit if the post already passed?

Because passing once doesn’t protect a post from later changes. A swapped link, a pasted section, or an updated table can each break a rule the original satisfied. Re-running the checks against the live markup at edit time catches violations a one-time gate has no way to see.

My Thoughts

The lesson I keep relearning is that willpower is a terrible enforcement mechanism. I know my own rules cold, and I still broke them, not out of ignorance but out of tiredness and haste. Moving the rules into code didn’t make me more disciplined; it made discipline unnecessary for the parts a machine can check. What’s left for me is the work that actually needs a person — is this true, is this useful, is this worth publishing — while the mechanical rules enforce themselves at both ends. A rule you can’t skip is worth ten you merely intend to follow.