Deterministic rules
A share of the standard needs no model. A ticket reference is present or it is not — and asking an LLM invites a false positive on a fact, which is the worst kind.
Why split the standard at all
Sending a decidable rule to a model costs tokens and buys uncertainty. It also produces the one kind of finding an author cannot argue with productively: you cannot debate a model about whether the word TODO appears on a line. These rules are evaluated directly instead, in the gate's policy job, with no model call.
What is classified today
| Rule | Severity | What the checker decides |
|---|---|---|
core/untracked-todo | HIGH | Flag `TODO`/`FIXME`/placeholder code without a ticket reference. |
core/type-checker-suppression | BLOCKER | No type-checker or linter suppression without an inline comment AND a ticket reference. Recognised in whichever dialect the stack uses: `@ts-ignore`, `@ts-expect-error`, `@ts-nocheck`, `eslint-disable`, `# type: ignore`, `# noqa`, `# pylint: disable`, `@SuppressWarnings("unchecked")`, `@Suppress("UNCHECKED_CAST")`, `// swiftlint:disable`, `//nolint`, `#pragma warning disable`. |
javascript/var-in-new-code | HIGH | `var` in new code — `const` by default, `let` when reassigned. |
javascript/unsafe-numeric-coercion | HIGH | `parseInt` without radix; `Number()`/`+` coercion of user input without `Number.isFinite`. |
Everything not listed is a judgement rule and stays with the model. The classification is complete by construction — listed means deterministic, absent means judgement.
The classification lives in the manifest, not the markdown
This is the decision that removes most of the risk from splitting the source of truth. standards/*.md is what a reviewer reads, and deleting a rule from it because a checker also covers it would narrow what the model considers. So the list lives in standards/manifest.json, no rule text changed, and not one rule id moved — every historical telemetry record is keyed on them.
Where a check can only decide part of a rule it decides that part and the model still sees the whole rule. javascript/unsafe-numeric-coercion is checked for a missing parseInt radix; whether a Number() coercion is applied to user input is judgement and stays where judgement belongs.
Only added lines
A checker that read whole files would flag every var in a legacy file the author merely renamed — which the standard's own “what NOT to flag” section forbids. An author who is right to ignore one finding learns to ignore the next one too.
Running it yourself
$ git diff main...HEAD > change.diff $ npx redlinegate policy --diff-file change.diff
The floor is BLOCKER, not HIGH, on purpose: a deterministic tier that failed merges over a missing ticket reference on day one would be switched off by week two, and then nothing it decides is enforced at all.
What is deliberately not in the tier
core/hardcoded-secrets. A regex over added lines is how a secret scanner earns a reputation for false positives; the gate already runs a real one against verified secrets, and the model keeps the rule for what a scanner misses — a credential in a comment, for instance.
A rule classified as machine-checked with no implementation is a build failure. That state — everyone believes it is covered, and it is enforced by nobody — is worse than leaving it to the model, which would at least have looked.