Skip to content

Commit 81f2bad

Browse files
authored
Merge pull request #451 from raifdmueller/feat/prerender-contracts
feat: pre-render /contracts page for SEO and LLM discoverability
2 parents e4ac35a + 0f64243 commit 81f2bad

3 files changed

Lines changed: 100 additions & 1 deletion

File tree

scripts/prerender-routes.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ const ROUTES = [
5858
description:
5959
'Applying semantic anchors to brownfield codebases using a bounded-context approach.',
6060
},
61+
{
62+
path: '/contracts',
63+
fragment: 'docs/contracts.html',
64+
title: 'Semantic Contracts — Semantic Anchors',
65+
description:
66+
'Semantic Contracts define what terms mean in your project — composing established anchors or custom definitions for your AGENTS.md or CLAUDE.md.',
67+
},
6168
{
6269
path: '/changelog',
6370
fragment: 'docs/changelog.html',
@@ -168,7 +175,7 @@ function prerenderRoute(shell, route) {
168175
fs.mkdirSync(outDir, { recursive: true })
169176
fs.writeFileSync(
170177
outFile,
171-
`<!doctype html><meta charset="utf-8"><link rel="canonical" href="https://llm-coding.github.io/Semantic-Anchors${route.redirectTo}"><meta http-equiv="refresh" content="0;url=${route.redirectTo}"><script>location.replace('${route.redirectTo}')</script>`,
178+
`<!doctype html><meta charset="utf-8"><link rel="canonical" href="https://llm-coding.github.io/Semantic-Anchors${route.redirectTo}"><meta http-equiv="refresh" content="0;url=/Semantic-Anchors${route.redirectTo}"><script>location.replace('/Semantic-Anchors${route.redirectTo}')</script>`,
172179
'utf-8'
173180
)
174181
return

scripts/render-contracts.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Render a static HTML fragment from contracts.json for pre-rendering.
4+
*
5+
* The contracts page is JS-interactive (checkboxes, download), but crawlers
6+
* and LLMs need to see the content without executing JavaScript.
7+
* This script generates a plain HTML summary of all contracts that
8+
* prerender-routes.js injects into the static shell.
9+
*
10+
* Output: website/public/docs/contracts.html
11+
*
12+
* Usage: node scripts/render-contracts.js
13+
*/
14+
15+
const fs = require('fs')
16+
const path = require('path')
17+
18+
const ROOT = path.join(__dirname, '..')
19+
const CONTRACTS_JSON = path.join(ROOT, 'website/public/data/contracts.json')
20+
const OUTPUT = path.join(ROOT, 'website/public/docs/contracts.html')
21+
22+
function escapeHtml(str) {
23+
return String(str)
24+
.replace(/&/g, '&amp;')
25+
.replace(/</g, '&lt;')
26+
.replace(/>/g, '&gt;')
27+
.replace(/"/g, '&quot;')
28+
}
29+
30+
function renderTemplate(template) {
31+
return template
32+
.split('\n')
33+
.map((line) => {
34+
if (line.startsWith('- ')) {
35+
return `<li>${escapeHtml(line.slice(2))}</li>`
36+
}
37+
if (line.trim() === '') return ''
38+
return `<p>${escapeHtml(line)}</p>`
39+
})
40+
.join('\n')
41+
}
42+
43+
function renderContract(contract) {
44+
const anchors = contract.anchors
45+
.map(
46+
(id) =>
47+
`<a href="#/anchor/${escapeHtml(id)}" class="inline-block rounded-full bg-blue-100 dark:bg-blue-900/30 px-2 py-0.5 text-xs text-blue-700 dark:text-blue-300">${escapeHtml(id)}</a>`
48+
)
49+
.join(' ')
50+
51+
return `
52+
<div class="rounded-lg border border-[var(--color-border)] bg-[var(--color-bg)] p-5 mb-4">
53+
<h3 class="text-lg font-semibold mb-1">${escapeHtml(contract.title)}</h3>
54+
<p class="text-sm text-[var(--color-text-secondary)] mb-3">${escapeHtml(contract.description)}</p>
55+
<div class="rounded-md bg-[var(--color-bg-secondary)] p-3 mb-3 text-sm leading-relaxed">
56+
<ul class="list-disc list-inside space-y-1">
57+
${renderTemplate(contract.template)}
58+
</ul>
59+
</div>
60+
<div class="flex flex-wrap gap-1.5">${anchors}</div>
61+
</div>`
62+
}
63+
64+
function main() {
65+
if (!fs.existsSync(CONTRACTS_JSON)) {
66+
console.error(`ERROR: ${CONTRACTS_JSON} not found`)
67+
process.exit(1)
68+
}
69+
70+
const contracts = JSON.parse(fs.readFileSync(CONTRACTS_JSON, 'utf-8'))
71+
72+
const html = `
73+
<h1>Semantic Contracts</h1>
74+
<p class="text-[var(--color-text-secondary)] mb-6">
75+
Semantic Anchors reference public knowledge that LLMs already understand.
76+
But your team's conventions, templates, and definitions need Semantic Contracts.
77+
A contract defines what a term means in your project — either by composing
78+
established anchors or by providing custom definitions that only exist within your team.
79+
</p>
80+
<div class="grid gap-4 md:grid-cols-2">
81+
${contracts.map(renderContract).join('\n')}
82+
</div>`
83+
84+
fs.mkdirSync(path.dirname(OUTPUT), { recursive: true })
85+
fs.writeFileSync(OUTPUT, html, 'utf-8')
86+
console.log(`Rendered: ${path.relative(ROOT, OUTPUT)}`)
87+
}
88+
89+
main()

scripts/render-docs.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ renderFile(
102102
path.join(WEB_DOCS, 'spec-driven-workflow.de.html')
103103
)
104104

105+
// Render contracts page from JSON
106+
require('./render-contracts.js')
107+
105108
// Copy evaluation report (self-contained HTML)
106109
const evalReport = path.join(ROOT, 'evaluations/report.html')
107110
if (fs.existsSync(evalReport)) {

0 commit comments

Comments
 (0)