---
name: pr-gfm-validator
description: Validate and fix GFM links in PR descriptions. TRIGGERS - PR links, gh pr create, GFM validation, broken PR links.
allowed-tools: Bash, Read, Grep
---

# PR GFM Link Validator

Validate and auto-convert GFM links in pull request descriptions to prevent 404 errors.

> **Self-Evolving Skill**: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.

## When to Use This Skill

This skill triggers when:

- Creating a pull request from a feature branch
- Discussing PR descriptions or body content
- Mentioning GFM links, PR links, or link validation
- Using `gh pr create` or `gh pr edit`

## The Problem

Repository-relative links in PR descriptions resolve to the **base branch** (main), not the feature branch:

| Link in PR Body            | GitHub Resolves To            | Result                            |
| -------------------------- | ----------------------------- | --------------------------------- |
| `[ADR](/docs/adr/file.md)` | `/blob/main/docs/adr/file.md` | 404 (file only on feature branch) |

## The Solution

Convert repo-relative links to absolute blob URLs with the correct branch:

```
/docs/adr/file.md
    ↓
https://github.com/{owner}/{repo}/blob/{branch}/docs/adr/file.md
```

---

## Workflow

### Step 1: Detect Context

Before any PR operation, gather repository context:

```bash
/usr/bin/env bash << 'PREFLIGHT_EOF'
# Get repo owner and name
gh repo view --json nameWithOwner --jq '.nameWithOwner'

# Get current branch
git rev-parse --abbrev-ref HEAD

# Check if on feature branch (not main/master)
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then
  echo "On default branch - no conversion needed"
  exit 0
fi
PREFLIGHT_EOF
```

### Step 2: Identify Links to Convert

Scan PR body for GFM links matching these patterns:

**CONVERT these patterns:**

- `/path/to/file.md` - Repo-root relative
- `./relative/path.md` - Current-directory relative
- `../parent/path.md` - Parent-directory relative

**SKIP these patterns:**

- `https://...` - Already absolute URLs
- `http://...` - Already absolute URLs
- `#anchor` - In-page anchors
- `mailto:...` - Email links

### Step 3: Construct Blob URLs

For each link to convert:

```python
# Pattern
f"https://github.com/{owner}/{repo}/blob/{branch}/{path}"

# Example
owner = "Eon-Labs"
repo = "alpha-forge"
branch = "feat/2025-12-01-eth-block-metrics"
path = "docs/adr/2025-12-01-file.md"

# Result
"https://github.com/Eon-Labs/alpha-forge/blob/feat/2025-12-01-eth-block-metrics/docs/adr/2025-12-01-file.md"
```

### Step 4: Apply Conversions

Replace all identified links in the PR body:

```markdown
# Before

[Plugin Design](/docs/adr/2025-12-01-slug.md)

# After

[Plugin Design](https://github.com/Eon-Labs/alpha-forge/blob/feat/branch/docs/adr/2025-12-01-slug.md)
```

### Step 5: Validate Result

After conversion, verify:

1. All repo-relative links are now absolute blob URLs
2. External links remain unchanged
3. Anchor links remain unchanged

---

## Integration with gh pr create

When creating a PR, apply this workflow automatically:

```bash
/usr/bin/env bash << 'GIT_EOF'
# 1. Get context
REPO_INFO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
OWNER=$(echo "$REPO_INFO" | cut -d'/' -f1)
REPO=$(echo "$REPO_INFO" | cut -d'/' -f2)
BRANCH=$(git rev-parse --abbrev-ref HEAD)

# 2. Process PR body (convert links)
# ... link conversion logic ...

# 3. Create PR with converted body
gh pr create --title "..." --body "$CONVERTED_BODY"
GIT_EOF
```

---

## Link Detection Regex

Use this regex pattern to find GFM links:

```regex
\[([^\]]+)\]\((/[^)]+|\.\.?/[^)]+)\)
```

Breakdown:

- `\[([^\]]+)\]` - Capture link text
- `\(` - Opening parenthesis
- `(/[^)]+|\.\.?/[^)]+)` - Capture path starting with `/`, `./`, or `../`
- `\)` - Closing parenthesis

---

## Examples

### Example 1: Simple Repo-Relative Link

**Input:**

```markdown
See the [ADR](/docs/adr/2025-12-01-eth-block-metrics.md) for details.
```

**Context:**

- Owner: `Eon-Labs`
- Repo: `alpha-forge`
- Branch: `feat/2025-12-01-eth-block-metrics-data-plugin`

**Output:**

```markdown
See the [ADR](https://github.com/Eon-Labs/alpha-forge/blob/feat/2025-12-01-eth-block-metrics-data-plugin/docs/adr/2025-12-01-eth-block-metrics.md) for details.
```

### Example 2: Multiple Links

**Input:**

```markdown
## References

- [Plugin Design](/docs/adr/2025-12-01-slug.md)
- [Probe Integration](/docs/adr/2025-12-02-slug.md)
- [External Guide](https://example.com/guide)
```

**Output:**

```markdown
## References

- [Plugin Design](https://github.com/Eon-Labs/alpha-forge/blob/feat/branch/docs/adr/2025-12-01-slug.md)
- [Probe Integration](https://github.com/Eon-Labs/alpha-forge/blob/feat/branch/docs/adr/2025-12-02-slug.md)
- [External Guide](https://example.com/guide)
```

Note: External link unchanged.

### Example 3: Credential File Link

**Input:**

```markdown
**See [`.env.clickhouse`](/.env.clickhouse)** for credentials.
```

**Output:**

```markdown
**See [`.env.clickhouse`](https://github.com/Eon-Labs/alpha-forge/blob/feat/branch/.env.clickhouse)** for credentials.
```

---

## Edge Cases

### Already on main/master

- Skip conversion entirely
- Repo-relative links will work correctly

### Empty PR Body

- Nothing to convert
- Proceed with PR creation

### No GFM Links Found

- Nothing to convert
- Proceed with PR creation

### Mixed Link Types

- Convert only repo-relative links
- Preserve external URLs, anchors, mailto links

---

## Post-Change Checklist

After modifying this skill:

1. [ ] Regex patterns still match intended link formats
2. [ ] Examples reflect current behavior
3. [ ] Edge cases documented
4. [ ] Workflow steps are executable

---

## References

- [GitHub Blob URLs](https://docs.github.com/en/repositories/working-with-files/using-files/getting-permanent-links-to-files)
- [GFM Link Syntax](https://github.github.com/gfm/#links)
- [gh CLI Documentation](https://cli.github.com/manual/gh_pr_create)

---

## Troubleshooting

| Issue                    | Cause                          | Solution                                          |
| ------------------------ | ------------------------------ | ------------------------------------------------- |
| Links still 404 after PR | File not pushed to branch yet  | Push commits before creating PR                   |
| Regex not matching links | Escaped parentheses in content | Use raw string regex pattern                      |
| Branch name has slashes  | URL encoding needed            | Encode branch name for URL construction           |
| External links converted | Pattern too broad              | Check link starts with `/`, `./`, or `../` only   |
| gh repo view fails       | Not in a git repository        | Run from repository root directory                |
| Anchor links broken      | Incorrectly included in scan   | Skip links starting with `#`                      |
| Wrong repo detected      | Remote not set correctly       | Check `git remote -v` output                      |
| Conversion duplicated    | Running validator twice        | Check if links already absolute before converting |


## Post-Execution Reflection

After this skill completes, reflect before closing the task:

0. **Locate yourself.** — Find this SKILL.md's canonical path before editing.
1. **What failed?** — Fix the instruction that caused it.
2. **What worked better than expected?** — Promote to recommended practice.
3. **What drifted?** — Fix any script, reference, or dependency that no longer matches reality.
4. **Log it.** — Evolution-log entry with trigger, fix, and evidence.

Do NOT defer. The next invocation inherits whatever you leave behind.
