check-issue-refs: catch ambiguous owner#N cross-repo refs in markdown too
This commit is contained in:
parent
9d02d81144
commit
786ab304d6
9 changed files with 64 additions and 39 deletions
|
|
@ -1,47 +1,72 @@
|
|||
#!/bin/sh
|
||||
# CI lint: flags tracker tags (a hash followed by an issue number) in
|
||||
# source comments. The hive convention is prose, not tracker tags, in
|
||||
# code (see /knowledge/hive-rules.md) — tags rot, they point at moving
|
||||
# targets and leak tracker coupling into the source tree.
|
||||
# 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.
|
||||
#
|
||||
# Emits a CI error annotation per hit and exits 1 if any tag is found,
|
||||
# 0 otherwise. It runs as its own CI job and IS a required check on the
|
||||
# forge (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.
|
||||
#
|
||||
# Scope: tracked *.rs *.nix *.js *.ts *.tsx *.css *.html. Markdown is exempt
|
||||
# (prose docs may legitimately cite the tracker). The pattern matches a
|
||||
# hash, 2-5 digits, then a non-alphanumeric char or end-of-line. A real
|
||||
# tracker tag is never glued to a letter, so the trailing class skips
|
||||
# both letter-bearing / 6-8-digit hex colours (the digit run breaks or
|
||||
# overruns) and digit-runs followed by a letter — e.g. hash-route
|
||||
# fragments like #24h. Residual: a pure-numeric short hex (e.g. three
|
||||
# identical digits) 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: a line containing the marker `lint:allow` is exempt.
|
||||
# Reserve it for genuine `#<digits>` that aren't tracker tags — e.g. a
|
||||
# `#123` markdown-heading example or hash-prefixed test-input data —
|
||||
# and keep a short reason next to the marker. Don't use it to keep a
|
||||
# real tracker tag; rewrite those to prose.
|
||||
# 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
|
||||
|
||||
pattern='#[0-9]{2,5}([^0-9a-zA-Z]|$)'
|
||||
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 `#<digits>`; see the header).
|
||||
hits="$(
|
||||
# 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 "$pattern" /dev/null 2>/dev/null \
|
||||
| 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
|
||||
)"
|
||||
|
||||
if [ -n "$hits" ]; then
|
||||
echo "$hits" | while IFS=: read -r file lineno _; do
|
||||
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
|
||||
count="$(printf '%s\n' "$hits" | wc -l | tr -d ' ')"
|
||||
printf 'check-issue-refs: %s tracker tag(s) found in source\n' "$count" >&2
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue