1仕組みと動作原理
skill全体の骨格は「レンダリング証拠階層制」です。A=実際の可視ブラウザ/ネイティブウィンドウによる操作、B=同型DevToolsまたはプロジェクト独自のE2E、C=同梱スクリプトによるクリーンなヘッドレス機械スキャン、D=ソースコード/lint/ビルド/単体テスト。下位レベルの証拠を上位の強い結論に昇格させることは明示的に禁止されています。同梱のPlaywrightスキャンはLevel Cと明確に位置づけられており、最終判定ではありません。
| C | Fresh headless mechanical sweep from the bundled script | Overflow, wrapping, images, basic layout heuristics, screenshot candidates |
同梱スクリプトは意図的にnpm依存関係を一切持たず、Playwrightは**監査対象プロジェクト**のnode_modulesから解決されます。まずcreateRequire(監査対象プロジェクト/package.json)を試し、その後skill自身のディレクトリへとフォールバックします。プロジェクト内にPlaywrightが存在しない場合は、インストールを行うのではなくグレースフルに降格し、その旨を正確に報告します。
createRequire(`${process.cwd()}/package.json`), // the audited project
静的解析ではなく、auditPage検出関数全体がpage.evaluate経由で実際のChromiumページ内に注入されて実行されます。getBoundingClientRect、scrollWidth-clientWidth、getComputedStyle、checkVisibilityを用い、本物のレンダリングエンジン上で値を取得して構造化されたissuesを返却します。
const result = await page.evaluate(auditPage, {
「文字が実際にレンダリングされたか」は1文字単位で判定されます。visibleTextFromNodeがRangeを用いて各文字のバウンディングボックスを個別に取得し、祖先のoverflow:hidden/clipによるクリッピング矩形を順次減算し、クリップされた文字を空白に置き換えます。そのため、--forbid/--requireが検査するのはtextContentではなく、実際に目に見えるテキストです。
range.setStart(node, index);
オーバーフローとクリッピングは「疑わしい要素の列挙」ではなく数値計算で処理されます。要素レベルのoverflowXはel.scrollWidth - el.clientWidth、ページレベルはdocumentElement.scrollWidth - clientWidth、セクションレベルはscrollWidth-clientWidthおよびrect.width - documentElement.clientWidthの2つの基準で個別に算出し、閾値を超えた場合のみpx数値を伴う項目を生成します。
const overflowX = el.scrollWidth - el.clientWidth;
メディアの欠陥は合否判定ではなく、selected / rendered / notRendered / loaded / broken / stillLoading / deferredLazy / missingSourceのように状態別に細分化されます。また、「ファーストビュー外 + lazy + 未リクエスト」はdeferredLazyとして独立して分類され、未リクエストの画像を破損画像と誤認するのを防ぎます。
const isDeferred = !mediaWalkRequested
「サイレントデグラデーション(静的な機能低下)」の検出には、APIの自己申告を信じるのではなく反証可能な測定を採用しています。fontモードではラテン文字を含むプローブ文字列をレンダリングし、意図的に存在しないフォントファミリの幅との差分を計算します。document.fonts.check()は対照用としてのみ出力されます(@font-faceが一切存在しない場合でもtrueを返すためです)。
const sentinel = measure('"NoSuchFamily__PROBE__"');
fontモードは5つの排他的な結論(platformGeneric / healthy / hostProvidedOnly / declaredButUnusable / absentStackEntries)を出力し、hostProvidedOnlyと破損アセットのみを欠陥と判定します。「バンドル済みで正常」と「未リリース」を混同すると、ユーザーが本来利用可能なフォールバックを削除してしまうリスクがあるためです。
const hostOnly = custom.filter((f) => f.actuallyRenders && !f.shippedByPage);
classモードは、「コンポーネントライブラリのメジャーバージョン変更に伴う改名により、CSSルールセット全体が永久にマッチしなくなる」といったエラーの出ない劣化を検出します。プロジェクトのCSSから参照されている.ant-*クラス名を抽出し、インストールされているライブラリのCSS内に存在するかを1件ずつ確認し、ヒット数ゼロのものをdeadと判定します。
Rules targeting them match nothing and fail silently — the component keeps the library's stock look.
証拠の規律はプロンプトだけでなくコードレベルで徹底されています。レポート内のURLはすべて構造化マスキング(origin + /<redacted-path> + クエリキー名 + #<redacted-fragment> のみ保持)され、ターゲットに関連する機密トークンはレンダリングテキストからマスクされます。headerは環境変数を経由してのみ渡すことができ、直接指定された--headerはブラウザ起動前に拒否されます。
process.stderr.write('--header is disabled; use --header-env for every header value\n');
2コア機能
3外部依存関係
| Kind | Dependency / Endpoint |
|---|---|
| package | Playwright(playwright-core / playwright / @playwright/test 三选一,从被审计项目解析) |
| cli | 本机 Chrome / Chromium / Edge 可执行文件(findChrome 的候选路径,可用 CHROME_PATH 覆盖) |
| network | 被审计目标 URL 及其子资源(唯一网络目的地,由调用者经 --url 指定) |
| cli | Node.js 运行时与内置模块(node:crypto/fs/os/path/url/module);自带回归用 node --test |
4推奨統制措置と判定根拠
- 本地凭据 / 浏览器配置: ユーザーのログインCookie/セッション/キーチェーンの読み取り: --storage <playwright-storage-state.json>:ディスク上のPlaywrightログイン状態ファイル(Cookie/を含む)を読み取り
- scripts/: ローカルスクリプトまたはコマンドの実行: node scripts/visual_layout_audit.mjs(メインスキャナー:headless Chromiumを起動し、auditPageを注入
- 网络端点: 外部ネットワークアクセスの宣言: page.goto(--url)(visual_layout_audit.mjs)およびその後にページ自身が開始するサブリソースリクエスト—接続先は監査対象オリジンのみ
- 本地工作目录: ファイルの読み書きと成果物の出力: <outDir>/<viewport>.png および <outDir>/frontend-visual-qa-report.json(issues、を含む
5二重目の独立検証
- [ok] external_deps 逐条反查调用点是否真实存在 — grep 工具全量扫 scripts/:Playwright 候选数组(playwright-core / playwright / @playwright/test)、findChrome 的 process.env.CHROME_PATH、page.goto(url, { waitUntil, timeout: 30_000 })、SKILL.md 的 node --test 四处均逐字定位到文件行;网络 token 扫描只命中两处——probe 的 route.fetch({ headers: requestHeaders, maxRedirects: 0 }) 与 visual_layout_audit.mjs 用法字符串里的 http://localhost:port/ 示例,无第三方端点,无 axios / XMLHttpRequest / WebSocket / child_process / new Function。
- [discrepancy] 安全结论反例搜索:是否存在未披露的外发/降级/隐藏脚本 — 主体结论(无第三方外发、无遥测、默认只读)成立,但发现两处 SKILL.md 未在安全语境点明的代码级能力:① probe 经 route.fetch 把 --header-env 提供的凭证以请求头形式发到目标 origin(值取自 process.env,不进 argv),文档只强调『argv 只含名字不含值』,未说『值会被转发给目标站』;② probe 有 --ignore-https-errors(ignoreHTTPSErrors: has('ignore-https-errors')),会关闭该上下文的 TLS 校验——SKILL.md 只在 sweep 段落自述『TLS stays enabled except for an explicitly trusted self-signed origin』,未提 probe 侧同一开关。两者都是显式 opt-in、需调用者自己写出 flag;未发现混淆内容、后门或隐藏脚本。
- [ok] 凭证读取与文件写入面逐条复核 — credential_reads 三条、file_writes 三条均落到代码行:process.env[envName](probe)、storageState: flag('storage') || undefined、findChrome 的 CHROME_PATH/PROGRAMFILES/LOCALAPPDATA;写入侧为 outDir(默认 os.tmpdir()/frontend-visual-qa-<ISO>-<pid>)、frontend-visual-qa-report.json、截图 PNG。脚本不读 cookie 数据库、不读 keychain、不读 .env,也无删除行为。
- [ok] 文件清单 glob 全量核对(含隐藏文件) — find -type f 得 24 个文件:SKILL.md 1 + scripts 3 + tests 2 + references 7 + evals/evals.json + evals/trigger-evals.json + evals/files 13(12 HTML + static-review.png);find -name ".*" 在 skill 目录内为空(无隐藏文件、无 .env、无嵌套仓库)。与 SKILL.md『Bundled Resources』清单一一对应,没有未列出的可执行文件。
- [ok] 『实现原理』是否夸大:功能声明 vs 实际代码能力 — SKILL.md 称自带脚本为 'Playwright-powered mechanical viewport, layout, and media-state sweep with screenshots and JSON evidence',代码确为 chromium.launch + 三档视口 + 截图 + JSON 报告;report.checked 列的七项(navigation status and final URL、effective viewport and meta viewport、page and section overflow、rendered text wrapping and clipping candidates、custom interactive-role focusability candidates、rendered image request/load state and aspect candidates、page-type contract heuristics)在 auditPage 内逐条可定位到具体计算与 push。反向也不夸大:脚本在 report.notChecked 里主动列出做不到的项(真实浏览器 chrome 与打印预览、下载/剪贴板/弹窗/原生对话框、键盘旅程与焦点外观、完整 WCAG、GIS 交互、主观 parity),自述边界严于对外宣称。
- [discrepancy] SKILL.md 关于『报告只写进唯一临时运行目录』的措辞与代码是否一致 — 默认路径确实唯一(os.tmpdir()/frontend-visual-qa-<ISO 时间戳>-<pid>,且拒绝非空 outDir 以防陈旧证据),但 --out 允许任意路径,脚本层不限制它落在被审计项目内;SKILL.md 的『Do not mutate the audited project merely to run the sweep』只是提示词层约束,代码不强制。属措辞与实现的轻微落差,非缺陷。
- [ok] 注入面复核:被审计页面内容是否会进入可执行路径 — 四个注入浏览器的函数体全部来自 skill 自带文件(auditPage / collectAttentionInventory / primeVisibleMedia / collectScreenshotSections),页面内容只作为数据被 Range 测量、正则匹配、结构描述,不参与代码执行;无 eval、无 new Function、无 child_process,也没有把页面文本拼进 argv/shell 的路径(probe 的 --select 只交给 page.locator)。残余风险在模型侧(页面可埋针对审计 agent 的指令文本,本 skill 无过滤)与用户输入侧(--forbid/--require 正则无回溯上限)。
- [ok] evals/ 的定位:是否确为自带评测夹具,能否在本地复跑出分数 — evals/evals.json 是 21 条 prompt + expectations 的行为回归集(12 条带 fixture 文件),evals/trigger-evals.json 是 24 条 should_trigger 路由集(14 true / 10 false),schema 与仓库内 daymade-skill/skill-creator/references/eval_pipeline_schemas.md 一致,且 skill-creator 的打包逻辑默认排除 root evals/(自述 'excluded by default as a development asset'),与 SKILL.md 的『excluded from packaged runtime content』吻合。但 skill 目录内没有 runner/评分器,仓库 .github/workflows/ci.yml 与 benchmark.yml 也未引用本 skill 的 tests 或 evals(grep 无命中),因此这些用例目前只在手动执行时生效。
6結論
48ff3a56b28549a2…