#!/bin/sh # CI lint, two passes over tracked files. Emits a CI error annotation per # hit, exits 1 if either pass finds something. Its own required CI job # (branch protection) — a hit blocks merge. # # 1. Source comments (`.rs .nix .js .ts .tsx .css .html`): flags ANY tracker # tag (hash + issue number) outright — the hive convention is prose, not # tracker tags, in code (/knowledge/hive-rules.md); tags rot. Markdown is # exempt here (prose docs may cite the tracker with a bare `#N`). Pattern: # a hash, 2-5 digits, then non-alphanumeric-or-EOL — skips letter-bearing # hex colours and digit-runs-then-letter (`#24h`). Residual: a pure- # numeric short hex trips it — write the six-digit form to dodge. # # 2. Markdown (`.md`): narrower — only an AMBIGUOUS cross-repo-shaped ref, a # word glued directly to `#N` with no `/` (`hyperhive#1234`). A bare # `#1234` or a qualified `owner/repo#1234` stay legal; only the glued, # unqualified `owner#N` — indistinguishable from a real cross-repo # citation without checking whether `owner` is a live org — gets caught # (the forge's public mirror carries no issue/PR data, so this is dead # weight for a public reader regardless of which repo it meant). The # glued prefix must start lowercase (real org/repo slugs are, see # `hive_types::Ident`) — rules out `PR#4124`/`Hyperhive#1234`. Residual: # an ordinary lowercase word glued to a number (`line#123`) still # matches — no live-allowlist option in a dependency-free script; rare in # practice, same escape hatch as pass 1. # # Escape hatch (both passes): a line with the marker `lint:allow` is exempt. # Reserve it for a genuine non-tag hit and keep a short reason next to it — # not for a real tracker tag or ambiguous ref, rewrite those instead. set -eu src_pattern='#[0-9]{2,5}([^0-9a-zA-Z]|$)' md_pattern='(^|[^0-9A-Za-z._/-])[a-z][a-z0-9-]*#[0-9]{2,5}([^0-9a-zA-Z]|$)' # `/dev/null` forces grep to always print a filename prefix, even when # xargs hands it a single file. `-r`/`-0` keep it robust to odd paths and # an empty file list. Lines carrying the `lint:allow` marker are dropped # (legitimate non-tracker hit; see the header). src_hits="$( git ls-files -z '*.rs' '*.nix' '*.js' '*.ts' '*.tsx' '*.css' '*.html' \ | xargs -0 -r grep -nE "$src_pattern" /dev/null 2>/dev/null \ | grep -v 'lint:allow' || true )" md_hits="$( git ls-files -z '*.md' \ | xargs -0 -r grep -nE "$md_pattern" /dev/null 2>/dev/null \ | grep -v 'lint:allow' || true )" count=0 if [ -n "$src_hits" ]; then echo "$src_hits" | while IFS=: read -r file lineno _; do printf '::error file=%s,line=%s::tracker tag in source — write prose, not a hash-number tag (see /knowledge/hive-rules.md)\n' "$file" "$lineno" done src_count="$(printf '%s\n' "$src_hits" | wc -l | tr -d ' ')" count=$((count + src_count)) fi if [ -n "$md_hits" ]; then echo "$md_hits" | while IFS=: read -r file lineno _; do printf '::error file=%s,line=%s::ambiguous cross-repo reference — a bare `#N` or a fully qualified `owner/repo#N` are fine, but this looks like an unqualified `owner#N`; spell out the repo or clarify in prose\n' "$file" "$lineno" done md_count="$(printf '%s\n' "$md_hits" | wc -l | tr -d ' ')" count=$((count + md_count)) fi if [ "$count" -gt 0 ]; then printf 'check-issue-refs: %s hit(s) found\n' "$count" >&2 exit 1 fi exit 0