MkDocs Wiki 写作
本项目用 MkDocs Material / Zensical 渲染 Markdown。每种语法直接演示使用 —— AI 读 markdown 源文件即看到写法,人类访问 wiki 看到渲染效果,一份内容两个视图,不再"写法/效果"双写。
这一页把 MkDocs Material + pymdownx 常用的语法全过一遍,验证 Zensical 是否原样兼容。
1. 选项卡 ===(pymdownx.tabbed)
2. 提示框 !!!(admonition)
补充说明
这是一段补充说明。
注意
这是警告性内容。
说明
这是信息介绍。
提示
这是操作建议。
完成
这是成功标记。
3. 可折叠块 ???(pymdownx.details)
点击展开:命令参数
-h: 人类可读格式-T: 显示文件系统类型
折叠块里嵌套选项卡
4. 代码注解 # (1)(content.code.annotate)
- 说明第一处标注 —— 打开 SQL 文件并读取内容。
- 说明第二处标注 —— 传入游戏 ID 参数。
非代码场景下用 { .annotate }:
工作时间 9:30-18:30 (1)
- 最多可提前 30 分钟下班。
5. 代码行高亮 hl_lines(pymdownx.highlight)
import pandas as pd
from jinja2 import Template
import os, time
os.chdir(os.path.dirname(os.path.abspath(__file__)))
os.environ['TZ'] = 'Asia/Shanghai'
连续行用 -:
6. Mermaid 图表(pymdownx.superfences)
流程图:
graph TD
A[数据采集] --> B{数据类型}
B -->|结构化| C[写入数据库]
B -->|非结构化| D[文本处理]
时序图:
sequenceDiagram
participant 用户
participant 服务端
用户->>服务端: 发起请求
服务端-->>用户: 响应数据
7. 脚注 [^1](footnotes)
8. 内联代码与链接
行内 code 高亮、{==高亮文本==}(critic 扩展,本项目未开启,预期不渲染)。
普通链接 Zensical 与自动链接 https://zensical.org/。
9. 嵌入交互单页(iframe + 静态 SPA)
需要展示交互效果(组件演示、可视化、小工具)时,把纯客户端的自包含单页放进本 skill 的 assets/,文章里用原生 <iframe> 嵌入。单页随 wiki 一起发布到对象存储,跟站点同域 —— 无跨域限制、国内速度、AI 顺 URL 还能直接 GET 到未压缩的源码读懂实现。下面就是一个活例(React 迷你 CRUD:搜索 / 筛选 / 分页 / 增改删 / 上下架 / toast):
真身在 assets/react-spa-demo.html。硬性规矩:
- 纯客户端才能这样发布:对象存储是静态托管,带服务端(server functions / API)的应用嵌不了,只能外部部署后跨域 iframe
- 自包含、不走 CDN:运行时库 vendor 进全站共享的
docs/vendor/(线上/vendor/,各 skill 的 demo 复用;不放docs/assets/是因为那会跟主题产物的site/assets/混居),demo 里用根绝对路径/vendor/xxx.js引用。本例是 React 18 UMD +htm(React 19 起不再发 UMD 构建,免构建场景钉 18),国内访问不抖、断外网也能跑 - demo 逻辑不压缩:单页是文本,线上有独立 URL,AI 可直接读。vite 构建产物(
base: './'后整个目录丢进assets/)也能嵌,但 minified 对 AI 不可读,算二等公民 - 免 JSX 构建:用
htm的 tagged template 配 React UMD,零工具链,源码即真身 - iframe
src用站点根绝对路径、且指到具体.html文件(/skills/<skill>/assets/xxx.html):目录式 URL 下相对路径容易算错层级;样式上给定固定高度 +loading="lazy" - vite 目录产物的
src同样写到index.html(/skills/<skill>/assets/xxx-demo/index.html),不写目录 URLxxx-demo/:目录 URL 能不能解析到入口取决于托管的索引文档配置,写死文件名才让 demo 的存活与托管配置无关,也让读 md 源的 AI 一眼看到入口文件而不用猜。活例:frontend-styles/reference/的 task1–task7 七篇(2026-08-04 从目录 URL 统一补成index.html)
assets/react-spa-demo.html —— 演示单页源码(自包含、未压缩)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>React 单页 demo:商品管理</title>
<!--
react-spa-demo.html —— 本 wiki「嵌入交互单页」规范(skills/writing/mkdocs-wiki/index.md 第 9 节)的演示真身。
约定的落实:
- 纯客户端:全部状态在内存里,无任何服务端依赖,静态托管(对象存储)直接可跑
- 自包含:运行时库 vendor 在全站共享的 /vendor/(仓库 docs/vendor/)、根绝对路径引用,不走 CDN
· React 18 UMD(React 19 起官方不再发 UMD 构建,免构建场景钉 18)
· htm —— 1KB 的 tagged template,替代 JSX,从而不需要 Babel/打包器
- demo 逻辑不压缩:本文件随 wiki 发布、有独立 URL,AI GET 到即可读懂全部实现
内容:一个迷你「CRUD 表格」形状(呼应 skills/dashboard 的 open-dashboard 文章)——
搜索 / 分类筛选 / 分页 / 新增 / 编辑 / 上下架 / 删除 / 统计卡片 / toast 反馈。
-->
<style>
/* 设计令牌:亮暗两套,跟随系统 prefers-color-scheme */
:root {
--bg: #f6f7f9; --card: #ffffff; --fg: #1f2328; --muted: #6a737d;
--border: #e4e7eb; --primary: #3564d7; --primary-fg: #ffffff; --danger: #c4372c;
--chip-on-bg: #e3f4e8; --chip-on-fg: #176e34;
--chip-off-bg: #f0f1f3; --chip-off-fg: #6a737d;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #15181c; --card: #1e2227; --fg: #e6e9ec; --muted: #8b949e;
--border: #333a42; --primary: #5b8bf0; --danger: #e5695f;
--chip-on-bg: #12351f; --chip-on-fg: #5fca86;
--chip-off-bg: #2a2f36; --chip-off-fg: #8b949e;
}
}
* { box-sizing: border-box; }
body {
margin: 0; background: var(--bg); color: var(--fg);
font: 14px/1.6 system-ui, "PingFang SC", "Microsoft YaHei", sans-serif;
}
.page { max-width: 880px; margin: 0 auto; padding: 20px 16px 28px; }
.head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.head h1 { margin: 0; font-size: 20px; }
.sub { margin: 2px 0 0; color: var(--muted); font-size: 12px; }
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 14px 0; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; }
.stat { padding: 10px 14px; }
.stat-label { margin: 0; color: var(--muted); font-size: 12px; }
.stat-value { margin: 2px 0 0; font-size: 20px; font-weight: 650; font-variant-numeric: tabular-nums; }
.toolbar { display: flex; gap: 8px; margin-bottom: 10px; }
.search { flex: 1; }
input, select {
padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px;
background: var(--card); color: var(--fg); font: inherit;
}
input:focus, select:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
table { width: 100%; border-collapse: collapse; background: var(--card);
border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
thead th { text-align: left; font-size: 12px; color: var(--muted); font-weight: 500;
padding: 9px 12px; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--bg) 55%, var(--card)); }
tbody td { padding: 9px 12px; border-bottom: 1px solid var(--border); }
tbody tr:last-child td { border-bottom: 0; }
td.name { font-weight: 550; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.ops { text-align: right; white-space: nowrap; }
.empty { text-align: center; color: var(--muted); padding: 26px 12px; }
.warn { color: var(--danger); font-weight: 650; }
.chip { display: inline-block; padding: 1px 9px; border-radius: 999px; font-size: 12px; }
.chip-on { background: var(--chip-on-bg); color: var(--chip-on-fg); }
.chip-off { background: var(--chip-off-bg); color: var(--chip-off-fg); }
.btn { padding: 7px 12px; border: 1px solid var(--border); border-radius: 8px;
background: var(--card); color: var(--fg); font: inherit; cursor: pointer; }
.btn:hover:not(:disabled) { border-color: var(--primary); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn.primary { background: var(--primary); border-color: var(--primary); color: var(--primary-fg); }
.link { border: 0; background: none; color: var(--primary); font: inherit; cursor: pointer; padding: 0 5px; }
.link.danger { color: var(--danger); }
.pager { display: flex; justify-content: space-between; align-items: center;
margin-top: 10px; color: var(--muted); font-size: 13px; }
.pager div { display: flex; gap: 8px; }
.overlay { position: fixed; inset: 0; background: rgba(0,0,0,.42);
display: flex; align-items: center; justify-content: center; padding: 16px; }
.modal { background: var(--card); border: 1px solid var(--border); border-radius: 12px;
padding: 18px 20px; width: 100%; max-width: 380px; display: flex; flex-direction: column; gap: 10px; }
.modal h2 { margin: 0 0 2px; font-size: 16px; }
.modal label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
.modal input, .modal select { width: 100%; }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.err { margin: 0; color: var(--danger); font-size: 12px; }
.actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.toast { position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
background: var(--fg); color: var(--bg); padding: 8px 16px; border-radius: 999px;
font-size: 13px; box-shadow: 0 4px 16px rgba(0,0,0,.25); }
</style>
</head>
<body>
<div id="root"></div>
<script src="/vendor/react.production.min.js"></script>
<script src="/vendor/react-dom.production.min.js"></script>
<script src="/vendor/htm.umd.js"></script>
<script>
"use strict";
const { useState, useMemo, useEffect } = React;
const html = htm.bind(React.createElement); // htm 替代 JSX:html`<${组件} 属性=${值} />`
const PAGE_SIZE = 5;
const CATS = ["数码", "服饰", "食品", "家居"];
const SEED = [
{ id: 1, name: "无线降噪耳机", cat: "数码", price: 899, stock: 42, on: true },
{ id: 2, name: "机械键盘 87 键", cat: "数码", price: 399, stock: 18, on: true },
{ id: 3, name: "4K 显示器 27 寸", cat: "数码", price: 1499, stock: 7, on: true },
{ id: 4, name: "纯棉基础 T 恤", cat: "服饰", price: 79, stock: 230, on: true },
{ id: 5, name: "防晒轻薄外套", cat: "服饰", price: 199, stock: 0, on: false },
{ id: 6, name: "羊毛混纺围巾", cat: "服饰", price: 129, stock: 64, on: true },
{ id: 7, name: "云南小粒咖啡豆 500g", cat: "食品", price: 68, stock: 96, on: true },
{ id: 8, name: "冻干草莓脆 100g", cat: "食品", price: 25, stock: 150, on: true },
{ id: 9, name: "山核桃仁礼盒", cat: "食品", price: 158, stock: 12, on: false },
{ id: 10, name: "香薰蜡烛(雪松)", cat: "家居", price: 89, stock: 40, on: true },
{ id: 11, name: "折叠收纳箱三件套", cat: "家居", price: 119, stock: 55, on: true },
{ id: 12, name: "陶瓷马克杯 350ml", cat: "家居", price: 39, stock: 0, on: true },
];
function StatCard({ label, value }) {
return html`
<div class="card stat">
<p class="stat-label">${label}</p>
<p class="stat-value">${value}</p>
</div>`;
}
// 新增 / 编辑共用一个弹窗:item 无 id 视为新增
function Modal({ item, cats, onSave, onClose }) {
const isNew = !item.id;
const [f, setF] = useState({
name: item.name || "", cat: item.cat || cats[0],
price: item.price ?? "", stock: item.stock ?? "",
});
const [err, setErr] = useState("");
const set = k => e => setF({ ...f, [k]: e.target.value });
const submit = e => {
e.preventDefault();
const price = Number(f.price), stock = Number(f.stock);
if (!f.name.trim()) return setErr("名称不能为空");
if (f.price === "" || !Number.isFinite(price) || price < 0) return setErr("价格要是非负数字");
if (f.stock === "" || !Number.isInteger(stock) || stock < 0) return setErr("库存要是非负整数");
onSave({ ...item, name: f.name.trim(), cat: f.cat, price, stock });
};
return html`
<div class="overlay" onClick=${e => { if (e.target === e.currentTarget) onClose(); }}>
<form class="modal" onSubmit=${submit}>
<h2>${isNew ? "新增商品" : "编辑商品"}</h2>
<label>名称
<input value=${f.name} onChange=${set("name")} placeholder="商品名称" autoFocus />
</label>
<label>分类
<select value=${f.cat} onChange=${set("cat")}>
${cats.map(c => html`<option key=${c} value=${c}>${c}</option>`)}
</select>
</label>
<div class="row2">
<label>价格(¥)
<input type="number" min="0" step="0.01" value=${f.price} onChange=${set("price")} />
</label>
<label>库存
<input type="number" min="0" step="1" value=${f.stock} onChange=${set("stock")} />
</label>
</div>
${err && html`<p class="err">${err}</p>`}
<div class="actions">
<button type="button" class="btn" onClick=${onClose}>取消</button>
<button type="submit" class="btn primary">${isNew ? "创建" : "保存"}</button>
</div>
</form>
</div>`;
}
function App() {
const [rows, setRows] = useState(SEED);
const [q, setQ] = useState("");
const [cat, setCat] = useState("全部");
const [page, setPage] = useState(1);
const [editing, setEditing] = useState(null); // null=关闭 | {}=新增 | 行对象=编辑
const [toast, setToast] = useState("");
useEffect(() => {
if (!toast) return;
const t = setTimeout(() => setToast(""), 2200);
return () => clearTimeout(t);
}, [toast]);
const filtered = useMemo(
() => rows.filter(r =>
(cat === "全部" || r.cat === cat) &&
r.name.toLowerCase().includes(q.trim().toLowerCase())),
[rows, q, cat],
);
const pages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
const cur = Math.min(page, pages);
const view = filtered.slice((cur - 1) * PAGE_SIZE, cur * PAGE_SIZE);
const stockValue = rows.reduce((s, r) => s + r.price * r.stock, 0);
const save = item => {
if (item.id) {
setRows(rows.map(r => (r.id === item.id ? item : r)));
setToast(`已保存「${item.name}」`);
} else {
const id = Math.max(0, ...rows.map(r => r.id)) + 1;
setRows([{ ...item, id, on: true }, ...rows]);
setToast(`已创建「${item.name}」`);
}
setEditing(null);
};
const toggle = r => {
setRows(rows.map(x => (x.id === r.id ? { ...x, on: !x.on } : x)));
setToast(`「${r.name}」已${r.on ? "下架" : "上架"}`);
};
const remove = r => {
if (!window.confirm(`删除「${r.name}」?`)) return;
setRows(rows.filter(x => x.id !== r.id));
setToast(`已删除「${r.name}」`);
};
return html`
<div class="page">
<header class="head">
<div>
<h1>商品管理</h1>
<p class="sub">React 18 + htm 自包含单页 · 数据在内存里,刷新即重置</p>
</div>
<button class="btn primary" onClick=${() => setEditing({})}>+ 新增商品</button>
</header>
<div class="stats">
<${StatCard} label="商品总数" value=${rows.length} />
<${StatCard} label="在售" value=${rows.filter(r => r.on).length} />
<${StatCard} label="库存货值" value=${"¥ " + stockValue.toLocaleString("zh-CN")} />
</div>
<div class="toolbar">
<input class="search" placeholder="搜索商品名…" value=${q}
onChange=${e => { setQ(e.target.value); setPage(1); }} />
<select value=${cat} onChange=${e => { setCat(e.target.value); setPage(1); }}>
<option value="全部">全部分类</option>
${CATS.map(c => html`<option key=${c} value=${c}>${c}</option>`)}
</select>
</div>
<table>
<thead>
<tr><th>名称</th><th>分类</th><th class="num">价格</th><th class="num">库存</th><th>状态</th><th class="ops">操作</th></tr>
</thead>
<tbody>
${view.map(r => html`
<tr key=${r.id}>
<td class="name">${r.name}</td>
<td>${r.cat}</td>
<td class="num">¥ ${r.price.toLocaleString("zh-CN")}</td>
<td class="num">${r.stock === 0 ? html`<span class="warn">0</span>` : r.stock}</td>
<td><span class=${"chip " + (r.on ? "chip-on" : "chip-off")}>${r.on ? "在售" : "下架"}</span></td>
<td class="ops">
<button class="link" onClick=${() => setEditing(r)}>编辑</button>
<button class="link" onClick=${() => toggle(r)}>${r.on ? "下架" : "上架"}</button>
<button class="link danger" onClick=${() => remove(r)}>删除</button>
</td>
</tr>`)}
${view.length === 0 && html`
<tr><td class="empty" colSpan="6">没有匹配的商品 —— 换个关键词或清空筛选试试</td></tr>`}
</tbody>
</table>
<footer class="pager">
<span>共 ${filtered.length} 件 · 第 ${cur} / ${pages} 页</span>
<div>
<button class="btn" disabled=${cur <= 1} onClick=${() => setPage(cur - 1)}>上一页</button>
<button class="btn" disabled=${cur >= pages} onClick=${() => setPage(cur + 1)}>下一页</button>
</div>
</footer>
${editing !== null && html`
<${Modal} item=${editing} cats=${CATS} onSave=${save} onClose=${() => setEditing(null)} />`}
${toast && html`<div class="toast">${toast}</div>`}
</div>`;
}
ReactDOM.createRoot(document.getElementById("root")).render(html`<${App} />`);
</script>
</body>
</html>
写作规范
- 同一功能有 Python / R 两种写法 → 用选项卡
=== - 内容很长但非必读 → 用折叠
??? - 重要注意事项 → 用提示框
!!!(warning 易犯错误、tip 建议、note 补充) - 系统架构或交互流程 → 用 Mermaid 图
- 交互效果演示 → iframe 嵌自包含静态单页(纯客户端、真身进
assets/、vendor 不走 CDN、逻辑不压缩,规矩与活例见第 9 节) - AI 生成概念配图 → 用白底黑色马克笔草图,一张图只讲一个概念
- 引用外部一手资料:有存档、有引用、有分析。三步缺一不可:① 真身 clone / 下载进文章
assets/存档(注明出处与获取日期),文中??? abstract折叠 +--8<--展示原文;② 正文摘关键原句(>引用块);③ 每处引用必须跟自己的分析 —— 只贴引用不给分析,等于没消化。例外:引用的是活跃开源仓库、且原文的关键契约(Invariants / 不变量 / API)已经在正文里消化成自己的语言,可以省掉真身存档,改用钉具体 commit hash 的 GitHub 永链代替。前提是永链钉的是 commit 而不是 branch(内容冻结不随上游变),且读者不依赖点开链接就能理解正文——链接只做溯源。活例:Dashboard skill 的 open-dashboard 蓝本吸收 - 收录外部 git 仓库:只取核心文件,不整仓 clone 进来。挑对理解主题真正关键的那几个文件复制进
assets/存档,其余一律不进 docs —— 本 wiki 不追求完整镜像,追求核心,因为人也要读;要完整原貌,顺钉 commit 的永链自己去 GitHub。活例:Dashboard 蓝本吸收曾把 84 篇原文全量镜像进assets/,后来删到只剩PATTERNS.md+backends/CONTRACT.md两份切不进单篇文章的系统级契约。唯一例外是吸收型 skill:行为定义文件(主文件 + 它引用的 references)必须收齐才能跑,见附录的吸收规矩 - markdown 层级缩进 统一用 4 个空格(折叠块 / 选项卡 / 提示框的子内容必须缩进 4 空格才被识别)。注意:这只指 markdown 写作时的缩进,代码块内部的 Python / 其他语言缩进原样保留,不受影响
- 代码块语言标识统一用
python(不用py) - 新增页面必须在
mkdocs.yml的nav:中注册 - 每篇新页面必须被某个祖先索引页用 markdown 链接直接链到 ——
nav只是给人的策展层,不在 AI 链路上,漏挂链接的文章对 AI 等于不存在。索引页指index.md/MIRROR.md/SKILL.md三种,写一行带钩子的链接即可。deploy.sh在构建前跑scripts/check-links.py强制校验,不过不许部署 - 不给挪走的文件留旧地址存根页。要保住旧 URL 就别挪文件;挪了就直接删原文件,让 404 诚实暴露。存根页是天生的孤儿(无人链接、不进 nav),留着只会逼校验开豁免口子,而每个豁免口子都是下一次漏挂链接的藏身处(2026-07-30 依此清掉了
posts/animated-ppt/reference/deck-skill.md和skills/data-visualization/reference/f1-rung-bars.md两页存根)
文章配图:白底黑色马克笔草图
概念配图不负责装饰,只负责让一个抽象关系更快被看懂。需要 AI 新生成概念图时,统一画成白底黑色马克笔随手草图;每张图只表达一个概念。 截图、数据图表、历史材料等有证据作用的图片不套这套风格。
视觉硬规矩
- 媒介 → 黑色马克笔或手机手写笔,不用蜡笔、水彩或铅笔
- 线条 → 实心黑线,粗细略有变化;允许重描、越界、歪斜和接缝不齐
- 造型 → 物体压成基础几何形,只留最关键的识别特征
- 构图 → 主体居中,占画面约 60–70%,四周留大量空白
- 色彩 → 纯黑线、纯白背景,不填色,不画阴影、灰阶或排线
- 气质 → 像不擅长画画的人临时拿笔解释概念——直接、笨拙、未经修饰
- 信息量 → 一张图只讲一个概念,不堆文字、标签、箭头或装饰
- 避免 → 工整图标感、儿童绘本感、毛茸茸的蜡笔颗粒、刻意卖萌、专业插画质感
生成与入库
- 先列清文章里真正需要画的概念——能用一句话说清的图才画;一句话里有两个关系就拆成两张。
- 每个概念单独生成,不让模型在一张画布里拼多张图或多个分镜。
- 逐张检查纯白背景、纯黑线、无文字、无阴影、主体比例和留白;不合格只改一个最明显的问题再生成。
- 最终图片放进文章自己的
assets/,不要只留在生成工具的默认目录。文件名用语义化 kebab-case,如random-sample-marker-doodle.png。 -
Markdown 用相对路径引用,alt text 写这张图解释的概念,不重复“白底黑色马克笔”这类视觉信息:
-
PNG 等二进制文件按仓库现有
.gitattributes走 git-lfs;照常git add、commit、push,不把图片转成 base64 塞进 markdown。
可直接复用的 prompt 骨架:
Use case: scientific-educational
Asset type: landscape illustration for a concise Chinese wiki article
Primary request: <只写一个要解释的概念,以及画面里必须出现的对象>
Scene/backdrop: pure white background
Style/medium: black marker or phone stylus doodle, drawn quickly by an adult
who is bad at drawing while explaining a concept; solid black strokes with
slightly uneven thickness; crooked outlines, imperfect proportions, visible
retracing, overshoot, and mismatched joins; objects reduced to basic geometry
Composition/framing: landscape 3:2, subject centered and occupying about
60–70 percent of the canvas, very large blank margins
Color palette: pure black linework only on pure white
Constraints: no text, no labels, no numbers, no arrows, no fill, no gray,
no shadows, no hatching, no decoration, no watermark
Avoid: polished icon design, children's picture-book style, cute expression,
crayon grain, pencil texture, watercolor, professional illustration,
geometric precision, dense infographic layout
活例:《抽样:检验之前,先把样本抽对》用四张图分别讲随机抽取、重复抽样、分层抽样和抽样框遗漏;《用西瓜搞懂准确率、精确率和召回率》把同一风格用于分类指标。
文风
本 wiki 的行文默认口吻,AI 代笔时照此写:
- 先结论后展开:段落第一句就是论点;标题尽量直接说出结论(「nav 随便重排,链接图纹丝不动」,不是「关于 nav 的说明」)
- 短句,破折号接推理:一句话说一件事;因果和转折用「——」接在句内,不起「因此 / 然而 / 值得注意的是」的套话头
- 要点一行一条,prompt 式:每条是可直接执行的指令或可检验的判断,不写「小标题:多行解释」的两段式
- 加粗只给结论和硬规矩,不给气氛词;形容词能删则删
- 统一术语不换词:真身、坑、规矩、钉 commit、软链……全站同一个词指同一件事,AI 顺链接连读多篇不用重新对齐概念
- 技术词保留英文原形(commit、nav、snippet、iframe),叙述用中文,不音译不生造
- 规矩必须带活例:立一条规范就指一个站内真实例子(「活例:Dashboard skill 的蓝本吸收」);没有活例的规矩先别写进规范
- 没做的事就说没做:「做了再回来补」「待续」,不拿计划冒充成果(活例:手记里 CDN 那条、COS 部署篇的下一步节)
- 写完对照 去 AI 味 的模式清单自查一遍
项目配置(附录)
下面以本 wiki 自己为例(基于 Zensical,跟 MkDocs Material 语法层面 100% 兼容)。
项目结构
zensical-wiki/
├── mkdocs.yml # Zensical / MkDocs 共用配置
├── docs/ # 所有文档源
│ ├── index.md
│ ├── posts/
│ └── skills/
├── deploy.sh # 构建 + 同步 COS
└── pyproject.toml # uv 管理 zensical 依赖
新增页面流程
- 在
docs/下合适位置创建.md - 编写内容
- 必须在
mkdocs.yml的nav:注册路径 - 必须在父级索引页(
index.md/MIRROR.md/SKILL.md)加一行链接 —— 给 AI 的通路,见下节 python3 scripts/check-links.py自查(可选,deploy.sh里会强制跑一遍)uv run zensical serve本地预览(毫秒级热更新)./deploy.sh一键构建 + 同步到 COS
链路校验:scripts/check-links.py
本 wiki 的 AI 入口是相对链接,不是 nav。所以「文章写完了、nav 也注册了」并不代表 AI 能读到它 —— 得有人在索引页里挂一行链接。这件事纯靠自觉会漏,所以做成部署闸门:deploy.sh 在 git pull 之后、读凭证和构建之前跑校验,非零退出码被 set -e 拦下,部署中止。
查三项:
| 检查 | 规则 | 失败后果 |
|---|---|---|
| 父级链接 | 每篇 .md 必须被某个祖先目录的 index.md / MIRROR.md / SKILL.md 直接链到 |
阻断部署 |
| 死链 | 指向不存在的 .md |
阻断部署 |
nav 注册 |
路径出现在 mkdocs.yml 里(MIRROR.md 按规范豁免) |
阻断部署 |
写作时只需记两条例外,别为了过校验去改错地方:
- 吸收型 skill 的
index.md不许改 —— 它是上游原文照录。归档文件的链接挂在MIRROR.md(目录里唯一自己写的文件)或分类索引上,校验认「祖先」而非「最近父级」正是为此。活例:xi-wen 的 9 份归档全靠MIRROR.md的「本地归档」一栏挂住 - 上游原文里的死链是警告不是错误 —— 原文里的路径常是举例,本地不可能存在;往原文加豁免注释就是改原文。判据是同级或祖先目录有
MIRROR.md
豁免写在文件顶部(前 5 行内),理由跟着文件走、不躺在脚本白名单里:
「已迁移存根」不是正当理由,本 wiki 不留存根页。
脚本真身与设计取舍(含它查不到什么)见 COS 部署篇 · 闸门一节。
当前项目的 mkdocs.yml
mkdocs.yml 真身在仓库根(zensical 才读得到),它是项目配置、不是本 skill 的资产,所以不复制进 assets/。下面用 snippets 直接引用仓库根真身展示(源文件只一处,引用不算重复):
mkdocs.yml
site_name: 牛合天's wiki
site_url: https://wiki.liuhetian.work/
copyright: >
Copyright © 2026 Liu Hetian ·
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">蜀ICP备2021023978号-2</a>
theme:
name: material
custom_dir: overrides # 首页开屏模板 home.html 所在目录
language: zh
font: false # 关掉 Google Fonts 远程引用(渲染阻塞,境内常超时);字体本地化见 extra_css
features:
- navigation.tabs
- navigation.indexes
- content.code.copy
- content.code.annotate
extra_css:
- vendor/fonts/fonts.css # 本地 Inter + JetBrains Mono,替代 fonts.googleapis.com;只出 --md-text-font/--md-code-font
- stylesheets/zx-tokens.css # 全站令牌层:Atelier Zero 令牌 + --md-*/--color-* 映射(首页 home.css 也用它)
- stylesheets/zx-theme.css # 全站皮肤层:只写选择器,消费上一层的变量
extra_javascript:
- vendor/mathjax-init.js # MathJax 配置,须在真身之前
- vendor/mathjax/tex-svg.js # 本地化真身(3.2.2,SVG 输出免字体文件),不依赖外网 CDN
- vendor/echarts-init.js # ```echarts 代码块渲染器;真身按需动态加载,无图页面零开销
markdown_extensions:
- admonition
- attr_list
- footnotes
- md_in_html
- pymdownx.arithmatex:
generic: true
- pymdownx.details
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.snippets:
base_path:
- docs # docs 内引用去掉 docs/ 前缀(如 skills/.../x.md),避免 AI 把 docs/ 当 URL
- . # 回退到项目根:mkdocs.yml / deploy.sh 等真实文件仍可被 --8<-- 引用
check_paths: true
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- name: echarts # 声明式图表:fence 里写 ECharts option JSON,vendor/echarts-init.js 渲染
class: echarts
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
# zensical 的链接校验有并行竞态:同一份内容偶发(约 1/15 次构建)误报大量
# "page does not exist",产物 HTML 实测无差异(0.0.46 与 0.0.53 均复现)。
# 死链检查由 scripts/check-links.py 在部署前把关(会阻断部署),这里关掉不丢保障;
# invalid_link_anchors(锚点校验)check-links.py 不覆盖,保留 —— 若它也出现偶发误报,同样对待。
validation:
invalid_links: false
nav:
- 首页:
- index.md
- 文章:
- posts/index.md
- AI友好知识库:
- posts/cos-wiki-deploy/index.md
- 部署实操手册: posts/cos-wiki-deploy/reference/deploy.md
- 建站手记: posts/cos-wiki-deploy/reference/wiki-build-log.md
- 自建 git-lfs 后端: posts/git-lfs-cos.md
- 预测项目闭环: posts/prediction-loop.md
- AI 时代的产品经理: posts/ai-pm.md
- 动画网页 PPT: posts/animated-ppt/index.md
- 超级轻量的自用AI编程Harness框架: posts/ai-code-skeleton/index.md
- Kindle 常显信息屏:
- posts/kindle-dashboard/index.md
- 越狱与显示通道: posts/kindle-dashboard/reference/jailbreak.md
- 上报接口交接: posts/kindle-dashboard/reference/report-api.md
- 决策日志: posts/kindle-dashboard/reference/decision-log.md
- 工作方法:
- posts/methods/index.md
- 精力管理:
- posts/methods/energy/index.md
- 神经系统降载: posts/methods/energy/nervous-system-regulation.md
- 推进执行:
- posts/methods/execution/index.md
- 如果—那么计划: posts/methods/execution/if-then-plans.md
- 限制同时开工: posts/methods/execution/wip-limits.md
- 每周清仓: posts/methods/execution/weekly-review.md
- 做出决策:
- posts/methods/decisions/index.md
- 单向门与双向门: posts/methods/decisions/two-way-door-decisions.md
- 事前验尸: posts/methods/decisions/premortem.md
- 笔记:
- notes/index.md
- 概率与算法:
- notes/probability/index.md
- 12 枚硬币的奇偶性: notes/probability/coin-parity.md
- 3 根绳子烧出 7 分钟: notes/probability/rope-timing.md
- 统计学:
- notes/statistics/index.md
- 基础:
- 描述统计: notes/statistics/data-description/index.md
- 概率模型: notes/statistics/probability-models/index.md
- 常见分布: notes/statistics/distributions/index.md
- 抽样: notes/statistics/sampling/index.md
- 抽样分布: notes/statistics/sampling-distributions/index.md
- 统计推断:
- 点估计: notes/statistics/estimation/index.md
- 置信区间: notes/statistics/confidence-intervals/index.md
- 假设检验: notes/statistics/hypothesis-testing/index.md
- 组间比较: notes/statistics/group-comparisons/index.md
- 功效与样本量: notes/statistics/power-sample-size/index.md
- 分类数据: notes/statistics/categorical-data/index.md
- 非参数方法: notes/statistics/nonparametric/index.md
- 重抽样: notes/statistics/resampling/index.md
- 统计模型:
- 简单线性回归: notes/statistics/linear-regression/index.md
- 多元回归: notes/statistics/multiple-regression/index.md
- 方差分析: notes/statistics/anova/index.md
- 时间序列: notes/statistics/time-series/index.md
- 设计与实务:
- 实验设计: notes/statistics/experimental-design/index.md
- 因果推断: notes/statistics/causal-inference/index.md
- 缺失数据: notes/statistics/missing-data/index.md
- 贝叶斯统计: notes/statistics/bayesian/index.md
- 统计分析工作流: notes/statistics/statistical-workflow/index.md
- 机器学习:
- notes/machine-learning/index.md
- 准确率、精确率与召回率: notes/machine-learning/classification-metrics/index.md
- Git:
- notes/git/index.md
- fork 后吸收上游: notes/git/fork-upstream-sync.md
- stash 的心智模型: notes/git/stash.md
- worktree 的心智模型: notes/git/worktree.md
- Skills:
- skills/index.md
- FastAPI 后端:
- skills/fastapi/index.md
- 项目布局: skills/fastapi/reference/项目布局.md
- 数据库: skills/fastapi/reference/数据库.md
- RAG: skills/fastapi/reference/rag.md
- MCP: skills/fastapi/reference/mcp.md
- 优雅终止: skills/fastapi/reference/优雅终止.md
- 定时任务: skills/fastapi/reference/定时任务.md
- 配置管理: skills/fastapi/reference/配置管理.md
- 对话前端: skills/fastapi/reference/对话前端.md
- 运行时补丁: skills/fastapi/reference/运行时补丁.md
- 日志管理: skills/fastapi/reference/日志管理.md
- 测试: skills/fastapi/reference/测试.md
- 部署: skills/fastapi/reference/部署.md
- 监控: skills/fastapi/reference/监控.md
- 事后检查: skills/fastapi/reference/事后检查.md
- Dashboard 后台:
- skills/dashboard/index.md
- 表单:
- 表单: skills/dashboard/reference/表单/表单.md
- 向导表单: skills/dashboard/reference/表单/向导表单.md
- 搜索下拉: skills/dashboard/reference/表单/搜索下拉.md
- 文件上传: skills/dashboard/reference/表单/文件上传.md
- 行内编辑: skills/dashboard/reference/表单/行内编辑.md
- 列表与表格:
- 表格: skills/dashboard/reference/列表与表格/表格.md
- 卡片列表: skills/dashboard/reference/列表与表格/卡片列表.md
- 紧凑列表: skills/dashboard/reference/列表与表格/紧凑列表.md
- 无限滚动: skills/dashboard/reference/列表与表格/无限滚动.md
- 虚拟表格: skills/dashboard/reference/列表与表格/虚拟表格.md
- 列控制: skills/dashboard/reference/列表与表格/列控制.md
- 筛选面板: skills/dashboard/reference/列表与表格/筛选面板.md
- 保存视图: skills/dashboard/reference/列表与表格/保存视图.md
- CSV 导入导出: skills/dashboard/reference/列表与表格/CSV导入导出.md
- 富视图:
- 看板: skills/dashboard/reference/富视图/看板.md
- 日历: skills/dashboard/reference/富视图/日历.md
- 树形: skills/dashboard/reference/富视图/树形.md
- 时间线: skills/dashboard/reference/富视图/时间线.md
- 主从视图: skills/dashboard/reference/富视图/主从视图.md
- 详情与页面:
- 详情页: skills/dashboard/reference/详情与页面/详情页.md
- 记录选项卡: skills/dashboard/reference/详情与页面/记录选项卡.md
- 关联记录: skills/dashboard/reference/详情与页面/关联记录.md
- 多栏布局: skills/dashboard/reference/详情与页面/多栏布局.md
- 设置页: skills/dashboard/reference/详情与页面/设置页.md
- 图表页: skills/dashboard/reference/详情与页面/图表页.md
- 展示与反馈:
- 数据展示件: skills/dashboard/reference/展示与反馈/数据展示件.md
- 反馈状态: skills/dashboard/reference/展示与反馈/反馈状态.md
- 通知中心: skills/dashboard/reference/展示与反馈/通知中心.md
- 审计日志: skills/dashboard/reference/展示与反馈/审计日志.md
- 平台能力:
- 权限门控: skills/dashboard/reference/平台能力/权限门控.md
- 登录方式: skills/dashboard/reference/平台能力/登录方式.md
- 全局搜索: skills/dashboard/reference/平台能力/全局搜索.md
- i18n: skills/dashboard/reference/平台能力/i18n.md
- 计费: skills/dashboard/reference/平台能力/计费.md
- 实时刷新: skills/dashboard/reference/平台能力/实时刷新.md
- 数据可视化:
- skills/data-visualization/index.md
- Lieflat Charts:
- skills/data-visualization/lieflat-charts/index.md
- 基础型 · Lupi Basics:
- F1 · 梯级柱状图: skills/data-visualization/lieflat-charts/reference/f1-rung-bars.md
- F2 · 发丝折线图: skills/data-visualization/lieflat-charts/reference/f2-hairline-line.md
- F3 · 发丝面积图: skills/data-visualization/lieflat-charts/reference/f3-hairline-area.md
- F4 · 刻线环形图: skills/data-visualization/lieflat-charts/reference/f4-tick-donut.md
- F5 · 刻线横条图: skills/data-visualization/lieflat-charts/reference/f5-tick-rows.md
- F6 · 并列梯级柱: skills/data-visualization/lieflat-charts/reference/f6-paired-rungs.md
- F7 · 堆叠梯级柱: skills/data-visualization/lieflat-charts/reference/f7-stacked-rungs.md
- F8 · 铅垂散点图: skills/data-visualization/lieflat-charts/reference/f8-plumb-scatter.md
- F9 · 梯级瀑布图: skills/data-visualization/lieflat-charts/reference/f9-rung-waterfall.md
- F10 · 点阵热力图: skills/data-visualization/lieflat-charts/reference/f10-dot-heat.md
- F11 · 刻线进度表: skills/data-visualization/lieflat-charts/reference/f11-tick-gauge.md
- F12 · 串珠哑铃图: skills/data-visualization/lieflat-charts/reference/f12-dumbbell-queue.md
- 编辑型 · Lupi Editorial:
- L1 · 上线扇形图: skills/data-visualization/lieflat-charts/reference/l1-launch-fan.md
- L2 · 点阵级联图: skills/data-visualization/lieflat-charts/reference/l2-dot-cascade.md
- L3 · 条码棒棒糖图: skills/data-visualization/lieflat-charts/reference/l3-barcode-lollipop.md
- L4 · 弧形矩阵: skills/data-visualization/lieflat-charts/reference/l4-arc-matrix.md
- L5 · 径向汇聚图: skills/data-visualization/lieflat-charts/reference/l5-radial-convergence.md
- L6 · 贡献者星群: skills/data-visualization/lieflat-charts/reference/l6-cluster-field.md
- L7 · 品牌光谱: skills/data-visualization/lieflat-charts/reference/l7-brand-spectrum.md
- L8 · 点阵空间矩阵: skills/data-visualization/lieflat-charts/reference/l8-dotty-matrix.md
- L9 · 气泡年鉴: skills/data-visualization/lieflat-charts/reference/l9-bubble-almanac.md
- L10 · 径向拼布图: skills/data-visualization/lieflat-charts/reference/l10-radial-patchwork.md
- L11 · 趋势谱系图: skills/data-visualization/lieflat-charts/reference/l11-trend-lineage.md
- L12 · 归属柱廊图: skills/data-visualization/lieflat-charts/reference/l12-type-colonnade.md
- L13 · 沙漏流图: skills/data-visualization/lieflat-charts/reference/l13-hourglass-stream.md
- L14 · 百人点阵: skills/data-visualization/lieflat-charts/reference/l14-hundred-field.md
- L15 · 选票刻线图: skills/data-visualization/lieflat-charts/reference/l15-ballot-tally.md
- 快读型 · Glance:
- G1 · 区间胶囊图: skills/data-visualization/lieflat-charts/reference/g1-range-capsules.md
- G2 · 花瓣玫瑰图: skills/data-visualization/lieflat-charts/reference/g2-petal-rose.md
- G3 · 粗体柱状图: skills/data-visualization/lieflat-charts/reference/g3-chunky-bars.md
- G4 · 点阵华夫图: skills/data-visualization/lieflat-charts/reference/g4-dot-waffle.md
- G5 · 象形柱状图: skills/data-visualization/lieflat-charts/reference/g5-pictorial-bar.md
- G6 · 小型环形关系图: skills/data-visualization/lieflat-charts/reference/g6-circular-graph.md
- G7 · 左右树状图: skills/data-visualization/lieflat-charts/reference/g7-tree-lr.md
- G8 · 雨幕双面积图: skills/data-visualization/lieflat-charts/reference/g8-rainfall-dual-area.md
- G9 · 散点变形图: skills/data-visualization/lieflat-charts/reference/g9-scatter-morph.md
- G10 · 发散条形图: skills/data-visualization/lieflat-charts/reference/g10-diverging-bar.md
- G11 · 小型力导向图: skills/data-visualization/lieflat-charts/reference/g11-force-graph.md
- G12 · 错落波形图: skills/data-visualization/lieflat-charts/reference/g12-stagger-wave.md
- G13 · 大切片图: skills/data-visualization/lieflat-charts/reference/g13-big-slice.md
- G14 · 单轴图: skills/data-visualization/lieflat-charts/reference/g14-single-axis.md
- G15 · 抖动条带图: skills/data-visualization/lieflat-charts/reference/g15-jitter-strip.md
- G16 · 动态条形竞赛: skills/data-visualization/lieflat-charts/reference/g16-bar-race.md
- G17 · 动态流图: skills/data-visualization/lieflat-charts/reference/g17-dynamic-stream.md
- G18 · 一笔绘制与计数器: skills/data-visualization/lieflat-charts/reference/g18-draw-in-plus-counter.md
- 独立交互大图:
- B1 · 密集环形关系图: skills/data-visualization/lieflat-charts/reference/b1-circular-graph-dense.md
- B2 · 密集力导向图: skills/data-visualization/lieflat-charts/reference/b2-force-graph-dense.md
- B3 · 三段丝线图: skills/data-visualization/lieflat-charts/reference/b3-thread-triptych.md
- 前端画布:
- skills/canvas/index.md
- L1 · 原生无限画布: skills/canvas/reference/l1-vanilla.md
- L2 · React 手写画布: skills/canvas/reference/l2-react.md
- L3 · 数据驱动节点图: skills/canvas/reference/l3-nodegraph.md
- L4 · 白板: skills/canvas/reference/l4-whiteboard.md
- 写作口味:
- skills/writing/index.md
- MkDocs Wiki 文档: skills/writing/mkdocs-wiki/index.md
- 报纸版 HTML: skills/writing/newspaper/index.md
- 滚动 deck 工程手册: skills/writing/deck/index.md
- 去 AI 味:
- skills/writing/qu-ai-wei/index.md
- 51 条 AI 腔模式: skills/writing/qu-ai-wei/references/patterns.md
- 平台场景规则: skills/writing/qu-ai-wei/references/platform-patterns.md
- 品牌文案语体: skills/writing/qu-ai-wei/references/brand-voice.md
- 白名单: skills/writing/qu-ai-wei/references/whitelists.md
- 标点规范: skills/writing/qu-ai-wei/references/punctuation.md
- 语序诊断: skills/writing/qu-ai-wei/references/syntax.md
- 完整示例: skills/writing/qu-ai-wei/references/examples.md
- 正面参考模型: skills/writing/qu-ai-wei/references/reference-models.md
- 参考来源: skills/writing/qu-ai-wei/references/sources.md
- 檄文:
- skills/writing/xi-wen/index.md
- 逐篇精读: skills/writing/xi-wen/references/逐篇精读.md
- 骈句声律: skills/writing/xi-wen/references/骈句声律.md
- 用典骂法库: skills/writing/xi-wen/references/用典骂法库.md
- 戏仿范例: skills/writing/xi-wen/references/戏仿范例.md
- 底本·为袁绍檄豫州文: skills/writing/xi-wen/references/originals/为袁绍檄豫州文.md
- 底本·为李密檄洛州文: skills/writing/xi-wen/references/originals/为李密檄洛州文.md
- 底本·为徐敬业讨武曌檄: skills/writing/xi-wen/references/originals/为徐敬业讨武曌檄.md
- 底本·谕中原檄: skills/writing/xi-wen/references/originals/谕中原檄.md
- 底本·讨粤匪檄: skills/writing/xi-wen/references/originals/讨粤匪檄.md
- 前端风格收集:
- skills/frontend-styles/index.md
- PIP-BOY 琥珀终端: skills/frontend-styles/reference/pip-boy-terminal.md
- Celestia 主题收藏卡: skills/frontend-styles/reference/celestia-collection.md
- Verdant Glass 苔光用量台: skills/frontend-styles/reference/verdant-glass.md
- Syzygy 克莱因蓝星穹: skills/frontend-styles/reference/syzygy-astral.md
- NEXUS 2030 酸绿深空首屏: skills/frontend-styles/reference/nexus-2030.md
- ATELIER 纸白动力学: skills/frontend-styles/reference/atelier-kinetic.md
- PLATTER 平面音乐档案: skills/frontend-styles/reference/task1-platter.md
- PARALLAX 三态作品档案: skills/frontend-styles/reference/task2-parallax-archive.md
- LINE//SYSTEM 线性工业图形: skills/frontend-styles/reference/task3-line-system.md
- WORLD FILES 档案袋叙事: skills/frontend-styles/reference/task4-world-files.md
- PIXEL BLOOM 网格揭示: skills/frontend-styles/reference/task5-pixel-bloom.md
- TINTORY 视觉考古编辑部: skills/frontend-styles/reference/task6-tintory.md
- NEAT ANNOTATIONS 手绘标注标本: skills/frontend-styles/reference/task7-neat-annotations.md
- One Hub 翡翠管理台: skills/frontend-styles/reference/onehub-berry-admin.md
- Vercel Lanyard 可拖拽 3D 工牌: skills/frontend-styles/reference/vercel-lanyard.md
- Open Design 官网拆解:
- skills/frontend-styles/open-design/index.md
- 滚动感知吸顶导航: skills/frontend-styles/open-design/headroom-nav.md
- 标题逐词模糊入场: skills/frontend-styles/open-design/blur-text.md
- 滚动入场编排: skills/frontend-styles/open-design/scroll-reveal.md
- 宣言逐字点亮: skills/frontend-styles/open-design/statement-reveal.md
- 滚动锁定步骤联动: skills/frontend-styles/open-design/scrolly-steps.md
- 可拖拽贴纸: skills/frontend-styles/open-design/drag-sticker.md
- 磁性 Dock 预览切换: skills/frontend-styles/open-design/magnetic-dock.md
- 图标物理掉落: skills/frontend-styles/open-design/falling-chips.md
- 点阵地球与贡献者轨道: skills/frontend-styles/open-design/globe-orbit.md
- 贴纸统计卡与数字滚动: skills/frontend-styles/open-design/stat-cards.md
- 页底渐进高斯模糊: skills/frontend-styles/open-design/gradual-blur.md
- HAOQI 3D 官网拆解:
- skills/frontend-styles/haoqi/index.md
- 3D 开场专题: skills/frontend-styles/haoqi/01-opening.md
- 第二个特效(预留): skills/frontend-styles/haoqi/02-effect.md
- 第三个特效(预留): skills/frontend-styles/haoqi/03-effect.md
- ORYZO 官网拆解:
- skills/frontend-styles/oryzo/index.md
- 蓝图开场动画: skills/frontend-styles/oryzo/blueprint-intro.md
- 高斯泼溅渲染系统: skills/frontend-styles/oryzo/splat-system.md
- 滚动叙事与场景切换: skills/frontend-styles/oryzo/scene-morph.md
- Wearable 一幕: skills/frontend-styles/oryzo/wearable-scene.md
- RGB 呼吸光边: skills/frontend-styles/oryzo/rgb-glow-border.md
- 和 AI 协作:
- skills/collab/index.md
- CLAUDE.md 初始化模板: skills/collab/claude-md-template/index.md
- 盘问我: skills/collab/grilling/index.md
- 带文档盘问:
- skills/collab/grill-with-docs/index.md
- Domain Modeling: skills/collab/grill-with-docs/domain-modeling/SKILL.md
- CONTEXT.md 格式: skills/collab/grill-with-docs/domain-modeling/CONTEXT-FORMAT.md
- ADR 格式: skills/collab/grill-with-docs/domain-modeling/ADR-FORMAT.md
nav 写法约定
nav:
- 首页: index.md # - 显示名: 路径
- Skills: # 子板块(可嵌套)
- FastAPI 后端:
- skills/fastapi/index.md # 不带显示名 = 用文件一级标题
- reference:
- 数据库: skills/fastapi/reference/数据库.md
- 显示名: 路径→ 自定义导航名- 路径.md→ 自动取文件一级标题- 路径相对于
docs/
内容放哪个板块:三问判定
顶层板块按用途和知识性质分——文章(完整可读的复盘、观点与方法整理)、笔记(专业科目的学习与推导)、Skills(给 AI 的成套资产);主题只做板块内的二级目录。新内容按顺序问三个问题:
文章和 skill 是共生不是成熟度递进:复盘归文章,复盘里沉淀出的可复用手册归 skill,正文互链——例:动画 PPT 复盘与它的工程手册。板块归属变了就把文件真的搬过去,让磁盘和分类保持一致。不留旧地址存根页 —— 要保住旧 URL 就别挪文件,挪了就直接删,理由见上文写作规范那条。
从 MkDocs Material 切换到 Zensical 几乎零成本
mkdocs.yml / markdown_extensions / 主题 features 全部继续工作。
命令 zensical build / zensical serve 代替 mkdocs build / mkdocs serve,速度快 4-5 倍。
按 Claude skill 标准组织 wiki 目录(本 wiki 沿用的规范)
本 wiki 的 "Skills" 系列直接沿用 Anthropic 官方 skill 标准目录布局,
让仓库结构跟 ~/.claude/skills/ 一一对应 —— AI 读到的目录形态就是它熟悉的 skill 形态。
目录分两层,角色不同。
分类目录(writing/、collab/ 这一层)只是装 skill 的普通文件夹:一个 index.md 做索引(几行链接),下面每个子目录是一个独立 skill。分类自己不是 skill,没有 assets/ / reference/。
skill 目录 = 一个文件夹,标准三件套(reference 和 assets 必须平级):
| 路径 | 角色 | 等价于 skill 原型的 |
|---|---|---|
index.md |
主入口(自写 skill 的主控;吸收型 skill 直接就是上游 SKILL.md 原文) |
SKILL.md |
assets/ |
资产真身(CSS / JS / SVG 等) | assets/ |
reference/ |
详细参考,按主题拆多个 .md(只有大 skill 需要,见下) |
references/ |
scripts/ |
可执行脚本(按需,wiki 这边一般不用) | scripts/ |
怎么判断是"一个 skill 带 reference"还是"分类装多个 skill":看子页之间的关系。
FastAPI 的 14 个章节、Dashboard 的 36 个形状都服务同一个主题,是一个 skill 的按需加载参考 —— 用 index.md + reference/。
写作口味下的 MkDocs 文档 / 报纸版 / 去 AI 味互不相干,谁也不是谁的参考 —— 那是三个独立 skill,各占一个目录。
把独立 skill 塞成别人的 reference 是层级错位(本 wiki 2026-07 重构前就犯过这个错:qu-ai-wei 自带 9 份 references 的完整 skill,被压成"writing 的一篇 reference")。
示例:写作口味分类
docs/skills/writing/ # 分类目录
├── index.md # 分类索引,几行链接
├── mkdocs-wiki/ # skill:MkDocs Wiki 写作(本页)
│ ├── index.md
│ └── assets/react-spa-demo.html
├── newspaper/ # skill:报纸版 HTML
│ ├── index.md
│ └── assets/ # newspaper.css / .js / favicon.svg / demo
└── qu-ai-wei/ # skill:吸收自上游的去 AI 味
├── index.md # = 上游 SKILL.md 原文照录(真身就是入口)
├── references/ # 上游依赖文件,按原相对路径平级归档
└── MIRROR.md # 来源与吸收说明(目录里唯一自己写的文件)
吸收外部 skill 的归档规矩:skill 目录镜像上游布局——主文件 SKILL.md 原文照录、仅改名为 index.md(真身就是入口,不另写落地文);它引用的全部依赖文件(references/、依赖的其他 skill)按上游相对路径与 index.md 平级归档,主文件内部的相对链接因此原样有效;一律钉 commit 原文照录。安装脚本 / 多语言翻译 / CI 这类非行为定义可跳过,但取舍要写进 MIRROR.md(记上游仓库、commit 永链、吸收日期、漂移处置,是目录里唯一自己写的文件)。skill 目录名对齐上游 slug(如 qu-ai-wei、grill-with-docs)。
人读的介绍压到分类索引一句话:吸收型 skill 的页面本身是给 AI 看的真身,人真正需要读的介绍——这个 skill 干什么、亮点在哪、吸收自谁——写在分类 index.md 的链接后面一句话即可,不单独维护落地文或中译页(2026-07-09 废除此前"落地文在前、SKILL.md 真身挂 nav 附件在后"的两层做法:人读内容太少,不值得两个页面)。nav 里吸收型 skill 的依赖文件(references/、依赖的其他 skill)跟大 skill 的 reference/ 同等待遇——展平进该 skill 的小节:index.md(真身)在前、依赖平铺在后;无依赖的 skill(如 grilling)就是平条目。MIRROR.md 不进 nav,从分类索引可达。
上游 README 不归档 —— wiki 里不需要两个诉说者。README 是上游作者写给"在 GitHub 上决定要不要装的人"的介绍与安装说明,这两个任务在本 wiki 都不存在:向读者介绍这个 skill 的是分类索引里的一句话(自己的声音),定义行为的是归档的主文件。把 README 也归档,等于让上游作者在自己的 wiki 里再讲一遍,还要替它维护死链归一化。要溯源上游原貌,在 MIRROR.md 里留一条钉 commit 的 README 永链即可。
几条硬性约束
- 带
reference/的大 skill,index.md只是入口/引导,写两三行 + 几个链接指向reference/就够了 —— 不要用--8<--把reference/全文塞进index.md。每个 reference 自己作为独立 URL 存在, index 是个目录页,不是合订本;单文 skill 的index.md本身就是全文 index.md控制在 500 行以内(目录页性质的通常只有 10-20 行)reference/和assets/都是"分类目录":reference/放具体文章(展平进父级 nav,见下文),assets/只放资产真身(css/js/svg 等)。两者都不在 nav 里单独成层,因此不需要reference/index.md/assets/index.md占位页reference/只保留一层深(不要reference/X/Y.md,Claude 可能只head -100预览嵌套引用导致漏读)- 资产放它所属 skill 自己的
assets/,跟该 skill 的reference/平级 —— 不要把多个 skill 的资产 混进分类级的共享assets/(重构前writing/assets/就这么混过,newspaper 的 css 和 qu-ai-wei 的真身挤在一起,归属只能靠文件名猜) - 长 reference 文件(>100 行)顶部加目录,方便 Claude 预览时看到全貌
assets/下的代码用注释自我说明(设计令牌、组件类等放 CSS 注释里),markdown 端只做引导,不要把代码细节用 markdown 重述- snippets 引用 assets 的代码块一律用
??? abstract折叠(默认收起),避免长配置/长 CSS 把文章撑到看不到正文 - 原文一律放
assets/,markdown 不要手抄。展示给前端读者的方式是--8<--snippets 引用 assets 里的源文件(构建时把原文塞进 HTML),不算"写两份" —— 源文件只在assets/一处,markdown 里只写--8<-- "docs/.../assets/foo.css"这样的引用。绝对禁止把 yaml/css/js 内容直接复制粘贴到 markdown 代码块里跟assets/foo.yaml同时存在 —— 那才是双份维护 - 完整独立代码文件不单独成页、不进 nav:要展示完整文件,就在讲它的 reference 文章 / post 里用
??? abstract折叠 +--8<--引真身(如文末折叠展示--8<-- "deploy.sh")。正文只放教学片段,完整文件折叠在文末就近查看即可 - 真身位置看它是否要被运行:skill 专属资产(
newspaper.css/.js/.svg)真身放assets/、可按文件 URL 直接下载;项目运行文件(deploy.sh/mkdocs.yml/pyproject.toml/deploy-cert/*等)真身必须留仓库根才能跑、被 zensical 读、靠相对路径定位项目根 —— 文章里一律--8<-- "mkdocs.yml"引真身,绝不复制一份进assets/(复制 = 双写,会脱节) - markdown 写法不要"写法 + 效果"双写:每种语法直接演示使用即可 —— AI 读源文件看到的是 markdown 写法,前端读者看到的是渲染效果,一份内容自然两个视图
reference / assets 都不在 nav 单独成层 → 不需要它们的 index.md 占位
mkdocs 的 nav: 项必须指向 .md,所以一个目录想在 nav 里成为折叠节点,就得有个 index.md。
但本 wiki 的规范是:reference 展平进父级 nav、assets 根本不进 nav(只存真身),两层都不在 nav 出现,
于是 reference/index.md / assets/index.md 这两个占位页都不需要。
(skill 自己的入口 index.md 照常保留,那是 skill 的门面,不是目录占位。)
nav 一律把 reference 展平到父级
mkdocs.yml 的 nav: 不会自动反映文件系统的目录结构 —— 它完全是手写的视觉组织。
本 wiki 的规范是:reference 这一层始终不在 nav 里出现,下面的文件直接列到父级(skill 主目录下)。
写法:
- 写作口味: # 分类
- skills/writing/index.md # 分类索引
- MkDocs Wiki: skills/writing/mkdocs-wiki/index.md # ← 每个 skill 的 index 直接列在分类级
- 报纸版 HTML: skills/writing/newspaper/index.md
- 去 AI 味: # ← 吸收型 skill:index.md 即真身
- skills/writing/qu-ai-wei/index.md # 真身在前
- 51 条 AI 腔模式: skills/writing/qu-ai-wei/references/patterns.md # ← references 照样展平,
- 白名单: skills/writing/qu-ai-wei/references/whitelists.md # 9 份全列(此处略)
# MIRROR.md / assets 不进 nav —— 从分类索引 / 页面内链接可达
- FastAPI 后端: # 大 skill:reference 展平
- skills/fastapi/index.md
- 项目布局: skills/fastapi/reference/项目布局.md # ← 直接放父级,
- 数据库: skills/fastapi/reference/数据库.md # 哪怕 14 个子页也展平
- ...
效果:
Skills
├── FastAPI 后端
│ ├── (主索引 = FastAPI 后端入口)
│ ├── 项目布局
│ ├── 数据库
│ └── ... # reference 直接展开,无 "reference" 折叠节点
└── 写作口味
├── (分类索引)
├── MkDocs Wiki
├── 报纸版 HTML
└── 去 AI 味 # 吸收型 skill:入口即 SKILL.md 真身
├── (真身入口)
├── 51 条 AI 腔模式 # references 展平,跟 reference/ 同等待遇
└── ...
为什么一律展平
- 多一层"reference"折叠让读者多一次点击,体感"还没进去内容"
- reference 数量多(如 FastAPI 14 个)时,让父级 nav 长 —— 但这正符合读者预期, 他们能一眼看到全部主题。多一层折叠反而隐藏信息
- nav 视觉跟文件系统解耦是本设计的核心 —— 把 reference 这层从 nav 拿掉不影响:
- 文件位置:仍在
docs/skills/<skill>/reference/<topic>.md - URL:仍是
/skills/<skill>/reference/<topic>/ - snippets 引用路径:仍是
docs/skills/<skill>/reference/<topic>.md - AI 读仓库看到的目录结构:仍是标准 skill 布局
index.md + reference/ + assets/
- 文件位置:仍在
assets/ 不进 nav,无例外 —— 它只是资产真身(css/js/svg 等)的存放目录。
资产的原文展示放在讲它的 reference 文章里(??? abstract 折叠 --8<--),真身本身可按文件 URL 直接下载。
(吸收型 skill 的真身不存在"藏在 assets 里要不要进 nav"的纠结——它就是 index.md 本身,见上文吸收规矩。)
关键认知:nav 是视觉组织,文件系统是真实结构,两者解耦。 怎么调 nav 都不改变文件 URL,也不影响 AI 读仓库时看到的 skill 标准布局。
保留名
禁止使用 reference / assets / scripts 作为 .md 文件名 —— 它们会跟同级目录撞 URL,内容被静默丢弃。
代码块复制按钮:主题内置,无需插件
mkdocs.yml 配 content.code.copy 即可。按钮不在静态 HTML 里——由主题 JS bundle 在浏览器端运行时注入:找到 <code> 最近的 <pre> 祖先、赋 id,把 clipboard 目标指向 #<id> > code,整条链路不依赖 .highlight wrapper(那个 wrapper 只有代码注解 annotate 才需要),所以 Zensical 生成的裸 <pre><code> 结构也照常工作。因此"查看页面源码没看到按钮"不代表功能没生效,浏览器里打开才见真章。