All hardcoded 'clawdie' references in production code now derive from AGENT_NAME (default: 'clawdie'). This makes the mevy canary strategy reliable — changing AGENT_NAME is all that's needed. Changes: - Hardcoded paths: CMS_WEBROOT, ASTRO_SITE_PATH, verify checks, controlplane dashboard dir, sessions dir, output dir, chown user - Prometheus metrics: prefixed with AGENT_NAME for multi-install dashboards - hostd log strings: use AGENT_NAME instead of 'clawdie-hostd' - MCP server name: derived from AGENT_NAME - Skill modify patches: container image and mount allowlist use AGENT_NAME - SQL migration file renamed: clawdie-brain-hybrid-upgrade → brain-hybrid-upgrade - Temp dir prefixes: all use AGENT_NAME Kept as-is (correct pattern): - 'clawdie' as default fallback when AGENT_NAME is unset - .pi/extensions/clawdie-harness/ directory (pi package identity) - html/docs-clawdie-si/ (public docs site URL) --- Build: pass | Tests: pass — 1527 passed, 3 failed (2 files, pre-existing)
395 lines
13 KiB
TypeScript
395 lines
13 KiB
TypeScript
/**
|
|
* Generate changelog HTML from annotated git tags.
|
|
*
|
|
* Usage:
|
|
* npm run gen-changelog
|
|
* npx tsx scripts/gen-changelog.ts
|
|
*
|
|
* Run after each release tag:
|
|
* git tag -a v0.6.0 -m "v0.6.0 - Release Name\n\n- key change\n- key change"
|
|
* npm run gen-changelog
|
|
* git add html/docs-clawdie-si/changelog.html && git commit -m "docs: regenerate changelog for v0.6.0"
|
|
*/
|
|
|
|
import { execSync } from 'child_process';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
import { formatDisplayDate } from '../src/display-date.js';
|
|
|
|
const OUT_FILE = path.join(
|
|
process.cwd(),
|
|
'html/docs-clawdie-si/changelog.html',
|
|
);
|
|
|
|
function run(cmd: string): string {
|
|
try {
|
|
return execSync(cmd, {
|
|
encoding: 'utf-8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
}).trim();
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
interface Commit {
|
|
hash: string;
|
|
message: string;
|
|
}
|
|
|
|
interface Release {
|
|
tag: string;
|
|
version: string;
|
|
name: string;
|
|
date: string;
|
|
body: string;
|
|
commits: Commit[];
|
|
}
|
|
|
|
function getReleases(): Release[] {
|
|
const tagList = run('git tag --sort=-version:refname')
|
|
.split('\n')
|
|
.filter(Boolean);
|
|
if (!tagList.length) {
|
|
console.log('No git tags found. Tag a release first:');
|
|
console.log(' git tag -a v0.5.0 -m "v0.5.0 - Release Name"');
|
|
process.exit(0);
|
|
}
|
|
|
|
return tagList.map((tag, i): Release => {
|
|
const prev = tagList[i + 1] ?? '';
|
|
|
|
// Date of the tag commit
|
|
const date = formatDisplayDate(run(`git log -1 --format=%aI "${tag}"`), {
|
|
includeTime: false,
|
|
});
|
|
|
|
// Annotated tag subject and body (lightweight tags return empty subject)
|
|
const subject = run(`git tag -l --format='%(subject)' "${tag}"`);
|
|
const body = run(`git tag -l --format='%(body)' "${tag}"`);
|
|
|
|
// Strip leading version prefix "v0.5.0 - " or "v0.5.0: "
|
|
const name = subject.replace(/^v?\d+\.\d+\.\d+[-:\s]+/, '').trim() || tag;
|
|
|
|
// Commits included in this release
|
|
const range = prev ? `${prev}..${tag}` : tag;
|
|
const lines = run(`git log ${range} --oneline --no-merges`)
|
|
.split('\n')
|
|
.filter(Boolean);
|
|
const commits: Commit[] = lines.map((line) => {
|
|
const sp = line.indexOf(' ');
|
|
return { hash: line.slice(0, sp), message: line.slice(sp + 1) };
|
|
});
|
|
|
|
return { tag, version: tag.replace(/^v/, ''), name, date, body, commits };
|
|
});
|
|
}
|
|
|
|
// Parse conventional commit prefix
|
|
function parseType(message: string): {
|
|
type: string;
|
|
scope: string;
|
|
rest: string;
|
|
} {
|
|
const m = message.match(
|
|
/^(feat|fix|docs|design|chore|refactor|perf|test|ci|build|style)(\(([^)]+)\))?!?:\s*(.*)/,
|
|
);
|
|
if (m) return { type: m[1], scope: m[3] ?? '', rest: m[4] };
|
|
return { type: '', scope: '', rest: message };
|
|
}
|
|
|
|
const TYPE_COLOR: Record<string, string> = {
|
|
feat: '#4ade80',
|
|
fix: '#f87171',
|
|
design: '#c8922a',
|
|
docs: '#00b4d8',
|
|
refactor: '#a78bfa',
|
|
chore: '#6e7d8f',
|
|
build: '#6e7d8f',
|
|
ci: '#6e7d8f',
|
|
perf: '#fcd34d',
|
|
test: '#6e7d8f',
|
|
style: '#6e7d8f',
|
|
};
|
|
|
|
function esc(s: string): string {
|
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
}
|
|
|
|
function renderCommitList(commits: Commit[]): string {
|
|
if (!commits.length)
|
|
return '<p style="color:var(--muted);font-size:0.85rem;margin:0.8rem 0">No commits in this range.</p>';
|
|
return `<ul class="cl-commits">\n${commits
|
|
.map((c) => {
|
|
const { type, scope, rest } = parseType(c.message);
|
|
const typeHtml = type
|
|
? `<span class="cl-type" style="color:${TYPE_COLOR[type] ?? '#6e7d8f'};border-color:${TYPE_COLOR[type] ?? '#6e7d8f'}33">${type}${scope ? `(${esc(scope)})` : ''}</span>`
|
|
: '';
|
|
return ` <li><span class="cl-hash">${esc(c.hash)}</span>${typeHtml}${esc(rest)}</li>`;
|
|
})
|
|
.join('\n')}\n </ul>`;
|
|
}
|
|
|
|
function renderSections(releases: Release[]): string {
|
|
return releases
|
|
.map(
|
|
(r, i) => ` <section>
|
|
<div class="cl-release">
|
|
<div class="cl-release-header">
|
|
<span class="cl-version">${esc(r.tag)}</span>
|
|
<span class="cl-release-name">${esc(r.name)}</span>
|
|
<span class="cl-date">${esc(r.date)}</span>
|
|
</div>
|
|
${r.body ? `<p class="cl-body">${esc(r.body).replace(/\n/g, '<br>')}</p>` : ''}
|
|
${renderCommitList(r.commits)}
|
|
</div>
|
|
</section>${i < releases.length - 1 ? '\n\n <div class="divider"></div>' : ''}`,
|
|
)
|
|
.join('\n\n');
|
|
}
|
|
|
|
function buildHtml(releases: Release[]): string {
|
|
const generated = formatDisplayDate(new Date(), { includeTime: false });
|
|
|
|
return `<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Changelog — Clawdie Docs</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=DM+Mono:wght@300;400&display=swap"
|
|
rel="stylesheet"
|
|
/>
|
|
<link rel="stylesheet" href="/css/shared.css" />
|
|
<style>
|
|
.cl-release-header {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 1.2rem;
|
|
padding-bottom: 0.8rem;
|
|
border-bottom: 1px solid var(--grey-light, #e5e5e5);
|
|
}
|
|
.cl-version {
|
|
font-family: 'DM Mono', monospace;
|
|
font-size: 1rem;
|
|
color: var(--amber, #b58900);
|
|
letter-spacing: 0.06em;
|
|
flex-shrink: 0;
|
|
}
|
|
.cl-release-name {
|
|
font-family: 'Cormorant Garamond', Georgia, serif;
|
|
font-size: 1.5rem;
|
|
font-style: italic;
|
|
font-weight: 300;
|
|
color: var(--charcoal, #333);
|
|
line-height: 1.2;
|
|
}
|
|
.cl-date {
|
|
font-family: 'DM Mono', monospace;
|
|
font-size: 0.6rem;
|
|
letter-spacing: 0.12em;
|
|
color: var(--muted, #888);
|
|
text-transform: uppercase;
|
|
margin-left: auto;
|
|
}
|
|
.cl-body {
|
|
font-size: 0.9rem;
|
|
color: var(--grey, #666);
|
|
font-style: italic;
|
|
margin-bottom: 1rem;
|
|
line-height: 1.7;
|
|
}
|
|
.cl-commits {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
.cl-commits li {
|
|
padding: 0.42rem 0;
|
|
border-bottom: 1px solid var(--grey-light, #e5e5e5);
|
|
font-size: 0.87rem;
|
|
font-weight: 300;
|
|
color: var(--ink, #222);
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
line-height: 1.5;
|
|
}
|
|
.cl-hash {
|
|
font-family: 'DM Mono', monospace;
|
|
font-size: 0.65rem;
|
|
color: var(--muted, #888);
|
|
flex-shrink: 0;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
.cl-type {
|
|
font-family: 'DM Mono', monospace;
|
|
font-size: 0.57rem;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
padding: 0.08rem 0.4rem;
|
|
flex-shrink: 0;
|
|
border: 1px solid;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="hex-bg"></div>
|
|
<div class="sidebar-overlay" id="overlay"></div>
|
|
|
|
<header class="top-bar">
|
|
<button class="mobile-menu-btn" id="menuBtn" aria-label="Toggle menu">☰</button>
|
|
<a href="/" class="brand"><span>△</span> Clawdie Docs</a>
|
|
<div class="nav-links">
|
|
<a href="https://clawdie.si">Home</a>
|
|
<a href="https://codeberg.org/Clawdie" target="_blank" rel="noopener">Source</a>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="docs-layout">
|
|
<nav class="sidebar" id="sidebar">
|
|
<div class="sidebar-section">
|
|
<span class="section-label">Getting Started</span>
|
|
<ul>
|
|
<li><a href="/">Introduction</a></li>
|
|
<li><a href="/docs/install.html">Installation</a></li>
|
|
<li><a href="/docs/iso.html">ISO Install</a></li>
|
|
<li><a href="/docs/split-brain.html">Split Brain</a></li>
|
|
</ul>
|
|
</div>
|
|
<div class="sidebar-section">
|
|
<span class="section-label">Architecture</span>
|
|
<ul>
|
|
<li><a href="/docs/">How It Works</a></li>
|
|
<li><a href="/docs/#jails-not-docker">Jails, Not Docker</a></li>
|
|
<li><a href="/docs/#wayland-first-display">Wayland Display</a></li>
|
|
<li><a href="/docs/#prompt-injection-and-web-browsing">Prompt Injection</a></li>
|
|
<li><a href="/guides/nanoclaw-upstream.html">NanoClaw Upstream</a></li>
|
|
<li><a href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/docs/public/operate/monitoring.md" target="_blank" rel="noopener">Monitoring</a></li>
|
|
<li><a href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/docs/public/operate/security.md" target="_blank" rel="noopener">Security</a></li>
|
|
</ul>
|
|
</div>
|
|
<div class="sidebar-section">
|
|
<span class="section-label">Setup Guides</span>
|
|
<ul>
|
|
<li><a href="/guides/nginx-ssl.html">Nginx + SSL</a></li>
|
|
<li><a href="/guides/tailscale-vpn.html">Tailscale VPN</a></li>
|
|
</ul>
|
|
</div>
|
|
<div class="sidebar-section">
|
|
<span class="section-label">Integrations</span>
|
|
<ul>
|
|
<li><a href="/guides/stripe-agents.html">Stripe Agents</a></li>
|
|
<li><a href="/guides/protonmail.html">ProtonMail</a></li>
|
|
</ul>
|
|
</div>
|
|
<div class="sidebar-section">
|
|
<span class="section-label">Project</span>
|
|
<ul>
|
|
<li><a href="/changelog.html" class="active">Changelog</a></li>
|
|
<li><a href="/license.html">License</a></li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
|
|
<main class="content">
|
|
<div class="breadcrumb">
|
|
<a href="/">Home</a><span class="sep">/</span>
|
|
Changelog
|
|
</div>
|
|
|
|
<div class="page-header">
|
|
<h1>Change<span>log</span></h1>
|
|
<p class="subtitle">Tagged releases — what changed and why</p>
|
|
</div>
|
|
|
|
<p>
|
|
Annotated git tags, regenerated with <code>npm run gen-changelog</code> on each release.
|
|
Full history at <a href="https://codeberg.org/Clawdie/Clawdie-AI/commits/branch/main" target="_blank" rel="noopener">Codeberg</a>.
|
|
</p>
|
|
|
|
<div class="divider"></div>
|
|
|
|
${renderSections(releases)}
|
|
|
|
<footer>
|
|
<div class="footer-left">
|
|
<a href="https://clawdie.si">Clawdie AI</a> ·
|
|
<a href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/html/docs-clawdie-si/changelog.html" target="_blank" rel="noopener">Page source</a><br>
|
|
<a href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/CHANGELOG.md" target="_blank" rel="noopener">Release notes</a>
|
|
·
|
|
<a href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/README.md" target="_blank" rel="noopener">Project context</a><br>
|
|
<a href="https://osa.smilepowered.org" target="_blank" rel="noopener">OSA — Mission Statement</a><br>
|
|
Generated ${generated}
|
|
</div>
|
|
<div class="footer-hex">△</div>
|
|
</footer>
|
|
</main>
|
|
|
|
<aside class="toc">
|
|
<p class="toc-title">On this page</p>
|
|
<nav id="toc-list"></nav>
|
|
</aside>
|
|
</div>
|
|
|
|
<script>
|
|
const toc = document.getElementById('toc-list');
|
|
if (toc) {
|
|
document.querySelectorAll('.content h2, .content h3').forEach((h) => {
|
|
if (!h.id)
|
|
h.id = h.textContent.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
const a = document.createElement('a');
|
|
a.href = '#' + h.id;
|
|
a.className = 'toc-link' + (h.tagName === 'H3' ? ' toc-sub' : '');
|
|
a.textContent = h.textContent;
|
|
toc.appendChild(a);
|
|
});
|
|
}
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((e, i) => {
|
|
if (e.isIntersecting)
|
|
setTimeout(() => e.target.classList.add('visible'), i * 80);
|
|
});
|
|
},
|
|
{ threshold: 0.08 },
|
|
);
|
|
document.querySelectorAll('section').forEach((s) => observer.observe(s));
|
|
|
|
const menuBtn = document.getElementById('menuBtn');
|
|
const sidebar = document.getElementById('sidebar');
|
|
const overlay = document.getElementById('overlay');
|
|
if (menuBtn && sidebar) {
|
|
menuBtn.addEventListener('click', () => {
|
|
sidebar.classList.toggle('open');
|
|
overlay?.classList.toggle('open');
|
|
});
|
|
overlay?.addEventListener('click', () => {
|
|
sidebar.classList.remove('open');
|
|
overlay.classList.remove('open');
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`;
|
|
}
|
|
|
|
const releases = getReleases();
|
|
const html = buildHtml(releases);
|
|
fs.writeFileSync(OUT_FILE, html);
|
|
console.log(
|
|
`✓ ${OUT_FILE} — ${releases.length} release${releases.length !== 1 ? 's' : ''}`,
|
|
);
|
|
releases.forEach((r) =>
|
|
console.log(
|
|
` ${r.tag} ${r.date} ${r.commits.length} commit(s) — ${r.name}`,
|
|
),
|
|
);
|