name: Doctrine — banned-token grep gate # Runs on every PR and every push to main. # No path filter: the SPA (web/src/**), packages/**, and all docs are scanned. # # Design: # - The banned-token pattern is stored as a shell variable (data), not prose, # so this workflow file does not trip its own check. # - Tailwind utility classes (leading-{none,tight,snug,normal,relaxed,loose,N}) # are excluded via a second grep that strips those matches before evaluation. # - Files listed in .doctrine-allowlist are excluded from the scan. That file # is the only legitimate way to opt a path out; self-granted exemptions # (e.g. __doctrine-scanner-exempt keys in package.json) are NOT honoured # by this gate and are flagged by the M2 finding. # # Authority: Doctrine v7 §1, Founder Stephen P. Lutar Jr. # ORCID: 0009-0001-0110-4173 # # Signed-off-by: Stephen P. Lutar Jr. on: push: branches: [main] pull_request: branches: [main] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: banned-token-grep: name: Banned-token scan (Doctrine v7 §1) runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 # need history for PR diff mode - name: Build file list id: filelist shell: bash run: | # For pull_request events: scan only files changed in the PR diff. # For push-to-main: scan the full tree (so stale files don't accumulate). if [ "${{ github.event_name }}" = "pull_request" ]; then git diff --name-only \ "origin/${{ github.base_ref }}" \ "${{ github.sha }}" \ > /tmp/changed_files.txt echo "mode=diff" >> "$GITHUB_OUTPUT" else git ls-files > /tmp/changed_files.txt echo "mode=full" >> "$GITHUB_OUTPUT" fi # Remove allowlisted paths from the scan list. if [ -f .doctrine-allowlist ]; then while IFS= read -r line; do # Skip blank lines and comments. [[ -z "$line" || "$line" == \#* ]] && continue grep -v "^${line}" /tmp/changed_files.txt > /tmp/changed_files_tmp.txt \ || true mv /tmp/changed_files_tmp.txt /tmp/changed_files.txt done < .doctrine-allowlist fi TOTAL=$(wc -l < /tmp/changed_files.txt | tr -d ' ') echo "Scanning ${TOTAL} file(s) (mode=${{ steps.filelist.outputs.mode }})." echo "total=${TOTAL}" >> "$GITHUB_OUTPUT" - name: Grep for banned tokens id: grep shell: bash run: | # ----------------------------------------------------------------------- # Tokens are stored as shell variables (data), not inline prose, so # this workflow file does not trigger its own scan. # # Two-pass strategy for the word "leading": # Pass 1 — all banned tokens except bare "leading"; these are always # flagged regardless of Tailwind context on the same line. # Pass 2 — bare \bleading\b only; Tailwind leading-* classes on the # same line suppress the hit (per-line filter is correct here # because a line with only "leading-tight" is fine, but a line # with only bare "leading" as a marketing word is not). # ----------------------------------------------------------------------- BANNED_NO_LEADING='(revolutionary|unprecedented|world-class|seamless|industry-leading|cutting-edge|game-changing|breakthrough|best-in-class|immaculate|state-of-the-art|premier|Bo11y|Bolly|Jarvis|Wayne Slaughter)' TAILWIND_LEADING_RE='leading-(none|tight|snug|normal|relaxed|loose|[0-9]+)' HITS_FILE=/tmp/doctrine_hits.txt > "$HITS_FILE" while IFS= read -r file; do [ -f "$file" ] || continue # Pass 1: all banned tokens except bare "leading". # -H ensures filename is included in the output (file:line:content). grep -nHEi "$BANNED_NO_LEADING" "$file" \ >> "$HITS_FILE" \ || true # Pass 2: bare \bleading\b — suppress lines that contain a Tailwind # leading-* class (those are utility classes, not marketing prose). grep -nHEi '\bleading\b' "$file" \ | grep -vEi "$TAILWIND_LEADING_RE" \ >> "$HITS_FILE" \ || true done < /tmp/changed_files.txt # Count hits (non-empty lines). HIT_COUNT=$(wc -l < "$HITS_FILE" 2>/dev/null | tr -d ' \n' || echo 0) echo "hit_count=${HIT_COUNT}" >> "$GITHUB_OUTPUT" - name: Report and fail on hits shell: bash run: | HIT_COUNT=${{ steps.grep.outputs.hit_count }} if [ "${HIT_COUNT}" -gt 0 ]; then echo "::error::Doctrine v7 §1 violation: ${HIT_COUNT} banned-token hit(s) found." echo "" echo "Each match below must either be removed or — if it is a factual" echo "claim — accompanied by an adjacent citation block within 5 lines." echo "" echo "Hits (file:line:content):" cat /tmp/doctrine_hits.txt echo "" echo "If this file legitimately enumerates banned tokens for detection" echo "purposes, add it to .doctrine-allowlist (founder approval required)." exit 1 fi echo "Doctrine v7 §1 — banned-token scan: PASS (0 hits)."