diff --git a/scripts/prerender-routes.js b/scripts/prerender-routes.js index 8b73270..1f1e884 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', @@ -168,7 +175,7 @@ function prerenderRoute(shell, route) { fs.mkdirSync(outDir, { recursive: true }) fs.writeFileSync( outFile, - ``, + ``, 'utf-8' ) return 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)}

    +
    + +
    +
    ${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)) {