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を入力して読み込まれたか確認します。
詳細
Web コンテンツ監査
スキーマの基準:ファイル間の一貫性ルール(教材の3か所同期、タスク 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 に移してください。
5つの監査カテゴリ
監査 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)
...
最初に概要を示し、すべてのセクション見出しの先頭に絵文字を付けます。監査の役目は、世界で最も効率的なコードレビューになることです。
「3 か所同期」監査(教育サイトでは重要)
static-spa-conversion のパターンでは、すべての教材が次の 3 か所に存在する必要があります。(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 を赤くしたくありません。
- 監査では、判断を要する問題(例:「セクションにイラストがありません。これは意図的ですか?」)が表面化しますが、自動化で判断すべきではありません。
- 監査が失敗すると、検証のように感じられます。その結果、人は監査をゲートとして扱い、レポートは「読まれない」状態に陥ります。
問題がリリースをブロックしなければならない場合は、assert.* を使う検証スクリプト(web-visual-verification)に移してください。監査は情報を提供し、検証はゲートとして機能します。
監査の実施タイミング(頻度)
| トリガー | 実行する監査 |
|---|---|
| 単元の追加・削除 | id-stability、material-references |
| 教材ファイルの追加 | material-references(特に 3 か所同期) |
| クイズの番号変更 | id-stability + ハードコードされた (N題) 文字列を手動で確認 |
| 法人向け版の引き渡し前 | corp-content(インライン ↔ ソース)+ material-references |
| 電子書籍の公開前 | inspect-docx-images / inspect-pdf-pagecount |
| 四半期ごとのメンテナンス | illustrations(ギャップ分析)→ 次のコンテンツサイクルを計画 |
アンチパターン
assert.*またはゼロ以外の終了コードを使う監査スクリプト — これは監査ではなく検証です。移動してください。- 誰も読まない監査レポート — 簡潔にし、集計数を最初に示し、ひと目でわかるように絵文字を積極的に使ってください。
- 5 つの役割をこなす 1 つの監査 — 分割してください。
audit-materials.mjsとaudit-illustrations.mjsは、異なる判断のために異なるフォルダーへ異なるレポートを出力します。 - 検証と同じように CI で監査を実行する — ノイズとなり、無効化され、役に立たなくなります。必要に応じて、またはリリースの節目に実行してください。
- レポートにタイムスタンプ/バージョンがない — 2 つの監査実行結果を比較する際、どちらが新しいかを知る必要があります。
引き継ぎ
このスキルの完了時には、次の状態になっています。
audit-*.mjsスクリプト(および補助的なinspect-*.mjsスクリプト)がscripts/にある。- その出力が
data/audit-*.md(ツールでの利用を想定する場合は.json)に保存される。 - プロジェクトの README に「監査レポートの読み方」を説明するセクションがある(上記の規約へのリンクを含む)。
- ユーザーが、すべてのコミットごとではなく、リリースの節目に監査を実行することを把握している。