Skip to content

Commit 373a548

Browse files
jhaugaCopilot
andauthored
new hook fix-broken-links (#2027)
* new hook fix-broken-links * codespell: add ans for variable short for answer * update: scripts hand off alternative url to copilot cmd * codespell: add ext. arcade-canvas/game/phaser.min.js * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * update: rm em-dash from hook scripts --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 1865463 commit 373a548

6 files changed

Lines changed: 977 additions & 1 deletion

File tree

.codespellrc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@
5454

5555
# CAF - Microsoft Cloud Adoption Framework acronym
5656

57-
ignore-words-list = numer,wit,aks,edn,ser,ois,gir,rouge,categor,aline,ative,afterall,deques,dateA,dateB,TE,FillIn,alle,vai,LOD,InOut,pixelX,aNULL,Wee,Sherif,queston,Vertexes,nin,FO,CAF,Parth
57+
# ans - bash and powershell variable short for answer
58+
59+
ignore-words-list = numer,wit,aks,edn,ser,ois,gir,rouge,categor,aline,ative,afterall,deques,dateA,dateB,TE,FillIn,alle,vai,LOD,InOut,pixelX,aNULL,Wee,Sherif,queston,Vertexes,nin,FO,CAF,Parth,ans
5860

5961
# Skip certain files and directories
6062

docs/README.hooks.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-hooks) for guidelines on how to
3232
| Name | Description | Events | Bundled Assets |
3333
| ---- | ----------- | ------ | -------------- |
3434
| [Dependency License Checker](../hooks/dependency-license-checker/README.md) | Scans newly added dependencies for license compliance (GPL, AGPL, etc.) at session end | sessionEnd | `check-licenses.sh`<br />`hooks.json` |
35+
| [Fix Broken Links](../hooks/fix-broken-links/README.md) | Checks changed web files for broken hyperlinks and SEO anchor issues after each Copilot tool use. | postToolUse | `hooks.json`<br />`link-fix.ps1`<br />`link-fix.sh` |
3536
| [Governance Audit](../hooks/governance-audit/README.md) | Scans Copilot agent prompts for threat signals and logs governance events | sessionStart, sessionEnd, userPromptSubmitted | `audit-prompt.sh`<br />`audit-session-end.sh`<br />`audit-session-start.sh`<br />`hooks.json` |
3637
| [Secrets Scanner](../hooks/secrets-scanner/README.md) | Scans files modified during a Copilot coding agent session for leaked secrets, credentials, and sensitive data | sessionEnd | `hooks.json`<br />`scan-secrets.sh` |
3738
| [Session Auto-Commit](../hooks/session-auto-commit/README.md) | Automatically commits and pushes changes when a Copilot coding agent session ends | sessionEnd | `auto-commit.sh`<br />`hooks.json` |

hooks/fix-broken-links/README.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
name: 'Fix Broken Links'
3+
description: 'Checks changed web files for broken hyperlinks and SEO anchor issues after each Copilot tool use.'
4+
tags: ['links', 'seo', 'html', 'markdown', 'post-tool-use']
5+
---
6+
7+
# Fix Broken Links Hook
8+
9+
Scans recently-changed web files for broken hyperlinks after each GitHub Copilot
10+
tool use. For each broken URL the hook tries common spelling variations, then hands
11+
the link to the Copilot CLI agent for suggested replacements, and presents an
12+
interactive fix menu. Generic anchor text (`click here`, `read more`, etc.) is
13+
flagged as an SEO issue.
14+
15+
## Overview
16+
17+
Broken links accumulate silently in web projects. Running on the `postToolUse`
18+
event, this hook checks the web files the agent just edited — and only those —
19+
right after each change, so you can fix, replace, or remove each broken link in
20+
the same terminal session.
21+
22+
The hook has two modes:
23+
24+
- **With file paths** (the edited files injected from the hook payload, or paths
25+
passed on the command line): it checks each link, looks up replacement
26+
candidates, and presents the interactive fix menu.
27+
- **With no file arguments**: it simply lists the broken links it finds — no
28+
replacement lookups and no prompts.
29+
30+
## Features
31+
32+
- **Self-contained core**: bash and PowerShell ports — no runtime to install (the optional agent
33+
hand-off reuses the Copilot CLI you already have)
34+
- **Edited-files scope**: as a `postToolUse` hook it only checks the files the agent just changed —
35+
never a full repo scan
36+
- **Format-agnostic link scan**: extracts every `http(s)` URL with `grep`, covering HTML, Markdown,
37+
JS/TS, JSON, CSS, SQL, and templates at once
38+
- **Automatic URL healing**: tries www, https, and trailing-slash variations
39+
- **Agent-assisted suggestions**: hands the broken link to the Copilot CLI agent (a lightweight,
40+
low-token `gpt-5-mini` prompt with no tools) for replacement candidates; if the CLI is missing or
41+
errors, it simply offers none
42+
- **SEO audit**: flags anchor text that is too generic to benefit search ranking
43+
- **Large-file guard**: prompts before checking files with more than 50 links
44+
- **Interactive fix menu**: replace with suggestion, enter custom URL, strip tag keeping text, or
45+
skip
46+
- **Standard tools only**: `curl`, `grep`, `sed` — present on any POSIX system
47+
48+
## Installation
49+
50+
1. Copy the hook folder to your repository:
51+
52+
```bash
53+
cp -r hooks/fix-broken-links .github/hooks/
54+
```
55+
56+
2. Make the script executable:
57+
58+
```bash
59+
chmod +x .github/hooks/fix-broken-links/link-fix.sh
60+
```
61+
62+
3. Commit the hook configuration to your repository's default branch.
63+
64+
## Configuration
65+
66+
The hook is configured in `hooks.json` to run on the `postToolUse` event:
67+
68+
```json
69+
{
70+
"version": 1,
71+
"hooks": {
72+
"postToolUse": [
73+
{
74+
"type": "command",
75+
"bash": ".github/hooks/fix-broken-links/link-fix.sh",
76+
"powershell": ".github/hooks/fix-broken-links/link-fix.ps1",
77+
"cwd": ".",
78+
"timeoutSec": 120
79+
}
80+
]
81+
}
82+
}
83+
```
84+
85+
## Supported Source Types
86+
87+
Links are found by scanning each file for `http(s)://` URLs, so the same logic
88+
covers every format that embeds absolute URLs:
89+
90+
| Source | Examples matched |
91+
| --- | --- |
92+
| HTML | `<a href>`, `<img src>`, `<script src>`, `<link href>`, `<iframe src>` |
93+
| Markdown | `[text](url)`, `[text][ref]`, bare `<url>` |
94+
| JS / TS / Vue / Svelte | `fetch()`, `XMLHttpRequest.open()`, jQuery, axios, `href:`/`url:` props |
95+
| JSON / JSONL | any string value that is an absolute URL |
96+
| CSS | `url(...)` |
97+
| SQL | URL literals in query strings |
98+
| Templates | Jinja2, ERB, EJS, Handlebars, Pug |
99+
100+
The `d` (remove) action understands HTML `<a>` wrappers and Markdown `[text](url)`
101+
links specifically, keeping the visible text. Other source types support
102+
`r` (replace) and `c` (custom) via literal URL substitution.
103+
104+
## Fix Options
105+
106+
For each broken link:
107+
108+
| Key | Action |
109+
| --- | --- |
110+
| `r` | Replace with the suggested URL (a working variation, or an agent-proposed alternative) |
111+
| `d` | Strip the link wrapper, keeping the visible text as plain text |
112+
| `c` | Enter a custom replacement URL |
113+
| `s` | Skip |
114+
115+
## Example Output
116+
117+
```text
118+
Checking 2 link(s) in docs/guide.md ...
119+
BROKEN (404) https://example.com/old-page
120+
121+
------------------------------------------------------------
122+
SEO anchor issues (consider descriptive link text)
123+
docs/guide.md: <a href="https://example.com/old-page">click here</a>
124+
125+
============================================================
126+
fix-broken-links report
127+
============================================================
128+
129+
[1] docs/guide.md
130+
URL : https://example.com/old-page
131+
HTTP: 404
132+
133+
r Replace -> https://example.com/docs/install
134+
1 Replace -> https://example.com/docs/getting-started
135+
d Remove link, keep text
136+
c Custom replacement URL
137+
s Skip
138+
> r
139+
replaced
140+
141+
1 file(s) updated:
142+
docs/guide.md
143+
```
144+
145+
With no file arguments (or when the edited file carries no checkable links) the
146+
hook stops after the broken-link list — the menu above is skipped.
147+
148+
## Requirements
149+
150+
- `curl` — HTTP status checks (the hook exits quietly if absent)
151+
- `grep`, `sed` — link extraction (standard on any POSIX system)
152+
- `jq` — required by the bash hook to parse the postToolUse JSON payload and discover edited files
153+
- Bash 4+ (for `link-fix.sh`); on Windows use Git Bash or WSL, or run the PowerShell 7+ port
154+
`link-fix.ps1`
155+
- `copilot` (GitHub Copilot CLI) — optional; powers the agent-suggested replacements. Without it,
156+
only verified spelling variations are offered
157+
- `git` is used for changed-file discovery; the hook falls back to a full repo scan without it
158+
159+
## File Structure
160+
161+
```
162+
.github/hooks/fix-broken-links/
163+
├── hooks.json GitHub Copilot hook configuration
164+
├── link-fix.sh Bash hook implementation
165+
├── link-fix.ps1 PowerShell 7+ port
166+
└── README.md This file
167+
```
168+
169+
## Limitations
170+
171+
- Only checks absolute `http://` and `https://` URLs; relative paths require a running server
172+
- Dynamic links generated at runtime from database queries are not detectable from source alone
173+
- When `copilot` suggestions are enabled, broken URLs are sent to the Copilot service as prompt input
174+
- Agent-suggested replacements are model proposals and are not verified live; confirm each before
175+
accepting
176+
- The `d` (remove) action targets HTML and Markdown link syntax; bare URLs in code are best handled
177+
with `r` or `c`

hooks/fix-broken-links/hooks.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"version": 1,
3+
"hooks": {
4+
"postToolUse": [
5+
{
6+
"type": "command",
7+
"bash": ".github/hooks/fix-broken-links/link-fix.sh",
8+
"powershell": ".github/hooks/fix-broken-links/link-fix.ps1",
9+
"cwd": ".",
10+
"timeoutSec": 120
11+
}
12+
]
13+
}
14+
}

0 commit comments

Comments
 (0)