1. One-time setup 1a. Dump ninja dependency data (once per session, after a clean build) This is the authoritative "which TUs read which headers" map for ranking and cascade prediction. It is reliable only immediately after a successful build — see §5a for staleness rules. Match headers by basename ( ), not full path: WTF deps appear via symlinks and JSC via copies, so the same header shows under multiple paths. 2. Prepare a filtered compile commands.json Homebrew clang can't read Apple-specific flags or Apple-built PCH, and the CMake textual prefix ( ) breaks standalone-header analysis. Buil…

`), not full path: WTF deps appear via `WebKitBuild/.../Headers/wtf/` symlinks and JSC via `PrivateHeaders/JavaScriptCore/` copies, so the same header shows under multiple paths.\n\n## 2. Prepare a filtered compile_commands.json\n\nHomebrew clang can't read Apple-specific flags or Apple-built PCH, and the CMake textual prefix (`cmake_pch.hxx`) breaks standalone-header analysis. Build a filtered CDB once per session:\n\n```sh\nRES=$(xcrun clang -print-resource-dir)\nmkdir -p /tmp/cdb-optimize\npython3 - \"$RES\" \u003c\u003c'PY'\nimport json, re, sys\nres = sys.argv[1]\nsrc = json.load(open(\"WebKitBuild/cmake-mac/Debug/compile_commands.json\"))\nDROP = re.compile(r\"-fcas-\\S+|-fno-odr-hash-protocols|-clang-vendor-feature=\\S+|\"\n r\"-Wno-error=allocator-wrappers|-mllvm|-cas-friendly-debug-info|-Winvalid-pch\")\nout = []\nfor e in src:\n a, i = e[\"command\"].split(), 0; keep = []\n while i \u003c len(a):\n t = a[i]\n if t == \"-Xclang\" and i+1 \u003c len(a) and (a[i+1] in (\"-include-pch\", \"-include\")\n or a[i+1].endswith((\".pch\", \".hxx\")) or a[i+1].startswith(\"-fno-builtin-\")):\n i += 2; continue\n if t == \"--serialize-diagnostics\": i += 2; continue\n if DROP.fullmatch(t): i += 1; continue\n keep.append(t); i += 1\n keep.append(f\"-resource-dir={res}\")\n e[\"command\"] = \" \".join(keep); out.append(e)\njson.dump(out, open(\"/tmp/cdb-optimize/compile_commands.json\", \"w\"))\nPY\n```\n\n**Why strip `-Xclang -include -Xclang cmake_pch.hxx`:** the textual prefix transitively includes most project headers. When analyzing `Foo.h` as the *main file*, `#pragma once` doesn't guard the main file itself, so the prefix pulls in `Foo.h` once and then clang parses it again as the TU body → \"redefinition\" errors. Stripping the prefix is safe for `.cpp` files because they all `#include \"config.h\"` on line 1 anyway.\n\n**Why inject `-resource-dir`:** without it, homebrew clang can't find `Availability.h`, so every `PLATFORM(...)`/`ENABLE(...)` macro resolves wrong and the suggestions are garbage.\n\n## 3. Rank targets by impact (ninja deps)\n\nProcess headers highest-fan-out first — removing one include from a header read by 500 TUs is worth 500× one read by 3 TUs:\n\n```sh\nfor h in \u003ccandidate headers>; do\n printf '%6d %s\\n' \"$(grep -c \"/$(basename \"$h\")\\$\" /tmp/ninja-deps.txt)\" \"$h\"\ndone | sort -rn | head -40\n```\n\nDeps records *opened*, not *used* — it ranks reach, it cannot tell you what's removable. That's Pass A's job.\n\n**Basename collision:** matching by basename merges same-named headers across projects (`bmalloc/Vector.h` + `wtf/Vector.h` reported as 405 when bmalloc's true reach is 4). When a count looks suspiciously high for the project, re-check with a parent-dir prefix: `grep -c \"bmalloc/$(basename \"$h\")\\$\"`.\n\n## 4. Pass A — Remove unused includes (`.h .cpp .c .mm .m`)\n\n```sh\n/opt/homebrew/opt/llvm/bin/clang-include-cleaner \\\n --print=changes --disable-insert \\\n --ignore-headers='config\\.h,.*SoftLinking\\.h,.*SPI\\.h' \\\n -p /tmp/cdb-optimize \u003cfile>\n```\n\nOutput: `- \"Header.h\" @Line:N` per removable include.\n\n**Headers (`.h`)** have no CDB entry; clang-include-cleaner interpolates flags from a sibling `.cpp`. Force the language and pre-include `config.h` (headers don't include it themselves):\n\n```sh\n/opt/homebrew/opt/llvm/bin/clang-include-cleaner \\\n --print=changes --disable-insert \\\n --ignore-headers='config\\.h,.*SoftLinking\\.h,.*SPI\\.h' \\\n --extra-arg-before=-xobjective-c++ \\\n --extra-arg-before=-std=c++2b \\\n --extra-arg=-includeconfig.h \\\n -p /tmp/cdb-optimize \u003cheader.h>\n```\n\nThe `-includeconfig.h` flag is **per-project** — bmalloc/libpas have no `config.h` (their headers self-include `BPlatform.h`/`pas_config.h`), so drop that flag there. Keep it for WTF/JSC/WebCore/WebKit.\n\nIf a header still won't parse standalone (heavy template/macro context, or it *is* one of the few headers `config.h` itself pulls in), skip it and note it — don't fight the tool.\n\n**Never remove (all file types)**, even if the tool says so:\n- `config.h`\n- `*SoftLinking.h` (symbols via `SOFT_LINK_*` macros)\n- The file's own header (`Foo.h` in `Foo.cpp`) — often only used via `DEFINE_ALLOCATOR`/`WTF_MAKE_*`\n- `NeverDestroyed.h` when a `*_ALLOCATOR` / `LazyNeverDestroyed` macro/use is present\n- `SIMDUTF.h`\n- `*SPI.h`\n- `\u003cTargetConditionals.h>`, `\u003cAvailabilityMacros.h>`, `\u003cAvailability.h>` — define `TARGET_OS_*`/`__MAC_*` macros that `#if` tests\n- `\u003cFoundation/Foundation.h>` and other ObjC framework umbrellas in `.mm`/`.m` — provide `NSString`/`NSBundle`/etc. via macros the tool can't trace\n- Project macro-only foundations: bmalloc `BPlatform.h`/`BCompiler.h`/`BExport.h`/`BInline.h`; WTF `Platform.h`/`Compiler.h`/`ExportMacros.h`\n- The flagged `#include` *is* the file's entire body for this platform — pure forwarding/portability shims (e.g., `pas_thread.h` is just `#include \u003cpthread.h>` on non-Windows). The tool sees no local symbol use because re-export is the point.\n- Any `#include` in a file that pulls `__has_include(\u003cWebKitAdditions/...>)` — the additions header is internal-SDK-only and invisible to open-source tooling, so includes that exist to satisfy it (e.g., `AllocationCounts.h`'s `\u003catomic>`/`BExport.h`) read as unused.\n\n**Never remove from `.h` files (OK to remove from `.c`/`.cpp`/`.m`/`.mm`):** these guard against *transitive* breakage in downstream TUs/platforms, which can't happen from a leaf file — there the file's own compile is the full verification.\n- `\u003cmach/*.h>` inside `#if BOS(DARWIN)`/`#if OS(DARWIN)` — needed on embedded even when macOS gets it transitively\n- Any `#include` inside an `#if PLATFORM(...)`/`#if ENABLE(...)` block whose condition can differ on non-mac\n- Any top-level `#include` whose only *use* is inside an `#if`/`#else` arm that's compiled out on mac (e.g., `BAssert.h`'s `Logging.h` — only referenced under `#if !BUSE(OS_LOG)`). The tool only sees the active config.\n- Any `#include` whose only use is inside a `#define` macro body (e.g., `GigacageConfig.h`'s `\u003cbit>` — `std::bit_cast` appears only in `#define g_gigacageConfig`). The tool doesn't analyze macro definitions, only expansions; if no expansion lands in the analyzed TU, it reports unused.\n- `*Inlines.h` — template/inline bodies in the *target* header may need them at instantiation time in a downstream TU; the tool only sees instantiations in the analyzed TU.\n- JSC split-inlines without the suffix: `JSCJSValueCell.h`, `JSCJSValueStructure.h`, `JSCJSValueBigInt.h`, `JSCellButterfly.h`. Same failure mode as `*Inlines.h` (out-of-class inline method *definitions*, not declarations) — removal links fine in the edited header but drops `JSValue::inherits()`/`structureOrNull()` defs from downstream TUs → undefined-symbol at link. Filter regex: `Inlines.*\\.h|JSCJSValue(Cell|Structure|BigInt)\\.h|JSCellButterfly\\.h`.\n- `wtf/text/StringHash.h` / `*Hash.h` when the target header defines a type used as a `HashMap`/`HashSet` key. The trait specialization (`DefaultHash\u003cK>`) is needed at container *instantiation* in a downstream TU, not in the key header itself, so the tool reports it unused. (Observed: `RegExpKey.h` → `PackedRefPtr\u003cStringImpl>` static_assert + `GenericHashTraits` undefined.)\n\n**Verify by exported symbol, not header basename.** Before applying a removal of `Foo.h`, grep the target file for the names `Foo.h` *declares*, not just the string \"Foo\" — header name and type name often differ (`ArgList.h` → `MarkedArgumentBuffer`, `HashSet.h` → `UncheckedKeyHashSet`). Quick extractor:\n\n```sh\ngrep -hoE '^\\s*(class|struct|enum class|using|typedef|#define)\\s+\\w+' \u003cFoo.h> | awk '{print $NF}' | sort -u\n```\n\nThen `grep -v '#include' \u003ctarget> | grep -wF -f -` for each. If any hit, it's a FP.\n\nApply removals with the Edit tool (one line per removal). Do **not** use `--edit` — the never-remove filter must be applied manually.\n\n### 4c. Leaf-file sweep (`.cpp .c .mm .m`, unranked)\n\nAfter the §3-ranked header pass, sweep *all* implementation files in the target directory. Leaf TUs have fan-out 0 so they never surface via §3, but every TP is a free win with **zero cascade risk** — only the file's own compile needs to pass. PR #62576 found 38 removals in libpas `.c` files this way that the header-ranked pass missed entirely.\n\nmacOS `xargs -I{}` truncates long command lines, so write a helper once and pass filenames as `$1`:\n\n```sh\ncat > /tmp/run-cleaner-cpp.sh \u003c\u003c'EOF'\n#!/bin/sh\nout=$(/opt/homebrew/opt/llvm/bin/clang-include-cleaner --print=changes --disable-insert \\\n --ignore-headers='config\\.h,.*SoftLinking\\.h,.*SPI\\.h' \\\n -p /tmp/cdb-optimize \"$1\" 2>/dev/null)\n[ -n \"$out\" ] && printf '=== %s ===\\n%s\\n' \"$1\" \"$out\"\nEOF\nchmod +x /tmp/run-cleaner-cpp.sh\n\nfind Source/\u003cdir> \\( -name '*.c' -o -name '*.cpp' -o -name '*.m' -o -name '*.mm' \\) -print0 \\\n | xargs -0 -n1 -P8 /tmp/run-cleaner-cpp.sh > /tmp/leaf-results.txt\ngrep '^- ' /tmp/leaf-results.txt | sort | uniq -c | sort -rn | head\n```\n\n(For headers, copy to `/tmp/run-cleaner-header.sh` and add the §4 `--extra-arg-before=-xobjective-c++ --extra-arg-before=-std=c++2b --extra-arg=-includeconfig.h` flags.)\n\nApply only the **\"all file types\"** never-remove list above — the \"headers only\" filters don't apply here. Then `-k 0` build; any failure is a per-file revert, not a cascade.\n\n## 5. Pass B — Add missing direct includes (`.cpp .c .mm .m` only)\n\n**Pass B is ~4× noisier than Pass A** (bmalloc dry-run: ~20% precision vs ~70%). Default to running it only as the *repair* tool inside the §6 fix-loop, not as a blanket pre-pass. If you do run it broadly, apply the never-insert filter and path normalization aggressively.\n\n```sh\n/opt/homebrew/opt/llvm/bin/clang-include-cleaner \\\n --print=changes --disable-remove \\\n -p /tmp/cdb-optimize \u003cfile>\n```\n\nOutput: `+ \"Header.h\"` / `+ \u003cwtf/Header.h>` per missing direct include.\n\n**Never run Pass B on `.h` files** — adding includes to headers bloats the transitive graph, which is what we're fighting.\n\n**Normalize paths before inserting.** The tool emits `-I`-relative paths; WebKit uses quoted basenames. Strip leading `bmalloc/`, `libpas/src/libpas/`, `JavaScriptCore/`, `WebCore/`, `wtf/` (when inside WTF) so `+ \"libpas/src/libpas/pas_lock.h\"` becomes `#include \"pas_lock.h\"`.\n\n**Never insert**:\n- `wtf/Forward.h`, `wtf/Platform.h`, `wtf/Compiler.h`, `wtf/Assertions.h`, `wtf/ExportMacros.h` — provided by `config.h`\n- bmalloc `BPlatform.h`/`BCompiler.h`/`BExport.h`/`BInline.h`/`BAssert.h` — bmalloc's `config.h`-equivalents\n- Underscore-prefixed system internals (`\u003c_strings.h>`, `\u003c_stdio.h>`, …) — use the public header instead\n- `\u003csys/qos.h>`, `\u003csys/_types/*.h>` — implementation details; use `\u003cdispatch/dispatch.h>` / `\u003ccstddef>` etc.\n- `cmake_pch.hxx` or any prefix/PCH header\n- A header that's already included via the file's own `.h`\n\nInsertion point: after `#include \"config.h\"` and the file's own header, alphabetically within the existing group. In `.mm`/`.m`, use `#import` for ObjC framework headers (`\u003cFoundation/...>`, `\u003cWebKit/...>`), `#include` for everything else.\n\n### 5a. Predict the cascade before rebuilding (optional shortcut)\n\nAfter removing `#include \"X.h\"` from header `H`, the candidate-casualty set is:\n\n```sh\nawk -v H=\"/$(basename H)\" -v X=\"/$(basename X.h)\" '\n /: #deps / { tu=$1; hasH=0; hasX=0 }\n $1 ~ H\"$\" { hasH=1 }\n $1 ~ X\"$\" { hasX=1 }\n hasH && hasX && tu { print tu; tu=\"\" }\n' /tmp/ninja-deps.txt | head\n```\n\nThis **over-predicts** (flat list, no include-tree — can't tell \"via H\" from \"via some other path\") but never under-predicts for this platform/config. Run Pass B on those TUs first instead of waiting 10–15 min for `-k 0` to find them. If the set is ≳10 TUs, that's the §6 escape-hatch signal up front.\n\n**Staleness:** `/tmp/ninja-deps.txt` reflects the *last successful compile* per TU. After edits it's stale-but-conservative (removed includes still listed → over-predicts, which is safe). Re-dump only after a clean `-k 0` build; don't re-dump mid-loop.\n\n## 6. Build-and-fix loop\n\n```sh\ncmake --build --preset mac-dev-debug -- -k 0 2>&1 | tee /tmp/build.log\n```\n\nRun with `dangerouslyDisableSandbox: true`, `run_in_background: true`. Monitor:\n\n```sh\ngrep -E \"error:\" /tmp/build.log | head -40\n```\n\nIf `-k 0` reports exactly 1 `FAILED:` and it's `LLInt{Settings,Offsets}Extractor.cpp.o`, that target *generates* headers the rest of JSC depends on, so ninja can't continue past it. Fix that one error and re-run; the next round will surface the real cascade.\n\nFor each `use of undeclared identifier 'X'` / `unknown type name 'X'` / `incomplete type 'X'` / `no member named 'X'`:\n\n1. **Identify the real TU.** If the error is in `UnifiedSourceN.cpp`/`.mm`, open the unified wrapper and find which constituent `.cpp` owns the failing line.\n2. **Expect casualties in files you didn't edit.** Removing an include from a header, or from an earlier sibling in a unified bundle, breaks downstream files that were leaning on it transitively. This is normal — do not revert.\n3. **Find the providing header.** `git grep -nE \"^(class|struct|enum class|using) X\\b\" -- 'Source/**/*.h' | head`. Prefer the header with the *definition*, not a forward declaration.\n4. **Add `#include \"Provider.h\"` to the failing real `.cpp`** — never to the unified wrapper. If the failing line is in a `.h` (the type appears in a signature/member), add the include to *that* `.h` — this is the IWYU fix; the header was leaning on a transitive path you just cut.\n5. Re-build with `-k 0`. Repeat until clean.\n\n**Escape hatch:** if one removed header line causes an unbounded cascade (≳10 TUs all needing the same add), that header was a de-facto umbrella — restore that single removal and move on. Known JSC umbrellas (skip removals here outright): `JSCInlines.h`, `JSCellInlines.h`, `JSCJSValueInlines.h`, `Lookup.h`, `ObjectAllocationProfile.h` — each fans out to all WebCore JS bindings.\n\n## 7. Cross-platform fallout (after PR)\n\nLocal CMake only builds macOS. EWS-only breaks from removed includes that other platforms need directly:\n\n- `\u003cmach/mach.h>` — embedded Darwin (ios/tv/watch/vision) for `mach_task_self`/`kern_return_t`; macOS gets it transitively via frameworks\n- `\u003cmach/task_info.h>` — `task_vm_info_data_t`/`TASK_VM_INFO_COUNT` (does **not** provide `mach_task_self` — need `\u003cmach/mach.h>` too)\n- `\u003csys/file.h>` — Linux (WPE/GTK) for `flock()`/`LOCK_*`\n- `\u003cwtf/NeverDestroyed.h>` — `LazyNeverDestroyed\u003cT>`; forward decl insufficient\n- `\u003cwtf/Function.h>` — `WTF::Function\u003c>` on Linux; not pulled in transitively there\n\nEWS-only breaks: re-add the include in the failing TU, guarded by the same `#if` the failing platform uses.\n---","attachment_filenames":[],"attachments":[],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":2},"content":[{"text":"1. One-time setup","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"brew install llvm # → /opt/homebrew/opt/llvm/bin/clang-include-cleaner\nBUILD=WebKitBuild/cmake-mac/Debug # mac-dev-debug preset's binaryDir\ncmake --preset mac-dev-debug # emits $BUILD/compile_commands.json\ncmake --build --preset mac-dev-debug # generated/forwarding headers + .ninja_deps must exist before analysis","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1a. Dump ninja dependency data (once per session, after a clean build)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"ninja -C $BUILD -t deps > /tmp/ninja-deps.txt","type":"text"}]},{"type":"paragraph","content":[{"text":"This is the authoritative \"which TUs read which headers\" map for ranking and cascade prediction. It is reliable ","type":"text"},{"text":"only immediately after a successful build","type":"text","marks":[{"type":"strong"}]},{"text":" — see §5a for staleness rules. Match headers by ","type":"text"},{"text":"basename","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"grep '/Foo\\.h

1. One-time setup 1a. Dump ninja dependency data (once per session, after a clean build) This is the authoritative "which TUs read which headers" map for ranking and cascade prediction. It is reliable only immediately after a successful build — see §5a for staleness rules. Match headers by basename ( ), not full path: WTF deps appear via symlinks and JSC via copies, so the same header shows under multiple paths. 2. Prepare a filtered compile commands.json Homebrew clang can't read Apple-specific flags or Apple-built PCH, and the CMake textual prefix ( ) breaks standalone-header analysis. Buil…

","type":"text","marks":[{"type":"code_inline"}]},{"text":"), not full path: WTF deps appear via ","type":"text"},{"text":"WebKitBuild/.../Headers/wtf/","type":"text","marks":[{"type":"code_inline"}]},{"text":" symlinks and JSC via ","type":"text"},{"text":"PrivateHeaders/JavaScriptCore/","type":"text","marks":[{"type":"code_inline"}]},{"text":" copies, so the same header shows under multiple paths.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"2. Prepare a filtered compile_commands.json","type":"text"}]},{"type":"paragraph","content":[{"text":"Homebrew clang can't read Apple-specific flags or Apple-built PCH, and the CMake textual prefix (","type":"text"},{"text":"cmake_pch.hxx","type":"text","marks":[{"type":"code_inline"}]},{"text":") breaks standalone-header analysis. Build a filtered CDB once per session:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"RES=$(xcrun clang -print-resource-dir)\nmkdir -p /tmp/cdb-optimize\npython3 - \"$RES\" \u003c\u003c'PY'\nimport json, re, sys\nres = sys.argv[1]\nsrc = json.load(open(\"WebKitBuild/cmake-mac/Debug/compile_commands.json\"))\nDROP = re.compile(r\"-fcas-\\S+|-fno-odr-hash-protocols|-clang-vendor-feature=\\S+|\"\n r\"-Wno-error=allocator-wrappers|-mllvm|-cas-friendly-debug-info|-Winvalid-pch\")\nout = []\nfor e in src:\n a, i = e[\"command\"].split(), 0; keep = []\n while i \u003c len(a):\n t = a[i]\n if t == \"-Xclang\" and i+1 \u003c len(a) and (a[i+1] in (\"-include-pch\", \"-include\")\n or a[i+1].endswith((\".pch\", \".hxx\")) or a[i+1].startswith(\"-fno-builtin-\")):\n i += 2; continue\n if t == \"--serialize-diagnostics\": i += 2; continue\n if DROP.fullmatch(t): i += 1; continue\n keep.append(t); i += 1\n keep.append(f\"-resource-dir={res}\")\n e[\"command\"] = \" \".join(keep); out.append(e)\njson.dump(out, open(\"/tmp/cdb-optimize/compile_commands.json\", \"w\"))\nPY","type":"text"}]},{"type":"paragraph","content":[{"text":"Why strip ","type":"text","marks":[{"type":"strong"}]},{"text":"-Xclang -include -Xclang cmake_pch.hxx","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":":","type":"text","marks":[{"type":"strong"}]},{"text":" the textual prefix transitively includes most project headers. When analyzing ","type":"text"},{"text":"Foo.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" as the ","type":"text"},{"text":"main file","type":"text","marks":[{"type":"em"}]},{"text":", ","type":"text"},{"text":"#pragma once","type":"text","marks":[{"type":"code_inline"}]},{"text":" doesn't guard the main file itself, so the prefix pulls in ","type":"text"},{"text":"Foo.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" once and then clang parses it again as the TU body → \"redefinition\" errors. Stripping the prefix is safe for ","type":"text"},{"text":".cpp","type":"text","marks":[{"type":"code_inline"}]},{"text":" files because they all ","type":"text"},{"text":"#include \"config.h\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" on line 1 anyway.","type":"text"}]},{"type":"paragraph","content":[{"text":"Why inject ","type":"text","marks":[{"type":"strong"}]},{"text":"-resource-dir","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":":","type":"text","marks":[{"type":"strong"}]},{"text":" without it, homebrew clang can't find ","type":"text"},{"text":"Availability.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", so every ","type":"text"},{"text":"PLATFORM(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"ENABLE(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":" macro resolves wrong and the suggestions are garbage.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"3. Rank targets by impact (ninja deps)","type":"text"}]},{"type":"paragraph","content":[{"text":"Process headers highest-fan-out first — removing one include from a header read by 500 TUs is worth 500× one read by 3 TUs:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"for h in \u003ccandidate headers>; do\n printf '%6d %s\\n' \"$(grep -c \"/$(basename \"$h\")\\$\" /tmp/ninja-deps.txt)\" \"$h\"\ndone | sort -rn | head -40","type":"text"}]},{"type":"paragraph","content":[{"text":"Deps records ","type":"text"},{"text":"opened","type":"text","marks":[{"type":"em"}]},{"text":", not ","type":"text"},{"text":"used","type":"text","marks":[{"type":"em"}]},{"text":" — it ranks reach, it cannot tell you what's removable. That's Pass A's job.","type":"text"}]},{"type":"paragraph","content":[{"text":"Basename collision:","type":"text","marks":[{"type":"strong"}]},{"text":" matching by basename merges same-named headers across projects (","type":"text"},{"text":"bmalloc/Vector.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"wtf/Vector.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" reported as 405 when bmalloc's true reach is 4). When a count looks suspiciously high for the project, re-check with a parent-dir prefix: ","type":"text"},{"text":"grep -c \"bmalloc/$(basename \"$h\")\\$\"","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"4. Pass A — Remove unused includes (","type":"text"},{"text":".h .cpp .c .mm .m","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"/opt/homebrew/opt/llvm/bin/clang-include-cleaner \\\n --print=changes --disable-insert \\\n --ignore-headers='config\\.h,.*SoftLinking\\.h,.*SPI\\.h' \\\n -p /tmp/cdb-optimize \u003cfile>","type":"text"}]},{"type":"paragraph","content":[{"text":"Output: ","type":"text"},{"text":"- \"Header.h\" @Line:N","type":"text","marks":[{"type":"code_inline"}]},{"text":" per removable include.","type":"text"}]},{"type":"paragraph","content":[{"text":"Headers (","type":"text","marks":[{"type":"strong"}]},{"text":".h","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":")","type":"text","marks":[{"type":"strong"}]},{"text":" have no CDB entry; clang-include-cleaner interpolates flags from a sibling ","type":"text"},{"text":".cpp","type":"text","marks":[{"type":"code_inline"}]},{"text":". Force the language and pre-include ","type":"text"},{"text":"config.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" (headers don't include it themselves):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"/opt/homebrew/opt/llvm/bin/clang-include-cleaner \\\n --print=changes --disable-insert \\\n --ignore-headers='config\\.h,.*SoftLinking\\.h,.*SPI\\.h' \\\n --extra-arg-before=-xobjective-c++ \\\n --extra-arg-before=-std=c++2b \\\n --extra-arg=-includeconfig.h \\\n -p /tmp/cdb-optimize \u003cheader.h>","type":"text"}]},{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"-includeconfig.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" flag is ","type":"text"},{"text":"per-project","type":"text","marks":[{"type":"strong"}]},{"text":" — bmalloc/libpas have no ","type":"text"},{"text":"config.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" (their headers self-include ","type":"text"},{"text":"BPlatform.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"pas_config.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"), so drop that flag there. Keep it for WTF/JSC/WebCore/WebKit.","type":"text"}]},{"type":"paragraph","content":[{"text":"If a header still won't parse standalone (heavy template/macro context, or it ","type":"text"},{"text":"is","type":"text","marks":[{"type":"em"}]},{"text":" one of the few headers ","type":"text"},{"text":"config.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" itself pulls in), skip it and note it — don't fight the tool.","type":"text"}]},{"type":"paragraph","content":[{"text":"Never remove (all file types)","type":"text","marks":[{"type":"strong"}]},{"text":", even if the tool says so:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"config.h","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"*SoftLinking.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" (symbols via ","type":"text"},{"text":"SOFT_LINK_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" macros)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The file's own header (","type":"text"},{"text":"Foo.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"Foo.cpp","type":"text","marks":[{"type":"code_inline"}]},{"text":") — often only used via ","type":"text"},{"text":"DEFINE_ALLOCATOR","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"WTF_MAKE_*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NeverDestroyed.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" when a ","type":"text"},{"text":"*_ALLOCATOR","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"LazyNeverDestroyed","type":"text","marks":[{"type":"code_inline"}]},{"text":" macro/use is present","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SIMDUTF.h","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"*SPI.h","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003cTargetConditionals.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003cAvailabilityMacros.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003cAvailability.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — define ","type":"text"},{"text":"TARGET_OS_*","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"__MAC_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" macros that ","type":"text"},{"text":"#if","type":"text","marks":[{"type":"code_inline"}]},{"text":" tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003cFoundation/Foundation.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" and other ObjC framework umbrellas in ","type":"text"},{"text":".mm","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":".m","type":"text","marks":[{"type":"code_inline"}]},{"text":" — provide ","type":"text"},{"text":"NSString","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"NSBundle","type":"text","marks":[{"type":"code_inline"}]},{"text":"/etc. via macros the tool can't trace","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Project macro-only foundations: bmalloc ","type":"text"},{"text":"BPlatform.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"BCompiler.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"BExport.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"BInline.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"; WTF ","type":"text"},{"text":"Platform.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"Compiler.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"ExportMacros.h","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The flagged ","type":"text"},{"text":"#include","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"is","type":"text","marks":[{"type":"em"}]},{"text":" the file's entire body for this platform — pure forwarding/portability shims (e.g., ","type":"text"},{"text":"pas_thread.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" is just ","type":"text"},{"text":"#include \u003cpthread.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" on non-Windows). The tool sees no local symbol use because re-export is the point.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any ","type":"text"},{"text":"#include","type":"text","marks":[{"type":"code_inline"}]},{"text":" in a file that pulls ","type":"text"},{"text":"__has_include(\u003cWebKitAdditions/...>)","type":"text","marks":[{"type":"code_inline"}]},{"text":" — the additions header is internal-SDK-only and invisible to open-source tooling, so includes that exist to satisfy it (e.g., ","type":"text"},{"text":"AllocationCounts.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"'s ","type":"text"},{"text":"\u003catomic>","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"BExport.h","type":"text","marks":[{"type":"code_inline"}]},{"text":") read as unused.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Never remove from ","type":"text","marks":[{"type":"strong"}]},{"text":".h","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" files (OK to remove from ","type":"text","marks":[{"type":"strong"}]},{"text":".c","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":"/","type":"text","marks":[{"type":"strong"}]},{"text":".cpp","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":"/","type":"text","marks":[{"type":"strong"}]},{"text":".m","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":"/","type":"text","marks":[{"type":"strong"}]},{"text":".mm","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":"):","type":"text","marks":[{"type":"strong"}]},{"text":" these guard against ","type":"text"},{"text":"transitive","type":"text","marks":[{"type":"em"}]},{"text":" breakage in downstream TUs/platforms, which can't happen from a leaf file — there the file's own compile is the full verification.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003cmach/*.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" inside ","type":"text"},{"text":"#if BOS(DARWIN)","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"#if OS(DARWIN)","type":"text","marks":[{"type":"code_inline"}]},{"text":" — needed on embedded even when macOS gets it transitively","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any ","type":"text"},{"text":"#include","type":"text","marks":[{"type":"code_inline"}]},{"text":" inside an ","type":"text"},{"text":"#if PLATFORM(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"#if ENABLE(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":" block whose condition can differ on non-mac","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any top-level ","type":"text"},{"text":"#include","type":"text","marks":[{"type":"code_inline"}]},{"text":" whose only ","type":"text"},{"text":"use","type":"text","marks":[{"type":"em"}]},{"text":" is inside an ","type":"text"},{"text":"#if","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"#else","type":"text","marks":[{"type":"code_inline"}]},{"text":" arm that's compiled out on mac (e.g., ","type":"text"},{"text":"BAssert.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"'s ","type":"text"},{"text":"Logging.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" — only referenced under ","type":"text"},{"text":"#if !BUSE(OS_LOG)","type":"text","marks":[{"type":"code_inline"}]},{"text":"). The tool only sees the active config.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any ","type":"text"},{"text":"#include","type":"text","marks":[{"type":"code_inline"}]},{"text":" whose only use is inside a ","type":"text"},{"text":"#define","type":"text","marks":[{"type":"code_inline"}]},{"text":" macro body (e.g., ","type":"text"},{"text":"GigacageConfig.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"'s ","type":"text"},{"text":"\u003cbit>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — ","type":"text"},{"text":"std::bit_cast","type":"text","marks":[{"type":"code_inline"}]},{"text":" appears only in ","type":"text"},{"text":"#define g_gigacageConfig","type":"text","marks":[{"type":"code_inline"}]},{"text":"). The tool doesn't analyze macro definitions, only expansions; if no expansion lands in the analyzed TU, it reports unused.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"*Inlines.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" — template/inline bodies in the ","type":"text"},{"text":"target","type":"text","marks":[{"type":"em"}]},{"text":" header may need them at instantiation time in a downstream TU; the tool only sees instantiations in the analyzed TU.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"JSC split-inlines without the suffix: ","type":"text"},{"text":"JSCJSValueCell.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"JSCJSValueStructure.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"JSCJSValueBigInt.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"JSCellButterfly.h","type":"text","marks":[{"type":"code_inline"}]},{"text":". Same failure mode as ","type":"text"},{"text":"*Inlines.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" (out-of-class inline method ","type":"text"},{"text":"definitions","type":"text","marks":[{"type":"em"}]},{"text":", not declarations) — removal links fine in the edited header but drops ","type":"text"},{"text":"JSValue::inherits()","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"structureOrNull()","type":"text","marks":[{"type":"code_inline"}]},{"text":" defs from downstream TUs → undefined-symbol at link. Filter regex: ","type":"text"},{"text":"Inlines.*\\.h|JSCJSValue(Cell|Structure|BigInt)\\.h|JSCellButterfly\\.h","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"wtf/text/StringHash.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"*Hash.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" when the target header defines a type used as a ","type":"text"},{"text":"HashMap","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"HashSet","type":"text","marks":[{"type":"code_inline"}]},{"text":" key. The trait specialization (","type":"text"},{"text":"DefaultHash\u003cK>","type":"text","marks":[{"type":"code_inline"}]},{"text":") is needed at container ","type":"text"},{"text":"instantiation","type":"text","marks":[{"type":"em"}]},{"text":" in a downstream TU, not in the key header itself, so the tool reports it unused. (Observed: ","type":"text"},{"text":"RegExpKey.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"PackedRefPtr\u003cStringImpl>","type":"text","marks":[{"type":"code_inline"}]},{"text":" static_assert + ","type":"text"},{"text":"GenericHashTraits","type":"text","marks":[{"type":"code_inline"}]},{"text":" undefined.)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Verify by exported symbol, not header basename.","type":"text","marks":[{"type":"strong"}]},{"text":" Before applying a removal of ","type":"text"},{"text":"Foo.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", grep the target file for the names ","type":"text"},{"text":"Foo.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"declares","type":"text","marks":[{"type":"em"}]},{"text":", not just the string \"Foo\" — header name and type name often differ (","type":"text"},{"text":"ArgList.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"MarkedArgumentBuffer","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"HashSet.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"UncheckedKeyHashSet","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Quick extractor:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"grep -hoE '^\\s*(class|struct|enum class|using|typedef|#define)\\s+\\w+' \u003cFoo.h> | awk '{print $NF}' | sort -u","type":"text"}]},{"type":"paragraph","content":[{"text":"Then ","type":"text"},{"text":"grep -v '#include' \u003ctarget> | grep -wF -f -","type":"text","marks":[{"type":"code_inline"}]},{"text":" for each. If any hit, it's a FP.","type":"text"}]},{"type":"paragraph","content":[{"text":"Apply removals with the Edit tool (one line per removal). Do ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" use ","type":"text"},{"text":"--edit","type":"text","marks":[{"type":"code_inline"}]},{"text":" — the never-remove filter must be applied manually.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4c. Leaf-file sweep (","type":"text"},{"text":".cpp .c .mm .m","type":"text","marks":[{"type":"code_inline"}]},{"text":", unranked)","type":"text"}]},{"type":"paragraph","content":[{"text":"After the §3-ranked header pass, sweep ","type":"text"},{"text":"all","type":"text","marks":[{"type":"em"}]},{"text":" implementation files in the target directory. Leaf TUs have fan-out 0 so they never surface via §3, but every TP is a free win with ","type":"text"},{"text":"zero cascade risk","type":"text","marks":[{"type":"strong"}]},{"text":" — only the file's own compile needs to pass. PR #62576 found 38 removals in libpas ","type":"text"},{"text":".c","type":"text","marks":[{"type":"code_inline"}]},{"text":" files this way that the header-ranked pass missed entirely.","type":"text"}]},{"type":"paragraph","content":[{"text":"macOS ","type":"text"},{"text":"xargs -I{}","type":"text","marks":[{"type":"code_inline"}]},{"text":" truncates long command lines, so write a helper once and pass filenames as ","type":"text"},{"text":"$1","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"cat > /tmp/run-cleaner-cpp.sh \u003c\u003c'EOF'\n#!/bin/sh\nout=$(/opt/homebrew/opt/llvm/bin/clang-include-cleaner --print=changes --disable-insert \\\n --ignore-headers='config\\.h,.*SoftLinking\\.h,.*SPI\\.h' \\\n -p /tmp/cdb-optimize \"$1\" 2>/dev/null)\n[ -n \"$out\" ] && printf '=== %s ===\\n%s\\n' \"$1\" \"$out\"\nEOF\nchmod +x /tmp/run-cleaner-cpp.sh\n\nfind Source/\u003cdir> \\( -name '*.c' -o -name '*.cpp' -o -name '*.m' -o -name '*.mm' \\) -print0 \\\n | xargs -0 -n1 -P8 /tmp/run-cleaner-cpp.sh > /tmp/leaf-results.txt\ngrep '^- ' /tmp/leaf-results.txt | sort | uniq -c | sort -rn | head","type":"text"}]},{"type":"paragraph","content":[{"text":"(For headers, copy to ","type":"text"},{"text":"/tmp/run-cleaner-header.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" and add the §4 ","type":"text"},{"text":"--extra-arg-before=-xobjective-c++ --extra-arg-before=-std=c++2b --extra-arg=-includeconfig.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" flags.)","type":"text"}]},{"type":"paragraph","content":[{"text":"Apply only the ","type":"text"},{"text":"\"all file types\"","type":"text","marks":[{"type":"strong"}]},{"text":" never-remove list above — the \"headers only\" filters don't apply here. Then ","type":"text"},{"text":"-k 0","type":"text","marks":[{"type":"code_inline"}]},{"text":" build; any failure is a per-file revert, not a cascade.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"5. Pass B — Add missing direct includes (","type":"text"},{"text":".cpp .c .mm .m","type":"text","marks":[{"type":"code_inline"}]},{"text":" only)","type":"text"}]},{"type":"paragraph","content":[{"text":"Pass B is ~4× noisier than Pass A","type":"text","marks":[{"type":"strong"}]},{"text":" (bmalloc dry-run: ~20% precision vs ~70%). Default to running it only as the ","type":"text"},{"text":"repair","type":"text","marks":[{"type":"em"}]},{"text":" tool inside the §6 fix-loop, not as a blanket pre-pass. If you do run it broadly, apply the never-insert filter and path normalization aggressively.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"/opt/homebrew/opt/llvm/bin/clang-include-cleaner \\\n --print=changes --disable-remove \\\n -p /tmp/cdb-optimize \u003cfile>","type":"text"}]},{"type":"paragraph","content":[{"text":"Output: ","type":"text"},{"text":"+ \"Header.h\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"+ \u003cwtf/Header.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" per missing direct include.","type":"text"}]},{"type":"paragraph","content":[{"text":"Never run Pass B on ","type":"text","marks":[{"type":"strong"}]},{"text":".h","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" files","type":"text","marks":[{"type":"strong"}]},{"text":" — adding includes to headers bloats the transitive graph, which is what we're fighting.","type":"text"}]},{"type":"paragraph","content":[{"text":"Normalize paths before inserting.","type":"text","marks":[{"type":"strong"}]},{"text":" The tool emits ","type":"text"},{"text":"-I","type":"text","marks":[{"type":"code_inline"}]},{"text":"-relative paths; WebKit uses quoted basenames. Strip leading ","type":"text"},{"text":"bmalloc/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"libpas/src/libpas/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"JavaScriptCore/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"WebCore/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"wtf/","type":"text","marks":[{"type":"code_inline"}]},{"text":" (when inside WTF) so ","type":"text"},{"text":"+ \"libpas/src/libpas/pas_lock.h\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" becomes ","type":"text"},{"text":"#include \"pas_lock.h\"","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Never insert","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"wtf/Forward.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"wtf/Platform.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"wtf/Compiler.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"wtf/Assertions.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"wtf/ExportMacros.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" — provided by ","type":"text"},{"text":"config.h","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"bmalloc ","type":"text"},{"text":"BPlatform.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"BCompiler.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"BExport.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"BInline.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"BAssert.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" — bmalloc's ","type":"text"},{"text":"config.h","type":"text","marks":[{"type":"code_inline"}]},{"text":"-equivalents","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Underscore-prefixed system internals (","type":"text"},{"text":"\u003c_strings.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003c_stdio.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":", …) — use the public header instead","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003csys/qos.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003csys/_types/*.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — implementation details; use ","type":"text"},{"text":"\u003cdispatch/dispatch.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"\u003ccstddef>","type":"text","marks":[{"type":"code_inline"}]},{"text":" etc.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cmake_pch.hxx","type":"text","marks":[{"type":"code_inline"}]},{"text":" or any prefix/PCH header","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A header that's already included via the file's own ","type":"text"},{"text":".h","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Insertion point: after ","type":"text"},{"text":"#include \"config.h\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the file's own header, alphabetically within the existing group. In ","type":"text"},{"text":".mm","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":".m","type":"text","marks":[{"type":"code_inline"}]},{"text":", use ","type":"text"},{"text":"#import","type":"text","marks":[{"type":"code_inline"}]},{"text":" for ObjC framework headers (","type":"text"},{"text":"\u003cFoundation/...>","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"\u003cWebKit/...>","type":"text","marks":[{"type":"code_inline"}]},{"text":"), ","type":"text"},{"text":"#include","type":"text","marks":[{"type":"code_inline"}]},{"text":" for everything else.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5a. Predict the cascade before rebuilding (optional shortcut)","type":"text"}]},{"type":"paragraph","content":[{"text":"After removing ","type":"text"},{"text":"#include \"X.h\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" from header ","type":"text"},{"text":"H","type":"text","marks":[{"type":"code_inline"}]},{"text":", the candidate-casualty set is:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"awk -v H=\"/$(basename H)\" -v X=\"/$(basename X.h)\" '\n /: #deps / { tu=$1; hasH=0; hasX=0 }\n $1 ~ H\"$\" { hasH=1 }\n $1 ~ X\"$\" { hasX=1 }\n hasH && hasX && tu { print tu; tu=\"\" }\n' /tmp/ninja-deps.txt | head","type":"text"}]},{"type":"paragraph","content":[{"text":"This ","type":"text"},{"text":"over-predicts","type":"text","marks":[{"type":"strong"}]},{"text":" (flat list, no include-tree — can't tell \"via H\" from \"via some other path\") but never under-predicts for this platform/config. Run Pass B on those TUs first instead of waiting 10–15 min for ","type":"text"},{"text":"-k 0","type":"text","marks":[{"type":"code_inline"}]},{"text":" to find them. If the set is ≳10 TUs, that's the §6 escape-hatch signal up front.","type":"text"}]},{"type":"paragraph","content":[{"text":"Staleness:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"/tmp/ninja-deps.txt","type":"text","marks":[{"type":"code_inline"}]},{"text":" reflects the ","type":"text"},{"text":"last successful compile","type":"text","marks":[{"type":"em"}]},{"text":" per TU. After edits it's stale-but-conservative (removed includes still listed → over-predicts, which is safe). Re-dump only after a clean ","type":"text"},{"text":"-k 0","type":"text","marks":[{"type":"code_inline"}]},{"text":" build; don't re-dump mid-loop.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"6. Build-and-fix loop","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"cmake --build --preset mac-dev-debug -- -k 0 2>&1 | tee /tmp/build.log","type":"text"}]},{"type":"paragraph","content":[{"text":"Run with ","type":"text"},{"text":"dangerouslyDisableSandbox: true","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"run_in_background: true","type":"text","marks":[{"type":"code_inline"}]},{"text":". Monitor:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"sh"},"content":[{"text":"grep -E \"error:\" /tmp/build.log | head -40","type":"text"}]},{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"-k 0","type":"text","marks":[{"type":"code_inline"}]},{"text":" reports exactly 1 ","type":"text"},{"text":"FAILED:","type":"text","marks":[{"type":"code_inline"}]},{"text":" and it's ","type":"text"},{"text":"LLInt{Settings,Offsets}Extractor.cpp.o","type":"text","marks":[{"type":"code_inline"}]},{"text":", that target ","type":"text"},{"text":"generates","type":"text","marks":[{"type":"em"}]},{"text":" headers the rest of JSC depends on, so ninja can't continue past it. Fix that one error and re-run; the next round will surface the real cascade.","type":"text"}]},{"type":"paragraph","content":[{"text":"For each ","type":"text"},{"text":"use of undeclared identifier 'X'","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"unknown type name 'X'","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"incomplete type 'X'","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"no member named 'X'","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify the real TU.","type":"text","marks":[{"type":"strong"}]},{"text":" If the error is in ","type":"text"},{"text":"UnifiedSourceN.cpp","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":".mm","type":"text","marks":[{"type":"code_inline"}]},{"text":", open the unified wrapper and find which constituent ","type":"text"},{"text":".cpp","type":"text","marks":[{"type":"code_inline"}]},{"text":" owns the failing line.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Expect casualties in files you didn't edit.","type":"text","marks":[{"type":"strong"}]},{"text":" Removing an include from a header, or from an earlier sibling in a unified bundle, breaks downstream files that were leaning on it transitively. This is normal — do not revert.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Find the providing header.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"git grep -nE \"^(class|struct|enum class|using) X\\b\" -- 'Source/**/*.h' | head","type":"text","marks":[{"type":"code_inline"}]},{"text":". Prefer the header with the ","type":"text"},{"text":"definition","type":"text","marks":[{"type":"em"}]},{"text":", not a forward declaration.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add ","type":"text","marks":[{"type":"strong"}]},{"text":"#include \"Provider.h\"","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" to the failing real ","type":"text","marks":[{"type":"strong"}]},{"text":".cpp","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" — never to the unified wrapper. If the failing line is in a ","type":"text"},{"text":".h","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the type appears in a signature/member), add the include to ","type":"text"},{"text":"that","type":"text","marks":[{"type":"em"}]},{"text":" ","type":"text"},{"text":".h","type":"text","marks":[{"type":"code_inline"}]},{"text":" — this is the IWYU fix; the header was leaning on a transitive path you just cut.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Re-build with ","type":"text"},{"text":"-k 0","type":"text","marks":[{"type":"code_inline"}]},{"text":". Repeat until clean.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Escape hatch:","type":"text","marks":[{"type":"strong"}]},{"text":" if one removed header line causes an unbounded cascade (≳10 TUs all needing the same add), that header was a de-facto umbrella — restore that single removal and move on. Known JSC umbrellas (skip removals here outright): ","type":"text"},{"text":"JSCInlines.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"JSCellInlines.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"JSCJSValueInlines.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Lookup.h","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ObjectAllocationProfile.h","type":"text","marks":[{"type":"code_inline"}]},{"text":" — each fans out to all WebCore JS bindings.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"7. Cross-platform fallout (after PR)","type":"text"}]},{"type":"paragraph","content":[{"text":"Local CMake only builds macOS. EWS-only breaks from removed includes that other platforms need directly:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003cmach/mach.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — embedded Darwin (ios/tv/watch/vision) for ","type":"text"},{"text":"mach_task_self","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"kern_return_t","type":"text","marks":[{"type":"code_inline"}]},{"text":"; macOS gets it transitively via frameworks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003cmach/task_info.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — ","type":"text"},{"text":"task_vm_info_data_t","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"TASK_VM_INFO_COUNT","type":"text","marks":[{"type":"code_inline"}]},{"text":" (does ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" provide ","type":"text"},{"text":"mach_task_self","type":"text","marks":[{"type":"code_inline"}]},{"text":" — need ","type":"text"},{"text":"\u003cmach/mach.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" too)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003csys/file.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Linux (WPE/GTK) for ","type":"text"},{"text":"flock()","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"LOCK_*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003cwtf/NeverDestroyed.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — ","type":"text"},{"text":"LazyNeverDestroyed\u003cT>","type":"text","marks":[{"type":"code_inline"}]},{"text":"; forward decl insufficient","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\u003cwtf/Function.h>","type":"text","marks":[{"type":"code_inline"}]},{"text":" — ","type":"text"},{"text":"WTF::Function\u003c>","type":"text","marks":[{"type":"code_inline"}]},{"text":" on Linux; not pulled in transitively there","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"EWS-only breaks: re-add the include in the failing TU, guarded by the same ","type":"text"},{"text":"#if","type":"text","marks":[{"type":"code_inline"}]},{"text":" the failing platform uses.","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"reduce-header-includes","author":"@skillopedia","source":{"stars":9864,"repo_name":"webkit","origin_url":"https://github.com/webkit/webkit/blob/HEAD/.claude/plugins/reduce-header-includes/skills/reduce-header-includes/SKILL.md","repo_owner":"webkit","body_sha256":"02f1f847950d991fe8270257d07a86fe1e181024c3d83745d6f5aeaf79b1c020","cluster_key":"bc5aa7322b5a5761ee2bb6cce5de8c3bca239d9844a6871de8717efedfad0551","clean_bundle":{"format":"clean-skill-bundle-v1","source":"webkit/webkit/.claude/plugins/reduce-header-includes/skills/reduce-header-includes/SKILL.md","bundle_sha256":"0535a1cf6c80290dd1dcdf540b05d4f7811fbcb635baaed85108d357861bdb63","attachment_count":0,"text_attachments":0,"binary_attachments":0},"cluster_size":1,"skill_md_path":".claude/plugins/reduce-header-includes/skills/reduce-header-includes/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"general","category_label":"General"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"general","import_tag":"clean-skills-v1","description":"Use when the user wants to remove unused","allowed-tools":"Bash, Read, Edit, Write, Agent","user-invocable":true}},"renderedAt":1782986697696}

1. One-time setup 1a. Dump ninja dependency data (once per session, after a clean build) This is the authoritative "which TUs read which headers" map for ranking and cascade prediction. It is reliable only immediately after a successful build — see §5a for staleness rules. Match headers by basename ( ), not full path: WTF deps appear via symlinks and JSC via copies, so the same header shows under multiple paths. 2. Prepare a filtered compile commands.json Homebrew clang can't read Apple-specific flags or Apple-built PCH, and the CMake textual prefix ( ) breaks standalone-header analysis. Buil…