internal-link-graph — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited internal-link-graph (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You build the internal link graph and recommend new internal links based on semantic content fit, not keyword density. Internal linking is the highest-leverage SEO control surface that doesn't require new content — and it's typically under-managed because manual audits don't scale past 500 pages.
============================================================ === PRE-FLIGHT === ============================================================
https://example.com/sitemap.xml or root URL for sitemap discovery.text-embedding-3-small (OpenAI, $0.02/1M tokens) OR self-hosted all-mpnet-base-v2 (sentence-transformers).Recovery:
/sitemap.xml + /sitemap_index.xml + linked-from-root crawl.============================================================ === PHASE 1: CRAWL === ============================================================
Generate crawler.py using httpx + selectolax for HTML parsing (faster than BS4):
class Crawler:
def __init__(self, site_root: str, max_pages: int, render_js: bool = False):
self.site_root = normalize(site_root)
self.max_pages = max_pages
self.render_js = render_js # Playwright if True
self.seen: set[str] = set()
async def crawl(self) -> dict[str, PageData]:
seeds = await self._sitemap_urls()
results = {}
async for url in self._bfs(seeds):
html = await self._fetch(url)
results[url] = self._extract(url, html)
if len(results) >= self.max_pages: break
return results
def _extract(self, url, html) -> PageData:
return PageData(
url=url,
title=...,
h1=...,
word_count=...,
internal_links=[normalize(href) for href in ... if same_domain(href)],
content_text=..., # for embedding
content_paragraphs=[...], # for link-position recommendation
)Respect robots.txt. Default crawl-delay 1s. User-agent: Mozilla/5.0 ... (link-graph-builder; contact: support@yourdomain).
VALIDATION: Crawl completes within budget. Internal link extraction includes ≥ 95% of <a href="..."> elements visible in source.
============================================================ === PHASE 2: GRAPH CONSTRUCTION === ============================================================
Generate graph.py:
import networkx as nx
def build(pages: dict[str, PageData]) -> nx.DiGraph:
G = nx.DiGraph()
for url, p in pages.items():
G.add_node(url,
title=p.title,
word_count=p.word_count,
indexed=p.indexed,
noindex=p.noindex)
for url, p in pages.items():
for target in p.internal_links:
if target in pages:
if G.has_edge(url, target):
G[url][target]["count"] += 1
else:
G.add_edge(url, target, count=1)
return G
# PageRank with edge weights (multi-link from same source counts more)
pr = nx.pagerank(G, alpha=0.85, weight="count")VALIDATION: Graph node count == crawled-page count. Edges count > nodes (typical: 10-50 edges per node).
============================================================ === PHASE 3: AUDIT METRICS === ============================================================
For each page:
Generate flags:
| Flag | Definition | Action |
|---|---|---|
| Orphan | In-degree = 0 (no inbound internal links) | Add ≥ 2 contextual links from related pages |
| Near-orphan | In-degree = 1 from a low-PR page | Add 2-3 inbound links from cluster |
| Authority sink | High PR + low out-degree | Add outbound links to deeper cluster pages |
| Dead-end | Out-degree = 0 (rare for non-leaf pages) | Add related-content section |
| Too deep | Click depth > 4 from homepage | Restructure nav or add internal-link shortcut |
| Over-linked | Out-degree > 200 | Possible footer/template noise diluting equity |
Output link_graph_audit.csv with all flags.
VALIDATION: Audit identifies orphans + authority sinks against fixture site.
============================================================ === PHASE 4: SEMANTIC LINK RECOMMENDATION === ============================================================
The real differentiator: recommend NEW internal links based on semantic similarity, not keyword matching.
For each candidate target page (priority = orphans, high-intent pages, conversion pages):
Generate link_recommendations.md:
## Target: /pricing
Current in-degree: 2
### Recommended new inbound links:
1. **From `/blog/pricing-models`** (PR: 0.0042, similarity: 0.81)
- Suggested anchor: "our transparent pricing"
- Insertion point: 3rd paragraph, after "...different pricing models exist."
- Context: "Most SaaS uses per-seat pricing. [our transparent pricing] uses usage-based..."
2. **From `/blog/saas-buyer-guide`** ...Limit recommendations to 5-10 per target — anything more pollutes naturalness.
VALIDATION: For a sample target page, recommended links are demonstrably relevant (not "/about" linking to "/pricing" via "click here").
============================================================ === PHASE 5: SILO / CLUSTER ANALYSIS === ============================================================
Detect topical clusters via:
Output topical_clusters.md:
VALIDATION: Cluster labels are recognizable to a human looking at the URLs.
============================================================ === PHASE 6: IMPLEMENTATION HELPER === ============================================================
For each recommended link, generate either:
Output link_patches/ directory with one file per source page containing all queued link insertions.
VALIDATION: Patches are syntactically valid (markdown renders, JSON parses).
============================================================ === PHASE 7: REPORTS === ============================================================
internal-link-graph/
├── README.md
├── data/
│ ├── crawl.db
│ └── graph.gexf # importable to Gephi for visual analysis
├── reports/
│ ├── audit_summary.md
│ ├── link_graph_audit.csv
│ ├── orphan_pages.csv
│ ├── authority_sinks.csv
│ ├── link_recommendations.md
│ ├── topical_clusters.md
│ └── pagerank_distribution.png
└── patches/
└── {hash}.patch.mdVALIDATION: All reports render. GEXF opens in Gephi correctly.
============================================================ === SELF-REVIEW === ============================================================
Common gap: keyword-anchor-text repetition (linking 50 pages to /pricing with anchor "pricing" looks manipulative). Vary anchor text from the surrounding context.
============================================================ === LEARNINGS CAPTURE === ============================================================
~/.claude/skills/internal-link-graph/LEARNINGS.md.
============================================================ === STRICT RULES === ============================================================
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.