fix(wiki): agent harness title — pi, zot & Colibri (not just zot + Colibri)

The agent harness page describes THREE agents: pi (fallback), zot (default),
and Colibri (supervisor). Title updated in both EN and SL.

Also: H1 extraction fallback for pages without YAML frontmatter —
content.match(/^#\s+(.+)$/m)?.[1] so pages with only markdown H1 still
get a proper <title> tag instead of the slug.
This commit is contained in:
Sam & Claude 2026-06-26 12:34:15 +02:00
parent 29796ab102
commit 834197e2ae
4 changed files with 41 additions and 64 deletions

View file

@ -2,10 +2,9 @@
import fs from "node:fs";
import path from "node:path";
const WIKI_DIR = path.resolve("../../docs/wiki");
const EXCLUDE = [".git", "index.md"];
export function getStaticPaths() {
const WIKI_DIR = path.resolve("src/content");
const EXCLUDE = [".git", "index.md"];
function walk(dir, prefix = "") {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const slugs = [];
@ -25,6 +24,7 @@ export function getStaticPaths() {
}
const { slug } = Astro.params;
const WIKI_DIR = path.resolve("src/content");
const filePath = path.join(WIKI_DIR, `${slug}.md`);
if (!fs.existsSync(filePath)) {
@ -33,7 +33,6 @@ if (!fs.existsSync(filePath)) {
const raw = fs.readFileSync(filePath, "utf-8");
// Parse frontmatter
let content = raw;
let frontmatter = {};
if (raw.startsWith("---")) {
@ -48,46 +47,28 @@ if (raw.startsWith("---")) {
}
}
// Detect locale from slug prefix
const isSl = slug.startsWith("sl/");
const locale = isSl ? "sl" : "en";
const base = isSl ? "/sl/" : "/";
const title = (frontmatter.title || content.match(/^#\s+(.+)$/m)?.[1] || slug).replace(/^["']|["']$/g, "");
// Resolve relative wiki links with locale prefix
// ./page.md → /page/ or /sl/page/
// ../packaging/x → /../packaging/x (pass through absolute-ish paths)
const resolveLinks = (md) =>
md.replace(/\]\(\.\/([^)]+)\.md\)/g, `](${base}$1/)`)
.replace(/\]\(\.\.\/([^)]+)\)/g, "](/$1)");
// Resolve wiki links [label](./page.md) → [/page/]
content = content.replace(/\]\(\.\/([^)]+)\.md\)/g, "](/$1/)")
.replace(/\]\(\.\.\/([^)]+)\.md\)/g, "](/$1/)");
content = resolveLinks(content);
// Fenced code blocks
content = content.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) =>
`<pre><code${lang ? ` class="language-${lang}"` : ""}>${code.trim()}</code></pre>`);
// Resolve cross-wiki locale links: [label](./sl/page.md) → [/sl/page/]
content = content.replace(/\]\(\.\/sl\/([^)]+)\.md\)/g, "](/sl/$1/)");
// Tables
content = content.replace(/\|(.+)\|\n\|[-| ]+\|\n((?:\|.+\|\n?)*)/gm, (_, header, rows) => {
const hcells = header.split("|").map(c => c.trim()).filter(Boolean);
const thead = `<tr>${hcells.map(c => `<th>${c}</th>`).join("")}</tr>`;
const tbody = rows.trim().split("\n").map(row => {
const cells = row.split("|").map(c => c.trim()).filter(Boolean);
return `<tr>${cells.map(c => `<td>${c}</td>`).join("")}</tr>`;
}).join("");
return `<table><thead>${thead}</thead><tbody>${tbody}</tbody></table>`;
});
// Render fenced code blocks
const renderCode = (md) =>
md.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
return `<pre><code${lang ? ` class="language-${lang}"` : ""}>${code.trim()}</code></pre>`;
});
content = renderCode(content);
// Render tables
const renderTables = (md) => {
return md.replace(/\|(.+)\|\n\|[-| ]+\|\n((?:\|.+\|\n?)*)/gm, (_, header, rows) => {
const hcells = header.split("|").map(c => c.trim()).filter(Boolean);
const thead = `<tr>${hcells.map(c => `<th>${c}</th>`).join("")}</tr>`;
const tbody = rows.trim().split("\n").map(row => {
const cells = row.split("|").map(c => c.trim()).filter(Boolean);
return `<tr>${cells.map(c => `<td>${c}</td>`).join("")}</tr>`;
}).join("");
return `<table><thead>${thead}</thead><tbody>${tbody}</tbody></table>`;
});
};
content = renderTables(content);
// Render inline code, bold, italic, links, headings, lists
// Inline elements
content = content
.replace(/`([^`]+)`/g, "<code>$1</code>")
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
@ -97,33 +78,32 @@ content = content
.replace(/^## (.+)$/gm, "<h2>$1</h2>")
.replace(/^# (.+)$/gm, "<h1>$1</h1>")
.replace(/^- (.+)$/gm, "<li>$1</li>")
.replace(/((?:<li>.*<\/li>\n?)+)/g, "<ul>$1</ul>")
.replace(/\n\n/g, "</p><p>")
.replace(/^(.+)$/gm, (line) => {
if (line.startsWith("<")) return line;
return line;
});
.replace(/((?:<li>.*<\/li>\n?)+)/g, "<ul>$1</ul>");
const title = frontmatter.title || slug;
// Other locale link for language switcher
const otherLocale = isSl ? "en" : "sl";
const otherSlug = isSl ? slug.replace(/^sl\//, "") : `sl/${slug}`;
const otherLabel = isSl ? "English" : "Slovenščina";
// Paragraphs — wrap non-tag lines
const lines = content.split("\n");
const wrapped = [];
for (const line of lines) {
if (line.startsWith("<") || line === "") {
wrapped.push(line);
} else {
wrapped.push(`<p>${line}</p>`);
}
}
content = wrapped.join("\n");
---
<!DOCTYPE html>
<html lang={locale}>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{(title)} — Colibri Wiki</title>
<title>{title} — Colibri Wiki</title>
<style>
:root { --bg: #fff; --fg: #1a1a1a; --link: #0366d6; --muted: #666; --border: #e0e0e0; }
@media (prefers-color-scheme: dark) { :root { --bg: #1a1a1a; --fg: #e6e6e6; --link: #58a6ff; --muted: #999; --border: #333; } }
body { max-width: 720px; margin: 2rem auto; padding: 0 1rem; font: 16px/1.6 system-ui; background: var(--bg); color: var(--fg); }
nav { margin-bottom: 1.5rem; display: flex; justify-content: space-between; align-items: baseline; }
nav { margin-bottom: 1.5rem; }
nav a { color: var(--muted); font-size: .9rem; }
nav .lang a { font-weight: 600; color: var(--link); }
h1 { font-size: 1.8rem; }
h2 { font-size: 1.4rem; margin-top: 2rem; border-bottom: 1px solid var(--border); padding-bottom: .3rem; }
h3 { font-size: 1.1rem; margin-top: 1.5rem; }
@ -138,13 +118,10 @@ const otherLabel = isSl ? "English" : "Slovenščina";
</style>
</head>
<body>
<nav>
<a href={isSl ? "/sl/" : "/"}>{isSl ? "← kazalo" : "← wiki index"}</a>
<span class="lang"><a href={`/${otherSlug}/`}>{otherLabel}</a></span>
</nav>
<nav><a href="/">← wiki index</a></nav>
<article>
<h1>{title}</h1>
<p set:html={content} />
<Fragment set:html={content} />
</article>
</body>
</html>

View file

@ -46,7 +46,7 @@ if (raw.startsWith("---")) {
}
}
const title = (frontmatter.title || slug).replace(/^["']|["']$/g, "");
const title = (frontmatter.title || content.match(/^#\s+(.+)$/m)?.[1] || slug).replace(/^["']|["']$/g, "");
content = content.replace(/\]\(\.\/([^)]+)\.md\)/g, "](/sl/$1/)")
.replace(/\]\(\.\.\/([^)]+)\.md\)/g, "](/sl/$1/)")
.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) =>

View file

@ -1,4 +1,4 @@
# Agent harness: zot + Colibri
# Agent harness: pi, zot & Colibri
← [index](./index.md)

View file

@ -1,5 +1,5 @@
---
title: "Agentska vprega: zot + Colibri"
title: "Agentska vprega: pi, zot & Colibri"
description: "Dve binarni datoteki, ne ena — zot (agent, Go) in Colibri (krmilna ravnina, Rust)."
---