Split out of #6092 by the PR 2 seat, under that card's own STOP condition. #6092's dispatch order ruled: "⛔ If those loops feed module-level exports that importers actually read, STOP and report rather than restructuring the module." They do. This records the measurement so the next seat does not re-derive it, and names the shape that would actually work.
What is there
scripts/check-lucide-icon-record-names.mjs builds two lookup maps in top-level for loops, outside any guard — :243 and :245 on main after #6092's PR 2:
constkeyByComponent=newMap();for(const[key,component]ofObject.entries(icons))if(!keyByComponent.has(component))keyByComponent.set(component,key);constkebabByKey=newMap();for(constkebaboficonNames)kebabByKey.set(toRecordKey(kebab),kebab);
It is the sole entry in check-entry-guard.mjs's KNOWN_IMPORT_UNSAFE, and that entry is true: those loops really do run inside an importer.
Why the obvious remedy is wrong — measured, not reasoned
The maps are module-private but they are read by the exportedliveSpellingFor and, through it, describeName. Importers really call both: scripts/__tests__/check-lucide-icon-record-names.test.ts calls liveSpellingFor at :153 and describeName at :204.
Ablation on #6092's PR 2 branch — both loops moved inside if (isEntrypoint(import.meta.url)), mutation confirmed on disk (top-level loop lines 1 to 0, indented-behind-guard 1), restored by trap with git checkout HEAD --:
check-entry-guard.mjsgoes green on rule 2 and reports the KNOWN_IMPORT_UNSAFE line as STALE. The baseline is satisfied.scripts/__tests__/check-lucide-icon-record-names.test.tsfails 5 of 25.- And the failure is not merely a red test. With the maps empty for importers,
describeName('BarChart3') stops saying write `chart-column` and says this instead:
"BarChart3" -> `BarChart3` is exported by lucide but is not a key of the runtime
`icons` record, and no live key names the same glyph.
That is a wrong diagnosis for a real violation, printed by a gate that still exits 1. Trading a correct message for a green baseline is a worse outcome than the unguarded loops, which is why #6092 stopped rather than shipping it.
The shape that satisfies both
Build the two maps lazily, on first use inside liveSpellingFor, memoised. Then:
- nothing runs at module top level, so rule 2's line can be deleted honestly;
- every importer keeps getting exactly what it gets today, because the maps are built the moment anything reads them;
- the CLI path is unchanged.
Worth confirming while doing it: icons and iconNames are themselves top-level await import(...) of lucide-react (:188–:191), so this module is never cheap to import. The lazy build removes the two loops, not that cost — if the goal is a genuinely cheap import, that is a separate and larger question about those four top-level awaits.
Why this is filed rather than done
#6092's ruling was explicit that restructuring a working module to satisfy a baseline is not a call an implementing seat makes. The KNOWN_IMPORT_UNSAFE comment in check-entry-guard.mjs now carries this measurement and points at the lazy-build shape; this card is the one it says that line owes.
Refs: #6092 (the sweep, and the STOP ruling) · #6133 (the gate and its rule 2) · #5633 (why the gate exists).
Generated by Claude Code
Split out of #6092 by the PR 2 seat, under that card's own STOP condition. #6092's dispatch order ruled: "⛔ If those loops feed module-level exports that importers actually read, STOP and report rather than restructuring the module." They do. This records the measurement so the next seat does not re-derive it, and names the shape that would actually work.
What is there
scripts/check-lucide-icon-record-names.mjsbuilds two lookup maps in top-levelforloops, outside any guard —:243and:245onmainafter #6092's PR 2:It is the sole entry in
check-entry-guard.mjs'sKNOWN_IMPORT_UNSAFE, and that entry is true: those loops really do run inside an importer.Why the obvious remedy is wrong — measured, not reasoned
The maps are module-private but they are read by the exported
liveSpellingForand, through it,describeName. Importers really call both:scripts/__tests__/check-lucide-icon-record-names.test.tscallsliveSpellingForat:153anddescribeNameat:204.Ablation on #6092's PR 2 branch — both loops moved inside
if (isEntrypoint(import.meta.url)), mutation confirmed on disk (top-level loop lines 1 to 0, indented-behind-guard 1), restored bytrapwithgit checkout HEAD --:check-entry-guard.mjsgoes green on rule 2 and reports theKNOWN_IMPORT_UNSAFEline as STALE. The baseline is satisfied.scripts/__tests__/check-lucide-icon-record-names.test.tsfails 5 of 25.describeName('BarChart3')stops sayingwrite `chart-column`and says this instead:That is a wrong diagnosis for a real violation, printed by a gate that still exits 1. Trading a correct message for a green baseline is a worse outcome than the unguarded loops, which is why #6092 stopped rather than shipping it.
The shape that satisfies both
Build the two maps lazily, on first use inside
liveSpellingFor, memoised. Then:Worth confirming while doing it:
iconsandiconNamesare themselves top-levelawait import(...)oflucide-react(:188–:191), so this module is never cheap to import. The lazy build removes the two loops, not that cost — if the goal is a genuinely cheap import, that is a separate and larger question about those four top-level awaits.Why this is filed rather than done
#6092's ruling was explicit that restructuring a working module to satisfy a baseline is not a call an implementing seat makes. The
KNOWN_IMPORT_UNSAFEcomment incheck-entry-guard.mjsnow carries this measurement and points at the lazy-build shape; this card is the one it says that line owes.Refs: #6092 (the sweep, and the STOP ruling) · #6133 (the gate and its rule 2) · #5633 (why the gate exists).
Generated by Claude Code