mcpField
W
web-content-audit

web-content-audit

github.com/kevintsai1202测试与质量5263 次安装24 周前更新
代码仓库

关于 web-content-audit

用于编写审计脚本,对照源文件与部署数据,并生成供人工审阅的报告。涵盖跨文件引用、素材缺口、内容偏移、ID 稳定性及构建产物检查;审计用于提供信息,不阻止发布。

主要功能

  1. 01跨文件引用检查
  2. 02素材覆盖与缺口报告
  3. 03源内容与部署内容偏差检查
  4. 04ID 稳定性检查
  5. 05构建产物检查

适用场景

  1. 01发布前检查材料引用
  2. 02找出可能需要插图的章节
  3. 03对照企业版内容与源文件
  4. 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 差异比较,可选 PlaywrightPlaywright + 断言
失败模式始终以 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 有一个介绍“如何阅读审计报告”的章节(链接到上述规范)。
  • 用户知道应在发布里程碑时运行审计,而不是每次提交都运行。

评论

支持 Markdown