diff --git a/skill/scripts/build-phase.mjs b/skill/scripts/build-phase.mjs
index d29b51ebf..73b367e8a 100644
--- a/skill/scripts/build-phase.mjs
+++ b/skill/scripts/build-phase.mjs
@@ -342,9 +342,24 @@ export function unreferencedPlates(spec, artifact = null) {
const plates = (spec?.regions || []).filter((r) => r.medium === 'raster' && r.plate);
if (!plates.length) return [];
// The artifact narrows nothing: a plate may be referenced only from a
- // stylesheet the artifact links (assets/hero.css), so the source walk runs
- // either way and the artifact is simply guaranteed a seat in the corpus.
- const files = [...new Set([...(artifact && fs.existsSync(artifact) ? [artifact] : []), ...sourceFiles()])];
+ // stylesheet the artifact links, so the source walk runs either way, the
+ // artifact keeps a seat in the corpus, and the stylesheets it links are
+ // added by name (resolved against the artifact's own directory), which
+ // covers a stylesheet outside the walk's root, depth, or file limit.
+ const linked = [];
+ if (artifact && fs.existsSync(artifact)) {
+ linked.push(artifact);
+ try {
+ const html = fs.readFileSync(artifact, 'utf8');
+ for (const m of html.matchAll(/]*href=["']([^"']+)["'][^>]*>/gi)) {
+ const href = m[1];
+ if (/^(https?:|data:|\/\/)/i.test(href)) continue;
+ if (!/rel=["']?stylesheet/i.test(m[0]) && !/\.css(\?|$)/i.test(href)) continue;
+ linked.push(path.resolve(path.dirname(artifact), href.split('?')[0]));
+ }
+ } catch { /* the artifact still counts */ }
+ }
+ const files = [...new Set([...linked, ...sourceFiles()])];
let corpus = '';
for (const f of files) { try { corpus += fs.readFileSync(f, 'utf8') + '\n'; } catch { /* skip */ } }
const missing = [];
diff --git a/tests/build-phase.test.mjs b/tests/build-phase.test.mjs
index 8bfa65654..cdcfa7162 100644
--- a/tests/build-phase.test.mjs
+++ b/tests/build-phase.test.mjs
@@ -478,7 +478,14 @@ describe('unreferencedPlates', () => {
// an explicit artifact does not bypass the stylesheet walk
fs.writeFileSync(path.join(d, 'index.html'), '');
assert.deepEqual(unreferencedPlates(spec, path.join(d, 'index.html')), [], 'the linked stylesheet still counts with --artifact set');
+ // a linked stylesheet beyond the walk's reach (deep path) still counts
+ const deep = path.join(d, 'a', 'b', 'c', 'e', 'f', 'g', 'h', 'styles');
+ fs.mkdirSync(deep, { recursive: true });
+ fs.writeFileSync(path.join(deep, 'deep.css'), ".hero { background-image: url('hero-plate.png'); }");
+ fs.writeFileSync(path.join(d, 'index.html'), ``);
fs.writeFileSync(path.join(d, 'assets', 'hero.css'), '.hero { background: red; }');
+ assert.deepEqual(unreferencedPlates(spec, path.join(d, 'index.html')), [], 'a stylesheet linked by the artifact counts wherever it lives');
+ fs.writeFileSync(path.join(deep, 'deep.css'), '.hero { background: red; }');
assert.equal(unreferencedPlates(spec, null).length, 1, 'an unreferenced plate is still named');
assert.equal(unreferencedPlates(spec, path.join(d, 'index.html')).length, 1, 'and with the artifact set too');
} finally { process.chdir(prev); fs.rmSync(d, { recursive: true, force: true }); }