Notes

I built a check, and the first thing it caught was me

Read as
working implementation · tests
Build treatmentUpdated 14 Jul 2026

Production AI systems · Verification gates

What you will leave with

Add a route registry, a duplication ratchet, a CI workflow, and a negative test that proves both gates reject.

For engineers turning route hygiene and duplication limits into required CI checks.~4 min read
PrerequisitesNode 20+ESLintGitHub Actions or equivalent CI
01 · System view

The gate you will ship

Two detectors, one required workflow, and proof that red really blocks.

  1. 01Route registryOne named source for internal paths.
  2. 02AST ruleRejects new hardcoded internal links.
  3. 03jscpdMeasures structural duplication as a ratchet.
  4. 04CI jobRuns both checks on every change.
  5. 05Branch rulePrevents a red job from merging.
02 · Implementation

Build it in sequence

01

Centralise internal routes

Use constants for fixed routes and functions for parameterised routes.

src/lib/routes.ts · sanitized excerpttypescript
export const ROUTES = {
  HOME: '/',
  DASHBOARD: '/dashboard',
  occupation: (code: string) => `/occupations/${code}` as const,
  reportDetail: (id: string) => `/dashboard/reports/${id}` as const,
} as const
02

Reject hardcoded internal links

The real custom rule checks router push/replace, redirects, Link, and anchor href values as syntax-tree nodes. It ignores protocol-relative URLs and dynamic expressions.

eslint-rules/no-hardcoded-route.js · production excerptjavascript
function isInternalPath(value) {
  return typeof value === 'string'
    && value.startsWith('/')
    && !value.startsWith('//')
}

function checkLiteral(context, node) {
  if (node?.type === 'Literal' && isInternalPath(node.value)) {
    context.report({
      node,
      message: 'Use a ROUTES constant instead of a hardcoded path.',
    })
  }
}

// The production rule calls checkLiteral for router.push/replace,
// redirect/permanentRedirect, and JSX href attributes.
03

Add a duplication ratchet

Measure the current baseline first. Set the threshold slightly above it, then lower it as duplication is removed.

.jscpd.jsonjson
{
  "path": ["src"],
  "format": ["typescript", "tsx"],
  "mode": "mild",
  "threshold": 3.6,
  "reporters": ["console"],
  "ignore": ["**/*.test.ts", "**/*.test.tsx", "**/*.d.ts"]
}

The repository measured a 3.06% baseline and set 3.6% as the first enforceable ceiling. Do not reuse that number without measuring your own tree.

04

Run both checks raw in CI

Do not pipe the duplication command through grep or tee without preserving its status. The detector must own the exit code.

.github/workflows/ci.yml · production commandyaml
duplication:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - name: Copy-paste detection (jscpd)
        run: npx --yes --package=jscpd@4.0.5 jscpd src --config .jscpd.json
05

Prove the failure path

Commit one forbidden link and one duplicated block on a throwaway branch. Capture the red output, then revert.

expected outputtext
error  Use a named route from src/lib/routes.ts
ERROR: duplication 4.37% exceeds threshold 3.60%
Process completed with exit code 1
06

Do not let “no checks yet” mean green

The repository cannot rely on paid branch protection, so its sanctioned merge script waits for every always-on check to register, complete, and succeed. Missing stays pending; red refuses the merge.

scripts/safe-merge.sh · production excerptbash
REQUIRED_CHECKS=(verify duplication type-drift permission-snapshot e2e-filter)

for check in "${REQUIRED_CHECKS[@]}"; do
  state="$(status_of "$check" "$ROLLUP")"
  if [ "$state" != "COMPLETED" ]; then
    pending+=("$check:$state")
  fi
done

# Missing is never success. Any failed conclusion exits without merging.
03 · Review

Definition of done

  • Existing call sites use the registry.
  • New hardcoded internal links fail lint.
  • Duplication threshold is based on a measured baseline.
  • CI preserves each detector’s exit code.
  • The workflow is a required branch check.
  • Both negative tests have been observed failing.
04 · Failure modes

What breaks and what it means

The duplication job prints an error but CI stays green.

A pipe or wrapper swallowed the detector’s exit code. Run it directly or enable pipefail.

The route rule produces hundreds of errors.

Migrate first, enable as warning during the sweep, then switch to error in the same change that reaches zero.

Public discussion

Questions, corrections, and useful disagreement

Moderated on GitHub ↗

Sign in with GitHub to join the conversation. Comments are public. Article views are anonymous and counted once per browser, per article, each day.

Read next03

The most expensive tester in my company is me

The most expensive tester in my company is me, catching bugs by eye on staging. Shift-left is the fix: every catch becomes a gate that blocks the bug automatically.

3 depths