* Fix: stop gray-on-color false positives on Tailwind opacity and JSX (#633)
Do not treat bg-*/10 tints as solid fills, and pair gray text with chromatic backgrounds only inside the same tag and ternary arm.
AI assistance: prepared with Cursor Grok under maintainer direction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: keep nested ternary arms and post-ternary classes exclusive (#633)
Recurse exclusive class scopes so nested else-arms do not pair, and treat classes after a finished ternary as shared across both arms.
AI assistance: prepared with Cursor Grok under maintainer direction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: ignore nullish coalescing when scoping gray-on-color (#633)
The second ? in ?? was treated as a ternary delimiter, so exclusive
arms stayed in one scope. Prepared with Cursor Grok under maintainer
direction.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix URL scan failure exit codes
Return exit 1 when browser setup or a URL scan fails, including partial multi-target scans, while preserving JSON findings output. Document the detector exit contract and cover isolated installs without Puppeteer.\n\nAI assistance disclosure: Codex implemented and tested this fix under maintainer direction.
* Fix local target failure exit codes
AI assistance disclosure: Codex implemented and tested this fix under maintainer direction.
* Handle unreadable detector targets
AI assistance disclosure: Codex implemented and tested this fix under maintainer direction.
* Report unreadable detector directories
AI assistance disclosure: Codex implemented and tested this fix under maintainer direction.
* Fix: resolve unique --target names in monorepos (#700)
Bare child names such as Cantaro.Web now match a unique workspace candidate instead of being reported missing.
AI assistance: Cursor Grok 4.6 implemented this change.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: resolve --target once in the context CLI
Reuse the resolved path for loadContext so a bare name does not walk workspace candidates twice.
AI assistance: Cursor Grok 4.6 implemented this change.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: match unique --target names after cwd absolutizing
Live and other helpers resolve --target against cwd before context.mjs sees it. Treat a missing single-segment path the same as a bare workspace name so those callers still select the unique child.
AI assistance: Cursor Grok 4.6 implemented this change.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
* Fix detector URL and advisory handling
Recover joined URL arguments without splitting local paths, derive advisory behavior from registry severity across consumers, inspect readable linked CSS in URL scans, and report only the dominant primary font.
AI assistance disclosure: Implemented and verified with Codex under maintainer direction.
* Filter linked CSS to rendered selectors
Flatten linked stylesheet grouping rules and collect only selector rules that target the live DOM, preventing unused grouped and selector-less patterns from leaking into URL findings.
AI assistance disclosure: Implemented and verified with Codex under maintainer direction.
* Fix detector review edge cases
AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction.
* Preserve unresolved linked CSS selectors
AI assistance disclosure: Codex implemented and verified this fix under maintainer direction.
* Fix linked CSS selector filtering
Resolve pseudo-element selectors to live hosts, reject unresolvable linked CSS findings, and make the regression assertions independent. Also ignore comment delimiters when recovering CSS rule selectors.
AI assistance disclosure: This commit was prepared with Codex under maintainer direction.
* Skip unresolved container query CSS
Exclude linked container-query groups when their current applicability cannot be resolved, with a browser regression proving inactive styles do not leak.
AI assistance disclosure: This commit was prepared with Codex under maintainer direction.
* Detect active container query CSS
Use a temporary custom-property probe so the browser decides whether a nested style rule actually applies in the current container layout.
AI assistance disclosure: Codex helped implement and test this fix under maintainer direction.
* Filter inactive linked CSS states
Keep valid empty pseudo-class matches authoritative and omit selector-less linked at-rules that cannot be tied to rendered nodes.
AI assistance disclosure: Codex helped implement and test this fix under maintainer direction.
* Parse pseudo-elements without rewriting literals
Preserve quoted attribute values and escaped identifiers while resolving real pseudo-elements to live hosts.
AI assistance disclosure: Codex helped implement and test this fix under maintainer direction.
* Restore live linked keyframes
AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction.
* Handle grouped linked keyframes
AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction.
* Respect keyframe definition order
AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction.
* Resolve effective linked keyframes
AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction.
* Fix keyframe easing detection
Serialize effective per-keyframe easing back into the linked stylesheet corpus so overshoot motion is detected. Add a browser regression with a neutral animation name.\n\nAI assistance disclosure: Codex helped implement and test this fix under maintainer direction.
* Fix CSP and hook ancestor discovery
Recognize Next.js 16 proxy files when detecting runtime CSP and mirror harness ancestor lookup when locating active hook manifests for nested projects.
AI assistance disclosure: Implemented and verified with Codex under maintainer direction.
* Tighten hook and proxy discovery
AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction.
* Honor ancestor hook disable config
AI assistance disclosure: Codex implemented and verified this fix under maintainer direction.
* Keep hook discovery within target repository
Stop manifest discovery at the target repository boundary instead of re-adding an outer workspace root, with regression coverage for nested Git targets.
AI assistance disclosure: This commit was prepared with Codex under maintainer direction.
* Detect proxy CSP in nested Next apps
Recognize proxy files at root or src placement relative to nested Next project markers while continuing to ignore unrelated proxy helpers.
AI assistance disclosure: This commit was prepared with Codex under maintainer direction.
* Resolve external targets from their own repository
Scope explicit sibling targets to their own Git root so caller context and hook manifests cannot suppress required detector guidance.
AI assistance disclosure: This commit was prepared with Codex under maintainer direction.
* Isolate explicit targets at Git boundaries
Keep nested repositories and external targets out of caller and home-level context or hook discovery.
AI assistance disclosure: Codex helped implement and test this fix under maintainer direction.
Ensure install, link, update, and check render static help before entering any operational path. Covers top-level and legacy routing for both -h and --help.
AI-assisted implementation under maintainer direction.
Move Codex and .agents skill versions under metadata while keeping all version readers compatible with legacy top-level frontmatter.\n\nAI assistance: prepared with Codex under maintainer direction.
Add a first-class OpenCode command bridge across builds, installs, updates, linked installs, and pinned shortcuts. Preserve current provider behavior while backfilling missing or drifted command files.\n\nAI assistance: contributor and maintainer work used AI tools as disclosed in the PR discussion and commits.
* Fix flat type hierarchy false positives
Use rendered semantic roles and dominant size frequency, align the adjacent-step guidance, and abstain in source-only scans.\n\nAI assistance: prepared with Codex under maintainer direction.
* Fix static hidden typography filtering
Honor the hidden attribute in the static wrapper and use raw browser findings in regression coverage.
AI assistance: prepared with Codex under maintainer direction.
* Align typography sampling with painted content
Count visibly painted aria-hidden text and exclude content-visibility hidden subtrees in both static and browser scans.
AI assistance: prepared with Codex under maintainer direction.
* Fix: never inject raw JSX in live-mode fallback (#454)
On React/JSX targets, missed HMR used to fetch source and DOMParser-inject it, painting {expressions} and comment markers as page text. Adopt a live wrapper that already has variants, otherwise leave HMR alone.
AI assistance: Cursor Grok 4.6 implemented this change.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: wait for unmounted JSX variants instead of tearing down (#454)
A missing live wrapper on React is often a closed modal or other route, not a failed generation. Leave the observer armed so mount can still reach CYCLING.
AI assistance: Cursor Grok 4.6 implemented this change.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: recover empty JSX replace wraps after fallback retries (#454)
Insert scaffolds still wait for HMR. A replace wrapper with no variants after retries is a failed generation and should leave GENERATING.
AI assistance: Cursor Grok 4.6 implemented this change.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: align live-reference setup assertions with current SKILL.src.md
#689 shortened Setup step 2, but the live-reference tests still expected the old playbook sentence, which kept CI red on main and this branch.
AI assistance: Cursor Grok 4.6 implemented this change.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Guard delayed accept and discard DOM fallbacks when framework/HMR ownership is present, while preserving static-page cleanup. Add unit/source regressions for both paths and refresh stale Setup wording assertions from #689.
AI-assisted: prepared with Codex under @pbakaus direction.
Declare UTF-8 on the generated live and detector JavaScript responses and cover both endpoints with integration assertions.\n\nAI-assisted: prepared with Codex under @pbakaus direction.
Resolve the CLI entry path through realpath and cover linked skill directories on Unix and Windows junctions.
AI-assisted change: implemented with Codex under @pbakaus direction.
Preserve critique trend history while closing the exact processed snapshot. Track target identity and content freshness for local files and URLs, isolate colliding streams, and prevent legacy snapshots from resurfacing after a modern close.
Validated with 50 focused tests, a clean 17-provider build, 834 core tests, passing GitHub CI and Cursor Bugbot, Greptile 5/5, and zero unresolved review threads.
AI-assisted maintainer repair: implemented and reviewed with Codex under @pbakaus direction.
Makes plugin instructions execute the installed plugin copy, safely quotes paths containing spaces, removes the broad Node pre-approval, and ships guarded generated plugin output.\n\nAI-assisted maintainer conflict resolution, review, and validation by Codex under maintainer direction.
Resolves the embed helper with a filesystem path, reports fallback accurately, and adds regression coverage while leaving generated harness output to the post-merge sync.\n\nAI-assisted maintainer repair, review, and validation by Codex under maintainer direction.
Centralize raster prompt lookup and PNG chunk parsing while preserving read, scan, replacement, and sidecar behavior.
AI-assisted merge: reviewed and executed by Codex under explicit interactive maintainer direction.
Treat the leading system face as primary so later Roboto fallbacks do not trigger overused-font, while named web-font primaries still flag.
AI-assisted merge: reviewed and executed by Codex under maintainer direction.
Store direction contracts in development-only surface briefs and guard against browser-delivered leakage with regression coverage.
AI-assisted merge: reviewed and executed by Codex under maintainer direction.
Make policy and merge-conflict blockers age as contributor work, keep maintainer-policy decisions out of ready state, auto-close regular contributors unless explicitly exempted, and mark new or reopened issues for triage.
AI-assisted change: implemented and validated by Codex under maintainer direction.
The generated browser bundle is rebuilt from the merged engine sources in the next commit's build step (both branches had regenerated it).
AI-assisted (Claude Code).
Bugbot on #599: path.resolve treated /assets/hero.css as filesystem-absolute. Both the working directory and the artifact's directory are tried; unreadable candidates skip.
AI-assisted (Claude Code).
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).
Greptile's follow-up P1 on #599: with --artifact set, only that HTML file was read, so a plate referenced exclusively from a linked stylesheet still read as unused. The bounded source walk now runs either way.
AI-assisted (Claude Code).
Greptile P1 on #599: unreferencedPlates read a plate referenced only from assets/hero.css as unused and the hero gate refused a valid build. The extension filter already keeps binaries out of the walk.
AI-assisted (Claude Code).
CI resolves playwright but has no downloaded chromium; launch threw instead of falling back to the catalog ranking, and every spec gate downstream failed. Launch failures now return the no-browser path (and the browser test skips instead of asserting).
AI-assisted (Claude Code).
Paul's decision on the tenth sweep's design question: hard vetoes (missing region, contradicted plate or text, SVG illustration, clipped plate, invented ink) stay unconditional; at overall >= HERO_MIN the numeric readings (ink colour, letter-spacing, line pitch, strip heights, box positions) print as advisories with the pass and belong to the polish pass. Every sweep-10 sample closes its hero under this condition, which settles 07 without another paid round.
Ninth-sweep defects: codeDrawn / container / bleed now persist into spec.json with WARN lines (an overridden refusal used to vanish from the record); font-match probes os.tmpdir() and points TMPDIR at .impeccable/tmp when the sandbox /tmp is unwritable (every ninth-sweep rank silently fell back to the catalog).
AI-assisted (Claude Code).
Bugbot on PR #665: the skipScan guard emptied only the analytic
collectBrowserFindings pass, and scan()'s detached visual-contrast
stage then repopulated an ignoreFiles-waived page with contrast
markers and a second non-zero results post. Hoist the guard into
skipScanActive() and honor it in scan() and the async collector;
regenerate the browser bundle.
Adds a browser-backed regression test that reproduces the leak
(second results post carrying low-contrast findings) and pins the
zero contract; drops a tautological assert flagged in review.
AI-assisted change: implemented with Claude Code under maintainer
direction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Read waiver config from every live root (appRoot, contextRoot, repoRoot),
so monorepo projects whose config lives at the repo root reach the
overlay; serialize served roots and page identities repo-relative there.
Resolve each page URL to its actual serving file via the inject config's
resolved page list before applying file-scoped waivers; ambiguous URLs
keep the conservative common-ancestor fallback (PR #645 review
discussion r3840011436).
Honour detector.ignoreFiles: a wholly waived page now scans to zero
findings in the overlay, matching the CLI and the edit hook.
Guard the resolver call so a throwing resolver degrades to an unfiltered
scan instead of breaking the detect toggle.
Match design-system-color waivers by color value across hex and rgb()
spellings, and stop extracting font values for bounce-easing findings,
mirroring extractFindingIgnoreValue. Regenerate the browser bundle.
AI-assisted change: reviewed, planned, and implemented with Claude Code
under maintainer direction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The per-project state dir key is now the readable separator-mapped
slug plus an 8-hex sha256 of the resolved project path. The readable
part alone is lossy (/x/my.app and /x/my-app both mapped to -x-my-app
and shared hook state); the digest keeps distinct projects' cache and
pending state apart while the dir name stays human-scannable.
- Tilde roots now expand via os.homedir() instead of HOME/USERPROFILE
with a '.' fallback. When no home dir can be determined, expansion is
rejected and state falls back to the project-local default rather
than anchoring under the hook process's working directory.
- Tests updated to the digest-suffixed slug via a mirrored slugFor()
helper, plus two new cases: colliding readable slugs get distinct
state dirs, and the tilde form resolves identically to the explicit
homedir-joined form.
Prepared with AI assistance (Claude Code) under direction of
0xDarkMatter, per the maintainer-approved issue #422.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Env files and settings JSON hand '~/caches' to Node unexpanded; without
this it would resolve to a literal '~' directory under the process cwd.
Mirrors the exact treatment IMPECCABLE_HOOK_LOG already gets in
writeAuditLog (HOME || USERPROFILE fallback), plus the Windows '~\'
spelling.
Prepared with AI assistance (Claude Code) under direction of
0xDarkMatter, per the maintainer-approved issue #422.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- hookStateDir now trims the env value (stray whitespace in env files)
and path.resolve()s both the root and the cwd, so trailing separators
and relative segments slug to the same per-project dir.
- The #344/#305 persist gate also treats an existing (possibly
redirected) cache file as the opted-in marker. Without this, once
state relocated, clean-edit editCount bumps stopped persisting because
the project-local .impeccable/ dir never appears. No-op under stock
paths, where the cache file lives inside .impeccable/.
- New tests: slug normalization equivalences, whitespace trim, graceful
persistCache failure on an unusable root, and three runHook
end-to-end cases (findings persist + dedup through the redirect,
clean-edit editCount persistence, and the no-footprint no-op gate
holding under redirect).
Prepared with AI assistance (Claude Code) under direction of
0xDarkMatter, per the maintainer-approved issue #422.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Honor an optional IMPECCABLE_CACHE_ROOT env var in getCachePath() /
getPendingPath(): when set, hook.cache.json and hook.pending.json land
under $IMPECCABLE_CACHE_ROOT/<project-slug>/ (slug = project path with
[:\/.] mapped to hyphens, mirroring Claude Code's ~/.claude/projects/
convention). Unset or blank env keeps stock project-local behavior.
User-authored config (config.json, config.local.json, design.json)
deliberately stays project-local - only disposable state relocates.
Also clears ambient IMPECCABLE_CACHE_ROOT at the top of hook.test.mjs so
a developer running the suite with the redirect active still gets
deterministic stock-path assertions; the new suite sets and restores the
var explicitly.
Prepared with AI assistance (Claude Code) under direction of
0xDarkMatter, per the maintainer-approved issue #422.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collapse the nightly alternate plan into the shared event routing while preserving every GitHub output and schedule behavior. Strengthen the nightly characterization for all deterministic suites.\n\nAI assistance: prepared by OpenAI Codex under maintainer pbakaus's standing scheduled-refactor authorization.
Cursor's review caught the previous commit over-correcting. One tree listed
at two depths (prototype/*.html plus prototype/library/**/*.html) derived
two roots, and requiring a waiver to match under both stopped a normal
project-relative waiver like prototype/index.html from applying anywhere.
The rule both reviews were circling is simpler: one live session is served
by one server, so a single document root must sit at or above every
configured page. The only prefix the resolver can safely assert is the
deepest common ancestor of the glob roots. Nested roots collapse to their
shared tree, so normal waivers keep applying. Disjoint roots (src/ and
public/) share nothing, so no prefix is asserted and only the URL path
itself matches, which keeps the earlier fix intact: a src/foo.html waiver
still cannot hide a finding on a page served from public/foo.html.
This also deletes the match-under-every-root machinery from the previous
commit; with a single asserted prefix, plain matching is enough.
Also switches the new test file to derive the repo root from
import.meta.url rather than process.cwd(), per review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Greptile's review found a real bug in the new resolver. When the live config
lists pages under more than one folder (src/**/*.html and public/**/*.html),
the overlay treated a URL like /foo.html as src/foo.html and public/foo.html
at the same time. A waiver written only for src/foo.html could then hide a
finding on the page actually served from public/foo.html. That fails in the
worst direction: a real finding disappears and nothing says so.
The overlay can never look up the right file. The live server does not serve
the pages; the project's own dev or static server does, and its URL-to-file
mapping is invisible from here. So the fix stops guessing: a file-scoped
waiver now applies only when it matches the URL path itself, which is true
whichever folder serves the page, or when it matches under every configured
folder, so no possible reading disagrees. Anything ambiguous shows the
finding, which is also what the CLI reports for the file really being served.
With a single configured root, the common case, nothing changes: the new
rule reduces to the old behaviour exactly. Multi-root projects keep three
ways to write a waiver that still applies: name the file under each folder,
use the bare path, or use **/.
Two new unit tests pin the ambiguous case and the safe spellings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The live overlay's detect scan ran unfiltered: requestDetectScan() posted
only { scanId }, so detector.ignoreRules and detector.ignoreValues in
.impeccable/config.json reached impeccable detect and the edit hook but
never the surface a designer actually watches.
The server now serializes the project's detector waivers into the /live.js
prelude (window.__IMPECCABLE_PROJECT_IGNORES__), read per request through
hook-lib's readConfig so config.local.json wins and edits land on the next
tab reload. A new script part, live-browser-ignores.js, resolves that
config against the page URL when a scan starts: ignoreRules suppress
outright, wildcard ignoreValues suppress their rule in the files their
globs name, and the remaining entries ride along as disabledValues for the
detector to match on each finding's own value. The detector bundle applies
those where the findings are assembled, since the overlay draws its own
markers from the collected findings.
Scope resolution mirrors cli/lib/impeccable-config.mjs deliberately: the
same glob dialect (globToRegex, including {a,b} alternation), the same
path-suffix matching as findingMatchesScopedIgnoreFile, and the same
refusal to apply an unscoped wildcard entry. The served-root prefixes that
bridge project-relative globs and site-relative URLs come from the inject
config's own files globs, never from the ignore globs; deriving them from
the ignore globs lets one entry scoped to prototype/library/** lend its
prefix to every page and suppress site-wide, which looks like success
because the numbers go down.
Known gaps, recorded in the detector comment: the motion value extractor
is not mirrored, so a value-scoped bounce-easing waiver only matches when
the finding carries ignoreValue directly, and design-system-color matches
on the normalized string without the CLI's color-equality fallback.
Tests: unit tests for the resolver part (stale globals, string ignoreRules,
malformed entries, directory URLs, percent-escapes, glob metacharacters,
the roots trap), an extension-mode puppeteer test that disabledValues
suppress exactly the waived findings, and the live-browser regression pin
now asserts the new scan config shape instead of { scanId }.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A reference, not the page: --r-<id>-x/y/w/h in % of the comp (plus cap height, font-size, family, weight where measured) to bind to any markup, and hero-reference.html with every region at its box and every plate placed with object-fit: contain, as a check on positions. Attacks the most common execution failure of weaker builders (badly positioned, overflowing, pushed below the fold) without dictating structure to strong ones; overlapping boxes are overlapping boxes and the gate reads pixels regardless.
AI-assisted (Claude Code).
From the human review's most repeated pin ('terrible svg instead of asset', on every model) and from sessions that wrote 'ship' with the hero open. Icons, arrows, chevrons, and runtime data charts stay code; diagrams, notation, and leader lines are plates.
AI-assisted (Claude Code).
From the final review batch: 'bad asset crop (crops are never allowed)' twice, 'letter spacing way too wide'. A crop resampled to the region scores 99.8% structure against the raw region; a produced plate scores 30-60.
AI-assisted (Claude Code).
A session's own note said its hero sat at 67 because the 10x10 grid boxes straddled two elements each, and it was right: every downstream measurement (cap height, line count, structure) inherited the slop. Text and control regions now snap to the dominant connected ink inside the span (page-ground threshold, dilated cells, masses touching the span's sides lose to inside masses), keep the span on the record for coverage, and can opt out with snap: false.
AI-assisted (Claude Code).
From the second review batch: the best build of the fifth sweep passed the hero at 87% with the cover arch cut flat on the left (object-fit: cover on a box narrower than the shape), and legends, badges, and extra controls one or two cells wide slipped under the invented-ink floor. comp-spec measures the artwork's contiguous contact with each box edge against the page ground; the spec gate refuses such a box unless bleed is set. Human pass line landed at comp-diff 72-73; HERO_MIN stays 0.72.
AI-assisted (Claude Code).
From the first human review of sweep-3 builds (pins on 12 samples): fonts at the wrong size, weight, colour, or place; nav bars too tall; kickers and dividers the comp does not have; a footer strip pushed off the frame that read as drift on ground colour alone; a drawing filed as chrome with no note to catch it.
- lib/hero-checks.mjs: textRegionCheck (cap height, line count, ink density, ink colour, first-line offset vs the comp crop, measured fresh), chromeStripCheck (first rule row), inventedInk (build energy over a calm comp cell and neighbourhood). Wired into gateHero as reasons; invented ink vetoes at 4% of cells.
- comp-diff verdictFor: detailRaw < 0.15 is missing whatever the palette.
- comp-spec: every region carries a note.
- font-fingerprint: the tall-line filter takes its median over lines with real mass, so two display lines above a small line are not 'tall'.
AI-assisted (Claude Code).