From d3ea0822ba469b59ce796228db6e40b0e788a11c Mon Sep 17 00:00:00 2001 From: Andrew Horn Date: Mon, 5 Oct 2026 12:22:40 -0700 Subject: [PATCH 1/4] required every blob link to name the full 40-character sha Co-Authored-By: Claude Opus 5.5 --- skills/explain-diff-html/references/validation.md | 8 ++++---- skills/explain-diff-html/scripts/validate-output.sh | 13 ++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/skills/explain-diff-html/references/validation.md b/skills/explain-diff-html/references/validation.md index fa68430..e312d27 100644 --- a/skills/explain-diff-html/references/validation.md +++ b/skills/explain-diff-html/references/validation.md @@ -74,10 +74,10 @@ that mistake now fails instead of passing. still opens offline. - The page linked the references it should have, and every link names the - provenance commit. The script prints both counts as `references=N linked=M`, - then fails on any `/blob/` link that does not name the short commit. It also - fails on a link to a `.md` or `.markdown` file that has a line anchor and no - `?plain=1`. + provenance commit in full. The script prints both counts as + `references=N linked=M`, then fails on any `/blob/` link whose sha is not the + full 40 characters starting with the short commit. It also fails on a link to + a `.md` or `.markdown` file that has a line anchor and no `?plain=1`. The script's count needs both the `tr` and the `sed`. A `.filename` label reads `class="filename">path:line` when bare and diff --git a/skills/explain-diff-html/scripts/validate-output.sh b/skills/explain-diff-html/scripts/validate-output.sh index 31c5dd1..0b1c1ba 100644 --- a/skills/explain-diff-html/scripts/validate-output.sh +++ b/skills/explain-diff-html/scripts/validate-output.sh @@ -14,6 +14,10 @@ if [ -z "$page" ] || [ -z "$sha" ]; then echo "usage: sh validate-output.sh " >&2 exit 2 fi +if ! printf '%s\n' "$sha" | grep -qE '^[0-9a-f]{4,40}$'; then + echo "usage: must be 4 to 40 lowercase hex characters" >&2 + exit 2 +fi # A directory passes test -s, and step 8's $out is a directory, so require a # regular file as well. @@ -30,13 +34,16 @@ refs=$(tr '\n' ' ' < "$page" | sed -E 's#]*>##g; s/ +/ /g' \ links=$(grep -o 'class="srcref"' "$page" | wc -l | tr -d ' ') echo "references=$refs linked=$links" -wrong=$(grep -o 'href="[^"]*/blob/[^"]*"' "$page" | grep -v "$sha") +# A link names the full 40-character sha. GitHub resolves an abbreviation only +# while the commit is on a branch, so a short sha 404s once a squash merge lands. +full="/blob/${sha}[0-9a-f]{$((40 - ${#sha}))}/" +wrong=$(grep -o 'href="[^"]*/blob/[^"]*"' "$page" | grep -vE "$full") if [ -n "$wrong" ]; then - echo "FAIL: links that do not name $sha:" + echo "FAIL: links that do not name the full 40-character sha starting $sha:" echo "$wrong" status=1 else - echo "pass: no blob link names another commit" + echo "pass: every blob link names the full sha of $sha" fi # GitHub renders Markdown, and the rendered view ignores a line anchor. Match From 4d5709b1e09e5454cd6fbaf9d630b5a92fec0a99 Mon Sep 17 00:00:00 2001 From: Andrew Horn Date: Mon, 5 Oct 2026 12:22:43 -0700 Subject: [PATCH 2/4] stripped credentials from the remote and linked only github.com Co-Authored-By: Claude Opus 5.5 --- skills/explain-diff-html/SKILL.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/skills/explain-diff-html/SKILL.md b/skills/explain-diff-html/SKILL.md index fbd1a1a..0236a42 100644 --- a/skills/explain-diff-html/SKILL.md +++ b/skills/explain-diff-html/SKILL.md @@ -414,9 +414,24 @@ gh pr view --json url -q .url # https://github.com/owner/repo/pull/ # strip the trailing /pull/ # Branch, commit range, single commit, or no argument: -git remote get-url origin | sed -E 's#^git@([^:]+):#https://\1/#; s#\.git$##' +git remote get-url origin | sed -E ' + s#^[a-z+]+://## + s#^[^@/]*@## + s#^([^:/]+):[0-9]+/#\1/# + s#^([^:/]+):#\1/# + s#\.git/?$## + s#/$## + /^github\.com\//!d + s#^#https://#' ``` +The `sed` strips the scheme, then any `user:token@`, then a port, and turns the +`host:owner` form into `host/owner`. It prints nothing unless the host is +exactly `github.com`, and empty output means leave every reference bare. Do not +shorten it. A CI clone's remote often embeds a token, as in +`https://x-access-token:@github.com/owner/repo.git`, and without the +second line that token lands in every link on a page meant for sharing. + Build each link against the same commit the provenance line names, using the full 40-character sha: @@ -463,7 +478,8 @@ it does today. This is GitHub only, on purpose. A url from `gh` names a GitHub-family host by construction, so link against whatever host it gives you. A parsed remote could -be any forge, so link it only when the host is exactly `github.com`. GitLab and +be any forge, so link it only when the host is exactly `github.com`, which the +`sed` above enforces. GitLab and Bitbucket build blob URLs differently, and a GitHub-shaped URL aimed at them resolves to nothing. Leave those references bare rather than guess. From 71bd14d953621209cd258c8a0f20c4f265a7368d Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 6 Oct 2026 12:27:41 -0700 Subject: [PATCH 3/4] fix: host/owner extraction Co-authored-by: Malav Shah --- skills/explain-diff-html/SKILL.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/skills/explain-diff-html/SKILL.md b/skills/explain-diff-html/SKILL.md index 0236a42..189d651 100644 --- a/skills/explain-diff-html/SKILL.md +++ b/skills/explain-diff-html/SKILL.md @@ -415,23 +415,27 @@ gh pr view --json url -q .url # https://github.com/owner/repo/pull/ # Branch, commit range, single commit, or no argument: git remote get-url origin | sed -E ' + # Drop a port. This runs before the scheme goes, because after that the + # owner in git@github.com:1234/repo.git would read like a port. + s#^([a-z+]+://[^/]*):[0-9]+/#\1/# + # Drop the scheme, such as https://, ssh://, or git+ssh://. s#^[a-z+]+://## + # Drop credentials, such as git@ or user:token@, so no token reaches a link. s#^[^@/]*@## - s#^([^:/]+):[0-9]+/#\1/# + # Turn the host:owner form into host/owner. s#^([^:/]+):#\1/# + # Drop a trailing .git, then a trailing slash. s#\.git/?$## s#/$## + # Print nothing unless the host is exactly github.com. /^github\.com\//!d s#^#https://#' ``` -The `sed` strips the scheme, then any `user:token@`, then a port, and turns the -`host:owner` form into `host/owner`. It prints nothing unless the host is -exactly `github.com`, and empty output means leave every reference bare. Do not -shorten it. A CI clone's remote often embeds a token, as in -`https://x-access-token:@github.com/owner/repo.git`, and without the -second line that token lands in every link on a page meant for sharing. - +Empty output means leave every reference bare. A CI clone's remote can embed a +token, as in `https://x-access-token:@github.com/owner/repo.git`, and +without the credentials line that token lands in every link on a page meant +for sharing. Build each link against the same commit the provenance line names, using the full 40-character sha: From feb22c7929408028c911771a824196736f804f09 Mon Sep 17 00:00:00 2001 From: Malav Shah Date: Tue, 6 Oct 2026 13:27:14 -0700 Subject: [PATCH 4/4] added empty line between skill description --- skills/explain-diff-html/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/skills/explain-diff-html/SKILL.md b/skills/explain-diff-html/SKILL.md index 189d651..6500474 100644 --- a/skills/explain-diff-html/SKILL.md +++ b/skills/explain-diff-html/SKILL.md @@ -436,6 +436,7 @@ Empty output means leave every reference bare. A CI clone's remote can embed a token, as in `https://x-access-token:@github.com/owner/repo.git`, and without the credentials line that token lands in every link on a page meant for sharing. + Build each link against the same commit the provenance line names, using the full 40-character sha: