关于 web-content-audit
用于编写审计脚本,对照源文件与部署数据,并生成供人工审阅的报告。涵盖跨文件引用、素材缺口、内容偏移、ID 稳定性及构建产物检查;审计用于提供信息,不阻止发布。
主要功能
- 01跨文件引用检查
- 02素材覆盖与缺口报告
- 03源内容与部署内容偏差检查
- 04ID 稳定性检查
- 05构建产物检查
适用场景
- 01发布前检查材料引用
- 02找出可能需要插图的章节
- 03对照企业版内容与源文件
- 04检查生成的 DOCX 或 PDF 交付物
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ kevintsai1202/ teaching-site-skills --skill web-content-audit安装后在 Claude Code 中输入/web-content-audit确认已加载。
详细介绍
网页内容审计
Schema 权威依据:跨文件一致性规则(素材三处同步、任务 ID 稳定性、测验重新编号陷阱)已编入
_shared/domain-primitives.md§13。本技能中的审计脚本将这些规则付诸实践。文件名约定(英文优先):审计报告存放在
data/audit-*.md下;脚本存放在scripts/audit-*.mjs下。
此技能用于生成审计脚本,读取源文件和已部署数据,对两者进行比对,并输出便于人类阅读的报告。目的不是把关部署(那是验证的工作),而是找出偏差,供人审查。
何时调用
- 重大内容发布前(在学生看到之前,检查大纲 ↔ 课程数据 ↔ 视觉素材是否存在偏差)。
- 添加或移除单元/素材/测验题目后(重新检查三处同步规则)。
- 将企业版交付给客户前(确认精简版 COURSE 与其源内容一致)。
- 用户感觉“哪里不对劲”却找不到运行时错误时——审计用于发现看不见的偏差。
- 规划下一轮迭代时(例如,“哪些章节需要新插图?”)。
如果问题是“页面是否正确渲染?”,请勿调用——这属于 web-visual-verification。
审计与验证——运行模式差异
| 特征 | 审计 | 验证 |
|---|---|---|
| 输出 | Markdown 报告(供人阅读) | 通过/失败(由 CI 把关) |
| 工具 | 文件读取、正则表达式、JSON 差异比较,可选 Playwright | Playwright + 断言 |
| 失败模式 | 始终以 0 退出;报告会告诉人哪些问题值得修复 | 状态不符合要求时以非零状态退出 |
| 运行时机 | 手动运行,在重要节点执行 | 每次 PR/部署前 |
| 思维模型 | “告诉我哪些地方出现了偏差” | “不要让问题再次出现” |
如果脚本以 assert.* 或 process.exit(1) 结束,那就不是审计——应将其移至 web-visual-verification。
五类审计
审计 1:跨产物引用解析
教学网站中常见的问题:course-data.js:materials[] 引用了磁盘上不存在的文件,或者 getMaterialUrl() 缺少规则。
// scripts/audit-material-references.mjs
import fs from 'node:fs/promises';
import path from 'node:path';
// Read course-data.js (via vm or regex extract)
const COURSE = await loadCourse('./course-data.js');
const report = ['# Material reference audit\n'];
for (const m of COURSE.materials) {
// 1. Does the file router know about it?
const routerCovers = await checkRouterCoverage(m.name);
// 2. Does the actual file exist?
const fileExists = await checkFileExists(m);
// 3. Is it referenced in any unit's materials[]?
const referencedInUnit = findReferencingUnits(COURSE, m.id);
if (!routerCovers || !fileExists || referencedInUnit.length === 0) {
report.push(`## ⚠️ ${m.id}: ${m.name}`);
report.push(`- Router: ${routerCovers ? '✅' : '❌'}`);
report.push(`- File: ${fileExists ? '✅' : '❌'}`);
report.push(`- Referenced in units: ${referencedInUnit.join(', ') || '❌ NONE'}`);
}
}
await fs.writeFile('data/audit-materials.md', report.join('\n'));
输出会写入 data/audit-*.md;人类打开报告后,自行决定要修复什么。
审计 2:资源覆盖率/缺口分析
“哪些章节还没有插图?”——为后续工作提供指引,不以此判定失败。
// scripts/audit-illustrations.mjs
// Walks every .chapter / .accordion-content; counts text length, image count, list density
// For each section: if (textLength > 500 && imageCount === 0) → candidate for illustration
// Output: data/illustration-audit.md (a markdown list with section titles + screenshots)
这类审计可以使用 Playwright 截图(捕获模式——不执行断言),但产出是缺口清单,而不是截图。不要将此与 web-visual-verification 的 capture-*.mjs 混为一谈,后者不会产生分析结果。
审计 3:内联数据 ↔ 源文件偏差(企业版)
当企业版将 window.COURSE 内联在 index.html 中时,它可能与精简前的源文件出现偏差。审计两者差异:
// scripts/audit-corp-content.mjs
const inlined = await loadCourseFromIndexHtml('corporate-editions/client_6h/index.html');
const source = await loadSourceMarkdown('course-package/');
const report = ['# Corporate edition content audit\n'];
// Compare meta
report.push(`## Meta`);
report.push(diff(inlined.meta, source.meta));
// Per-unit: deliverables / prompts / tasks count
for (const day of ['day1', 'day2']) {
for (const unit of inlined[day].units) {
report.push(`## ${unit.id}: ${unit.title}`);
report.push(`- Prompts: ${unit.prompts?.length || 0}`);
report.push(`- Tasks: ${unit.tasks?.length || 0}`);
report.push(`- Materials: ${unit.materials?.length || 0}`);
}
}
await fs.writeFile('data/audit-corp-content.md', report.join('\n'));
示例工作坊中的 audit-corp-6h-content.mjs 正是这样做的——它会生成 Markdown 转储,让讲师在每场企业培训前快速浏览。
审计 4:ID 稳定性与复用
task.id、quiz.id、unit.id 都是稳定契约(localStorage 键、内部引用)。对它们进行审计:
// scripts/audit-id-stability.mjs
// 1. Collect all task IDs across course-data.js — check no duplicates
// 2. Read git log for course-data.js — flag any task ID that was renamed (not just deleted)
// 3. Cross-check quiz[N].sourceUnit references actual unit IDs
// 4. Cross-check materials[].id appears in at least one unit's materials[]
这类审计可以捕获“我们不小心把 d2-u3-t1 重命名为 d2-u3-task1”这类问题——它会在所有学生那里悄无声息地清空 localStorage 中的学习进度。
审计 5:产物检查
当流水线生成二进制交付物(DOCX、PDF、zip)时,检查其内部结构,尽早发现构建故障。
// scripts/inspect-docx-images.mjs
import JSZip from 'jszip';
const zip = await JSZip.loadAsync(await readFile('dist/ebook.docx'));
const images = Object.keys(zip.files).filter(f => f.startsWith('word/media/'));
console.log(`📷 內嵌圖片:${images.length} 張`);
for (const f of images) {
const size = zip.files[f]._data?.uncompressedSize || 0;
console.log(` ${f} ${(size / 1024).toFixed(1)} KB`);
}
如果预期有约 30 张图片,但 DOCX 中只有 3 张,那构建过程中就悄无声息地出了问题。这样比打开 Word 再滚动查找快得多。
输出约定
data/
├── audit-materials.md ← Audit 1
├── audit-illustrations.md ← Audit 2 (gap analysis)
├── audit/section-*.png ← ↳ supporting screenshots
├── audit-corp-content.md ← Audit 3
├── audit-id-stability.md ← Audit 4
└── audit-ebook-inspection.txt ← Audit 5
所有审计输出都是可纳入版本控制的 Markdown(或 JSON)。在发布里程碑时提交这些文件;比较不同发布版本的差异,就能知道“实际更改了哪些内容”。
让报告便于快速浏览
审计人员会快速浏览,而不是逐字阅读。报告结构应便于快速浏览:
# {Audit name} — {timestamp}
## Summary
- ✅ 24 of 30 materials fully wired
- ⚠️ 4 materials missing router rules
- ❌ 2 materials referenced but file missing
## ⚠️ Warnings (need attention but not blocking)
...
## ❌ Errors (likely broken in production)
...
## ℹ️ Notes (FYI)
...
摘要放在最前面,并为每个章节标题加上表情符号前缀。审计的目标是成为世界上最高效的代码审查。 ##“三处同步”审计(对教学网站至关重要)
模式源自 static-spa-conversion:每项资料都必须出现在(1)文件系统、(2)course-data.js:materials[]、(3)getMaterialUrl() 路由中。审计脚本:
const fsFiles = new Set(await fs.readdir('course-package/materials/'));
const dataMaterials = new Set(COURSE.materials.map(m => routerNameToFile(m.name)));
const routerCoverage = extractRouterRules(indexHtmlContent);
const inFsNotInData = [...fsFiles].filter(f => !dataMaterials.has(f));
const inDataNotInFs = [...dataMaterials].filter(f => !fsFiles.has(f));
const inDataNotInRouter = [...dataMaterials].filter(f => !routerCoverage.has(f));
// Emit a 3-column markdown table showing each file's coverage in each of the three places.
这个脚本就能捕获 90% 的“资料链接失效”问题。
##审计不阻止发布——只提供信息
最严格的原则:审计脚本始终以 0 退出,即使发现问题也是如此。原因如下:
- 审计会定期运行;你不会希望 CI 因为一个已知的“下个版本再修复”问题而变红。
- 审计会揭示需要判断的问题(例如“这个小节没有插图——这是有意的吗?”),不应由自动化来决定。
- 审计失败会让人觉得这是验证;于是人们会把审计当作门禁,报告最终就会沦为“没看”的摆设。
如果某个问题必须阻止发布,就把它升级为验证脚本(web-visual-verification),并使用 assert.*。审计提供信息;验证把关。
##何时进行审计(频率)
| 触发条件 | 要运行的审计 |
|---|---|
| 添加/移除单元 | id-stability、material-references |
| 添加资料文件 | material-references(尤其是三处同步) |
| 重新编号测验 | id-stability + 手动检查硬编码的 (N題) 字符串 |
| 移交企业版之前 | corp-content(行内 ↔ 源文件)+ material-references |
| 发布电子书之前 | inspect-docx-images / inspect-pdf-pagecount |
| 季度维护 | illustrations(差距分析)→ 规划下一个内容周期 |
##反模式
- 使用
assert.*/以非零值退出的审计脚本——那是验证,不是审计。把它移走。 - 无人阅读的审计报告——保持简短,摘要计数放在开头,并大量使用表情符号以便快速浏览。
- 一个审计包办五项工作——拆分。
audit-materials.mjs和audit-illustrations.mjs会为不同决策生成不同报告,并保存到不同文件夹。 - 在 CI 中把审计当作验证运行——它们会变成噪声,被屏蔽,最终毫无用处。按需运行,或在发布里程碑时运行。
- 报告中没有时间戳/版本——比较两次审计时,你需要知道哪次更新。
##交接
此技能完成时:
scripts/中包含一组audit-*.mjs(及配套的inspect-*.mjs)脚本。- 脚本输出保存到
data/audit-*.md(供工具读取时使用.json)。 - 项目中的 README 有一个介绍“如何阅读审计报告”的章节(链接到上述规范)。
- 用户知道应在发布里程碑时运行审计,而不是每次提交都运行。