forked from bchanot/claude
fix(seo,geo): C1a — find sees build output, grep does not; the two disagreed
Surfaced by dogfooding on a real Astro repo instead of reading the spec. Claude Code installs a shell function routing `grep` to ugrep with `--ignore-files`, so grep honours .gitignore and never descends into a gitignored dist/. `find` honours nothing. seo-analyzer uses both. Measured on zenquality (Astro, dist/ gitignored but built locally), seo-analyzer.md:497 returned 92 images, 45 of them under dist/ — every asset listed twice, source and generated copy, byte-identical. Two live consequences: - "top 20 by size" was ~10 real images dressed as 20. - Batch C (`cwebp -q 80 <img> -o <img>.webp`) could target dist/og-image.png; the .webp lands in dist/ and the `npm run build` the dispatcher runs to VERIFY the fix erases it. Fix lands, verification passes, nothing survives, report says applied. lib/source-scope.sh separates source from build output, framework-aware. `public/` is deliberately NOT excluded by default: it is Astro/Vite/Next SOURCE and holds favicon.ico, apple-touch-icon.png and robots.txt — the very files STEP 4 curls. It is build output only for Hugo/Gatsby, detected from config (legacy config.toml alone is ambiguous, so it needs archetypes/ too). Blanket-excluding it would blind the audit to its own resource checks. findargs emits one token per line and MUST be consumed via a quoted array. I shipped a flat-string version first and the dogfood caught it: the shell globs */dist/* against the CWD and passes the matches to find as search paths, which turned 90 hits into 135 and kept every dist/ file. Both the header and a functional test now pin that. Also: no bundle item may target build output, in either agent. That is the real safety net — even if some future find leaks a dist path, the fix cannot land there. Fix the source that generates the artifact; if the source cannot be found, that is a finding, not a reason to patch the artifact. SCOPE CORRECTION: the proposal claimed grep was auditing 86 generated files instead of 9 templates. That was FALSE — the ugrep shim already skips them. Killed my own premise before coding it; the real bug is narrower and lives in find only. Do NOT "fix" the grep lines to match: they are already correct, and adding these exclusions there would drop public/. Verified: 90 -> 45 images on the real repo, 0 dist survivors, public/ preserved (favicon.ico still visible); 34 new assertions PASS / 0 FAIL; full suite green; shellcheck clean. Test file addition used the documented one-shot config-edit sentinel.
This commit is contained in:
+36
-4
@@ -181,8 +181,9 @@ keep the two consistent.
|
||||
ls .htaccess nginx.conf netlify.toml vercel.json wrangler.toml 2>/dev/null
|
||||
# SEO files
|
||||
ls robots.txt sitemap.xml sitemap-index.xml sitemap-images.xml sitemap-videos.xml 2>/dev/null
|
||||
# Legal pages
|
||||
find . -maxdepth 3 \( -iname "*mention*" -o -iname "*legal*" -o -iname "*confidentialite*" -o -iname "*privacy*" -o -iname "*cgv*" -o -iname "*cgu*" \) 2>/dev/null | head -10
|
||||
# Legal pages — source only (C1a: find ignores .gitignore, grep does not)
|
||||
mapfile -t FEXCL < <(bash ~/.claude/lib/source-scope.sh findargs)
|
||||
find . "${FEXCL[@]}" -maxdepth 3 \( -iname "*mention*" -o -iname "*legal*" -o -iname "*confidentialite*" -o -iname "*privacy*" -o -iname "*cgv*" -o -iname "*cgu*" \) 2>/dev/null | head -10
|
||||
# Analytics / trackers
|
||||
grep -rl "gtag\|GTM-\|analytics\|matomo\|_paq\|plausible\|umami" --include="*.html" --include="*.js" --include="*.tsx" --include="*.astro" --include="*.php" . 2>/dev/null | head -10
|
||||
# Cookie consent / CMP
|
||||
@@ -493,10 +494,32 @@ grep -rE '<img[^>]*>' --include="*.html" --include="*.astro" --include="*.tsx" -
|
||||
# Images missing dimensions (CLS risk)
|
||||
grep -rE '<img[^>]*>' --include="*.html" --include="*.astro" --include="*.tsx" --include="*.jsx" --include="*.php" . 2>/dev/null | grep -vE 'width=|height=' | head -30
|
||||
|
||||
# Check image asset sizes
|
||||
find . -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" \) ! -path "./node_modules/*" ! -path "./.git/*" -printf "%s %p\n" 2>/dev/null | sort -rn | head -20
|
||||
# Check image asset sizes — source only, never build output (C1a)
|
||||
mapfile -t FEXCL < <(bash ~/.claude/lib/source-scope.sh findargs)
|
||||
find . "${FEXCL[@]}" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" \) -printf "%s %p\n" 2>/dev/null | sort -rn | head -20
|
||||
```
|
||||
|
||||
**Why the guard, and why `find` specifically (C1a).** `grep` and `find`
|
||||
disagree about this repo and you use both. Claude Code routes `grep` through
|
||||
ugrep with `--ignore-files`, so it honours `.gitignore` and never descends
|
||||
into a gitignored `dist/`. `find` honours nothing. Measured on a real Astro
|
||||
repo: this command returned **92 images, 45 of them under `dist/`** — every
|
||||
asset twice, source and generated copy, byte-identical. So "top 20 by size"
|
||||
was ~10 real images dressed as 20, and a batch-C item
|
||||
(`cwebp -q 80 <img> -o <img>.webp`) could target `dist/og-image.png`, whose
|
||||
`.webp` the dispatcher's own `npm run build` then erases. The fix lands,
|
||||
verification passes, nothing survives.
|
||||
|
||||
`FEXCL` MUST be consumed as a quoted array. `find . $FEXCL …` lets the shell
|
||||
glob `*/dist/*` against the CWD and hand the matches to find as search paths
|
||||
— that made the same run return 135 hits and kept every `dist/` file.
|
||||
|
||||
Do NOT add these exclusions to the `grep` lines: the shim already covers
|
||||
them, `public/` is deliberately kept (it is Astro/Vite/Next SOURCE and holds
|
||||
`favicon.ico`, `apple-touch-icon.png`, `robots.txt` — the very files STEP 4
|
||||
curls), and it is build output only for Hugo/Gatsby, which the script
|
||||
detects.
|
||||
|
||||
Flag images over 100 KB as compression candidates. WebP/AVIF preferred
|
||||
over JPEG/PNG.
|
||||
|
||||
@@ -1195,6 +1218,15 @@ PROCHAINE ETAPE : <highest-priority>
|
||||
`Write` on shared templates. `Write` is reserved for files you
|
||||
solely own: sitemap.xml, .htaccess, legal pages, new city/service
|
||||
pages. Full-template refactor → escalate as user action in §11.
|
||||
- **NEVER emit a bundle item targeting build output (C1a).** No path under
|
||||
`dist/ build/ .next/ .nuxt/ .output/ _site/ .astro/ .svelte-kit/ out/` —
|
||||
`bash ~/.claude/lib/source-scope.sh list` is the authoritative set. Those
|
||||
files are regenerated: the `npm run build` the dispatcher runs to VERIFY
|
||||
your fix is what erases it. The fix lands, verification passes, nothing
|
||||
survives, and the report claims it was applied. This bites batch C hardest
|
||||
(`cwebp -q 80 <img> -o <img>.webp` on a `dist/` asset writes a `.webp` the
|
||||
next build deletes). Fix the SOURCE that generates the artifact; if you
|
||||
cannot find it, that is a finding — say so, do not patch the artifact.
|
||||
- **Landing page protection.** Zero visible change except meta tags,
|
||||
footer links, JSON-LD, image optimization.
|
||||
- **Preserve existing valid SEO.** Don't rewrite correct tags.
|
||||
|
||||
Reference in New Issue
Block a user