From 733a2138c1335d0a0731d796bfae85c656795ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=7BAI=7Df=20D=2E=20M=C3=BCller?= Date: Thu, 30 Apr 2026 12:12:40 +0200 Subject: [PATCH 1/2] feat: pre-render /contracts page for SEO and LLM discoverability (#450) Add build-time HTML generation for the contracts page so crawlers and LLMs can read the content without executing JavaScript. - New script render-contracts.js: reads contracts.json, generates static HTML fragment with all contract titles, descriptions, templates, and anchor links - Chained from render-docs.js (runs during prebuild) - Registered in prerender-routes.js with SEO metadata The interactive features (checkbox selection, download) remain JS-only as progressive enhancement. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/prerender-routes.js | 7 +++ scripts/render-contracts.js | 89 +++++++++++++++++++++++++++++++++++++ scripts/render-docs.js | 3 ++ 3 files changed, 99 insertions(+) create mode 100644 scripts/render-contracts.js diff --git a/scripts/prerender-routes.js b/scripts/prerender-routes.js index 8b73270..2c555c4 100644 --- a/scripts/prerender-routes.js +++ b/scripts/prerender-routes.js @@ -58,6 +58,13 @@ const ROUTES = [ description: 'Applying semantic anchors to brownfield codebases using a bounded-context approach.', }, + { + path: '/contracts', + fragment: 'docs/contracts.html', + title: 'Semantic Contracts — Semantic Anchors', + description: + 'Semantic Contracts define what terms mean in your project — composing established anchors or custom definitions for your AGENTS.md or CLAUDE.md.', + }, { path: '/changelog', fragment: 'docs/changelog.html', diff --git a/scripts/render-contracts.js b/scripts/render-contracts.js new file mode 100644 index 0000000..94a4d82 --- /dev/null +++ b/scripts/render-contracts.js @@ -0,0 +1,89 @@ +#!/usr/bin/env node +/** + * Render a static HTML fragment from contracts.json for pre-rendering. + * + * The contracts page is JS-interactive (checkboxes, download), but crawlers + * and LLMs need to see the content without executing JavaScript. + * This script generates a plain HTML summary of all contracts that + * prerender-routes.js injects into the static shell. + * + * Output: website/public/docs/contracts.html + * + * Usage: node scripts/render-contracts.js + */ + +const fs = require('fs') +const path = require('path') + +const ROOT = path.join(__dirname, '..') +const CONTRACTS_JSON = path.join(ROOT, 'website/public/data/contracts.json') +const OUTPUT = path.join(ROOT, 'website/public/docs/contracts.html') + +function escapeHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} + +function renderTemplate(template) { + return template + .split('\n') + .map((line) => { + if (line.startsWith('- ')) { + return `
  • ${escapeHtml(line.slice(2))}
  • ` + } + if (line.trim() === '') return '' + return `

    ${escapeHtml(line)}

    ` + }) + .join('\n') +} + +function renderContract(contract) { + const anchors = contract.anchors + .map( + (id) => + `${escapeHtml(id)}` + ) + .join(' ') + + return ` +
    +

    ${escapeHtml(contract.title)}

    +

    ${escapeHtml(contract.description)}

    +
    +
      + ${renderTemplate(contract.template)} +
    +
    +
    ${anchors}
    +
    ` +} + +function main() { + if (!fs.existsSync(CONTRACTS_JSON)) { + console.error(`ERROR: ${CONTRACTS_JSON} not found`) + process.exit(1) + } + + const contracts = JSON.parse(fs.readFileSync(CONTRACTS_JSON, 'utf-8')) + + const html = ` +

    Semantic Contracts

    +

    + Semantic Anchors reference public knowledge that LLMs already understand. + But your team's conventions, templates, and definitions need Semantic Contracts. + A contract defines what a term means in your project — either by composing + established anchors or by providing custom definitions that only exist within your team. +

    +
    + ${contracts.map(renderContract).join('\n')} +
    ` + + fs.mkdirSync(path.dirname(OUTPUT), { recursive: true }) + fs.writeFileSync(OUTPUT, html, 'utf-8') + console.log(`Rendered: ${path.relative(ROOT, OUTPUT)}`) +} + +main() diff --git a/scripts/render-docs.js b/scripts/render-docs.js index 3d35f57..4c4a8f3 100644 --- a/scripts/render-docs.js +++ b/scripts/render-docs.js @@ -102,6 +102,9 @@ renderFile( path.join(WEB_DOCS, 'spec-driven-workflow.de.html') ) +// Render contracts page from JSON +require('./render-contracts.js') + // Copy evaluation report (self-contained HTML) const evalReport = path.join(ROOT, 'evaluations/report.html') if (fs.existsSync(evalReport)) { From 0f642434350a88566b976ac3367eee3da3102c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=7BAI=7Df=20D=2E=20M=C3=BCller?= Date: Thu, 30 Apr 2026 12:14:31 +0200 Subject: [PATCH 2/2] fix: include /Semantic-Anchors base path in static redirect URL The redirect page for /workflow used a bare /spec-driven-development path, which resolved to the root domain instead of the GitHub Pages subdirectory. Now uses /Semantic-Anchors/spec-driven-development. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/prerender-routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prerender-routes.js b/scripts/prerender-routes.js index 2c555c4..1f1e884 100644 --- a/scripts/prerender-routes.js +++ b/scripts/prerender-routes.js @@ -175,7 +175,7 @@ function prerenderRoute(shell, route) { fs.mkdirSync(outDir, { recursive: true }) fs.writeFileSync( outFile, - ``, + ``, 'utf-8' ) return