From 64001fe213429b957a246fc29eff0c39643973be Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Fri, 28 Aug 2026 15:26:28 -0700 Subject: [PATCH] unreferencedPlates follows the artifact's linked stylesheets by name Greptile's third P1 on the same seam: a stylesheet linked from the artifact but outside the bounded walk's root, depth, or file limit was still invisible. The hrefs the artifact itself declares are resolved against its directory and joined to the corpus, which closes every variant. AI-assisted (Claude Code). --- skill/scripts/build-phase.mjs | 21 ++++++++++++++++++--- tests/build-phase.test.mjs | 7 +++++++ 2 files changed, 25 insertions(+), 3 deletions(-) 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 }); }