* Add data-impeccable-ignore scoped waivers; fix occlusion and image-backed contrast FPs
Three changes that let a page hosting deliberate anti-pattern exhibits
scan clean without losing coverage, prepared with AI assistance (Claude
Code) on maintainer instruction:
- data-impeccable-ignore="rule-a rule-b" (or "*" / bare) on any element
suppresses matching findings for its whole subtree, in the browser
overlay, the extension, and the static engine. The DOM twin of the
line-based impeccable-disable comments (which a live DOM cannot
apply) and the generalization of data-impeccable-allow-kickers.
Applied at the addBrowserFindings choke point, at the static element
walk, and for regex findings that carry a live selector.
- text-occlusion: an occluder whose effective opacity multiplies out to
~0 paints nothing. An opacity-0 range scrubber stretched over a
before/after comparison produced 16 "100% covered by an opaque
element" findings on one page because elementFromPoint returns it and
its UA background-color read as opaque paint. Invisible-at-rest
elements are also no longer probed as victims.
- Analytic contrast now skips what it cannot measure: a url() image
layer anywhere in the background stack ends the gradient-stops walk
(dark ink on a bright gold-leaf image measured 2.6:1 against the wash
composited over the wrong base), and elements that are invisible at
rest (visibility hidden, effective opacity ~0 — hidden scene decks)
are skipped by the color checks in both engines. The static cascade
now tracks opacity to support this.
Covered by a new scoped-ignore fixture (exact rule, star, comma list,
nested depth, wrong-rule control) tested in both engines, a scrubber
pass case in the occlusion fixture, and image-backed / photo-panel /
hidden-scene pass cases in the gradient-ground fixture. Full suite
passes; browser and extension bundles regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* CSS-scan findings carry their enclosing selector; browser pass resolves them
Page-level CSS-text findings (marquee, dark-glow, radial-halo,
repeating-stripes, codex-grid, ai-color-palette, image-hover-transform,
pseudo/inset side-tab stripes) now attach the selector of the rule that
matched, via a best-effort enclosingCssSelector() helper or the
selector already in scope. The browser pass resolves that selector
against the live DOM: pseudo segments are stripped, a selector that
renders nowhere on the page drops the finding (the CSS ships there but
the pattern never paints — the live DOM is ground truth in a browser
scan), and matches under a data-impeccable-ignore ancestor are waived.
Static scans are unchanged: partial documents keep the text-level
findings. Applied with AI assistance (Claude Code).
Covered in the scoped-ignore fixture: a live marquee under a marquee
waiver is suppressed, and dead two-axis grid CSS matching no element is
dropped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Attribute selectors on gradient-text and bounce-easing page emitters too
Same mechanism as the previous commit, extended to the three page-level
motion/text emitters that were still selector-less. Applied with AI
assistance (Claude Code).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* text-overflow: skip SVG content; scrollWidth lies there
Chrome reports arbitrary non-zero scrollWidth/clientWidth on SVG
elements (a <text> gave 78/48 while its rendered length sat inside its
box), so the box-metric delta is noise. SVG clips to its own viewport
anyway. Pass case added to the quality fixture. Applied with AI
assistance (Claude Code).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Overlay samples image-backed text at the pixel level by default
Visual contrast gains a third mode. Explicit true still runs the full
sampled pass, explicit false still disables everything (the mode the
test suites use), and unset — the default overlay run — now samples
ONLY image-backed text: the one class the analytic walk deliberately
skips, because a url() layer's pixels are unknowable without looking.
The cost is bounded and the method is precise: at most a 3x3 grid of
sample points per candidate (degrading to 3 or 1 for small rects), the
source image drawn once to a canvas with only those pixels read, and
glyph ink never pollutes the samples because the image is drawn alone.
A cross-origin image without CORS headers reports unresolved rather
than guessing. Applied with AI assistance (Claude Code).
Covered by a new fixture: white text on a near-white same-origin
data-URI image background flags via sampled pixels under default
options; dark ink on the same image passes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Review fixes: root opacity, keyframe steps, static parity, attributed fixtures
Applied with AI assistance (Claude Code), addressing all seven findings
from the automated reviews:
- effectiveOpacityDOM walks through body and html: a page-fade wrapper
with body/html opacity 0 hides every descendant (Greptile executed a
Chromium repro of the false positive).
- enclosingCssSelector refuses `from`/`to` keyframe steps, which read
as never-matching type selectors and got valid findings wrongly
dropped by the zero-match rule (Bugbot, high). Regression case: an
overshoot bezier inside a `to` step must survive as page-level.
- The static cascade now inherits visibility, so descendants of a
hidden container compute as hidden like the browser path; a declared
visibility:visible still overrides.
- The static engine applies scoped waivers to selector-backed
html-pattern findings, mirroring the browser — but keeps findings
whose selector matches nothing, since static scans see partial
documents.
- The scoped-ignore fixture grows to the mandated matrix: 4 flag cases
(control, other-rule waiver, sibling waiver, misspelled rule id) and
5 waived shapes (exact rule, nested depth, star, comma list, self),
each with a unique border width so every finding attributes to
exactly one case in both engines' tests.
- The image-backed contrast test pins its cases via the sampled
finding's candidate text: the white-on-light specimen must flag and
the dark-ink control must stay clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Review fixes: image-only starvation, selector rejection class, inset stripes
Second review round, applied with AI assistance (Claude Code):
- The image-only filter moves inside the candidate collector, before the
cap: gradient/opacity/filter candidates earlier in DOM order no longer
consume the 12-candidate budget and starve the url()-backed texts the
mode exists to sample (Bugbot, high). The regression fixture packs 14
gradient decoys ahead of the photo panels, and the test now drives the
overlay entry (impeccableDetectAsync, default options) rather than
detectUrl's Node-side full fallback, which is where the image-only
mode actually lives.
- enclosingCssSelector no longer rejects the child combinator or quoted
attribute selectors; only braces and angle brackets disqualify.
- The inset box-shadow side-tab scanner attaches its selector like the
pseudo-element scanner does, so those findings waive and dead-drop
the same way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix: measure gradient body grounds instead of assuming white (browser mode)
A page whose ground is set via background: linear-gradient(...) on body
leaves backgroundColor transparent, and resolveBackground assumed white
for any body/html-level gradient. In a real browser that assumption is
wrong: the shorthand is always decomposed there, so reaching that branch
means the ground truly is the gradient. On a dark oklch gradient ground
(impeccable.style's lacquer) this turned every light-on-dark text into a
~1.3:1 "on #ffffff" low-contrast finding, ~120 false positives on one
site. Browser mode now returns null so the caller measures against the
actual gradient stops; the white assumption stays for jsdom, where the
undecomposed-shorthand rationale still holds.
Gradient stops also now parse modern color syntax: computed
backgroundImage keeps oklch()/oklab()/hsl()/hwb() stops as authored, and
parseGradientColors only read rgb()/hex, so a token-driven gradient
ground was invisible even once the walk deferred to it. New
parseGradientColorsModern routes those stops through parseAnyColor.
Covered by a Puppeteer fixture (dark oklch body gradient): light text on
the ground must not flag, muted dark-gray ink must, proving the stops
are measured rather than the checks silently skipping.
Prepared with AI assistance (Claude Code), on maintainer instruction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Composite translucent layers over gradient stops; parse modern glow stops
Review fixes from PR #557's automated reviews, applied with AI
assistance (Claude Code):
- Cursor Bugbot found the new browser-mode early return discarded the
translucent ancestors resolveBackground had collected: text on a
frosted wash over a body gradient was measured against raw stops.
resolveGradientStops now collects translucent layers during its own
walk (through readCascadeBackgroundColor, extracted so both walks
read surfaces identically) and composites every stop under them.
- Copilot flagged the other legacy parseGradientColors call sites. The
glow-context fallback now uses parseGradientColorsModern, since body
gradients reach it more often after this change. The AI-palette rule
and the injected analytic sampler stay on the legacy parser
deliberately: the former is a rule-behavior expansion deserving its
own fixtures, the latter degrades to pixel sampling or a skip.
- Greptile asked for standard fixture structure: the fixture now has
labeled flag/pass cases (3 flag, 5 pass) including the frosted-wash
pair that locks the overlay compositing in both directions and a
legacy hex-stop gradient guarding the original parser path.
The test scopes itself to the DOM path via visualContrast: false, the
suite's established pattern; the screenshot sampler is a separate
subsystem with its own coverage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Pin gradient-ground flag cases to their snippet signatures
Bugbot follow-up: a count-only assertion let an offsetting miss and
false positive cancel, especially the frosted pair. Each flag case now
asserts its full text-on-background signature, so the frosted case must
measure against the composited wash and the count guard excludes any
pass case flagging in its place. Applied with AI assistance (Claude
Code).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Comments: the static path is the custom engine now, not jsdom
jsdom left the dependency tree when the static-html engine (StaticElement
+ css-cascade.mjs) replaced it, and that engine does decompose the
background shorthand, so the comments this PR added were dated in both
name and rationale. Only comments touched by this PR are renamed; the
~40 legacy jsdom mentions elsewhere in checks.mjs are a separate sweep.
Applied with AI assistance (Claude Code).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Static engine: measure body gradients too, dropping the white assumption
Follow-up to the browser-mode fix: the white assumption for body/html
gradients was a jsdom guard, and jsdom is gone. The static cascade
decomposes the background shorthand (expandStaticDeclaration) and
preserves var() colors for later resolution, so a missing solid under a
body gradient is now as real in static mode as in a browser — and the
static engine had the identical false-positive class (light text on a
dark gradient ground flagged "on #ffffff") while missing the muted-ink
true positives on the same page.
The old catastrophic case cannot recur: opaque stops fully cover any
hidden solid (they are the ground), alpha stops composite over the
resolved base or the white canvas default, and unresolvable stops drop
rather than guess.
Static twin of the browser test added over the same fixture; the full
suite, the url()-ancestor guard, and a source scan of impeccable.style
(0 low-contrast findings) all stay clean. Applied with AI assistance
(Claude Code).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The value-capture regex stopped only at ;{}, so in single-line JSX
style objects it ran past the closing quote and swallowed later
properties, flagging layout props that were never transitioned. The
capture now stops at the matching closing quote when the value is a
quoted string, falling back to the old bounds for real CSS.
Prepared with AI assistance under maintainer direction.
Co-authored-by: Cursor <cursoragent@cursor.com>
Review finding on #553: an object-literal argument like
${getOffset({ size: 2 })} ended the interpolation match at the inner
closing brace, losing the shadow context. Interpolations now admit one
level of braces (with paired quotes inside); the shared subpattern is
hoisted into compiled constants. Deeper nesting stays fail-safe by
design: a line-scoped regex cannot balance arbitrary braces, and the
miss produces a waivable finding, never a leak.
AI-assisted (Cursor agent), reviewed by maintainer.
Co-authored-by: Cursor <cursoragent@cursor.com>
Review finding on #553: the interpolation subpattern excluded quotes,
so a documented shadow color after ${getShadow('lg')} or a quoted
ternary branch lost its context and fired as drift. Interpolations now
admit complete single/double-quoted strings; the quotes pair up inside
the ${...}, so an unpaired quote or the template's closing backtick
still ends the context and the allowance cannot leak to a later
property.
AI-assisted (Cursor agent), reviewed by maintainer.
Co-authored-by: Cursor <cursoragent@cursor.com>
Review finding on #553: the end-anchored shadow-context tails excluded
`}` (JS) and `{`/`}` (CSS), so a documented shadow color after a ${...}
interpolation in a boxShadow template literal or a CSS-in-JS
box-shadow line lost its allowance and fired as drift. Both tails now
admit complete ${...} interpolations; a bare `}`, quote, or `;` still
ends the context, so the allowance cannot leak past a template's
closing backtick into a later property.
AI-assisted (Cursor agent), reviewed by maintainer.
Co-authored-by: Cursor <cursoragent@cursor.com>
The detector never read the sidecar's extensions.shadows, and the only
workaround (a colors entry for black) allowlisted every black at every
alpha because colorKey() drops alpha. Shadow token colors now live in a
separate allowlist matched on alpha as well as r/g/b, and the allowance
applies only inside box-shadow / text-shadow values, so a documented
shadow black still fires as a page ground.
AI-assisted (Cursor agent), reviewed by maintainer.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Bound copy-edit prompt context
Whitelist and truncate staged operation context before it reaches the local agent prompt.
AI assistance: Implemented and validated with OpenAI Codex under maintainer authorization.
* Harden copy-edit prompt bounds
Bound repair, candidate, and element context consistently and preserve absent source positions as null.\n\nAI assistance: Implemented and validated with OpenAI Codex under maintainer authorization.
* Preserve bounded repair context
Keep repair attempt metadata and nested diagnostics while retaining prompt limits.\n\nAI assistance: Implemented and validated with OpenAI Codex under maintainer authorization.
Pass staged copy-edit prompts over stdin so large batches do not exceed platform argv limits.
AI assistance: Implemented and validated with OpenAI Codex under maintainer authorization.
* Resolve {{scripts_path}} in the agent bodies Codex ships
Three code paths emit an agent body: the degraded fallback reference, the
.toml nested inside the skill for Codex, and the native agent file. Only the
nested .toml skipped placeholder substitution and rule-marker stripping, so
the codex and .agents dists shipped `node {{scripts_path}}/embed-prompt.mjs`
verbatim in the asset producer, and every caller had to substitute the token
itself at load time.
All three now render through renderAgentBody(), and the new regression test
asserts a runnable embed-prompt command on each emitted surface plus a
synthetic agent proving markers and placeholders resolve in the nested .toml.
Prepared by an AI agent (Claude Code) under pbakaus's instruction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Give the finish reviewer's screenshots one fixed address
The Input Contract asked for "desktop and mobile screenshot paths captured by
the parent" and named none, so each session invented a filename and the
verdict pass went looking for a recapture that was never written there. Two
reviewer passes burned on that in the eval runs.
The parent now captures and recaptures to .impeccable/review/desktop.png and
.impeccable/review/mobile.png, and the reviewer reads those two first,
treating a brief-named path as the fallback for a parent that wrote elsewhere.
Prepared by an AI agent (Claude Code) under pbakaus's instruction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Lead Setup with the base directory the runtime reports
The rendered claude and codex skills opened with
`node .claude/skills/impeccable/scripts/context.mjs`, a project-relative path
that resolves in this repo and in nothing a user installs: a personal or
plugin install puts the scripts outside the project entirely. The working form
was already in the text, parenthesized, after the one that fails.
Setup now leads with `node <skill-base-dir>/scripts/context.mjs` and says once
that the base directory resolves every scripts-path command in the skill and
its references, leaving the project-relative path as the fallback for runtimes
that report no base directory.
Prepared by an AI agent (Claude Code) under pbakaus's instruction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Answer the Copilot review: brittle model assertion, missing review dir
Assert that {{model}} resolved rather than that it resolved to "GPT", which
belongs to PROVIDER_PLACEHOLDERS and can change without touching what the test
guards. And have the parent create .impeccable/review/ when the harness does
not, so a fresh project's first capture has somewhere to land.
Prepared by an AI agent (Claude Code) under pbakaus's instruction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Make the review-screenshot contract directory-based, not web-viewport-named
Two amendments to the recapture contract from review feedback:
1. The canonical location is the directory .impeccable/review/, one file
per captured viewport; desktop.png and mobile.png are the web case,
not the contract. Baking web-viewport names into the reviewer's spec
would have hardened a web assumption into paths that a native
(ios/android/adaptive) build cannot honestly write.
2. Precedence restored to explicit-beats-convention: paths the calling
brief names are authoritative when the files exist; the canonical
directory is where the reviewer looks when the brief names none or a
named path is missing. This avoids stale canonical files from an
earlier run silently winning over fresh explicit paths. The observed
failure (the verdict round inventing a round-stamped filename) stays
fixed: recapture happens over the same files, and invented filenames
are still called out as pointing at nothing.
Assisted-by: Claude Code
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix: use argv exec and single-quote escaping for the four #476 shell-injection sites
JSON.stringify and raw double-quote interpolation were used as shell quoting,
but /bin/sh still expands $(...), backticks, and ${} inside double quotes.
- is-generated.mjs / live.mjs runScript: switch execSync string commands to
execFileSync argv form, which never invokes a shell. Closes the remote path
where a source file named `$(...)` executes during the live-mode walk.
- skills.mjs hook command + hook-lib.mjs ignore-value suggestion: values that
must stay shell strings now use POSIX single-quote escaping instead of
JSON/double quotes. The doctor's hook-token parser learns the single-quoted
absolute form so it keeps verifying user-level installs.
Adds regression tests for the single-quoted absolute hook form and the
single-quoted ignore-value suggestion. Verified end to end in a browser through
a real live-mode wrap walk against a hostile-named source file.
Prepared with AI assistance (Cursor) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Test: lock in POSIX single-quoting for a $(...) absolute install path (#476)
Follow-up from security review: prove an install path embedding $(...) is
single-quoted in the written hook manifest, not double-quoted.
Prepared with AI assistance (Cursor) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: quote ignore-command args per platform so Windows cmd.exe keeps spaces (#533)
Greptile flagged that switching quoteCommandArg to POSIX single quotes fixed
$(...) injection on /bin/sh but regressed Windows cmd.exe, where single quotes
are literal, so a --file path containing spaces was split and the ignore scope
was stored malformed.
The suggested command runs on the same machine the hook fired on, so branch on
process.platform (the pattern skills.mjs already uses): single-quote on POSIX
for the #476 fix, and keep the original double-quote escaping on Windows so
that path's behavior is unchanged. Adds a regression test asserting both forms.
Prepared with AI assistance (Cursor) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Test: prove the POSIX hook guard is inert under /bin/sh and Windows keeps double quotes (#533)
Greptile's probe could not reach the generated manifest, leaving the hook
command contract unverified. Convert that into committed proof:
- POSIX: install with a $(touch pwned) absolute path, then actually execute the
generated guard under /bin/sh from a clean cwd and assert no marker file
appears and the guard exits 0 (single-quoted substitution stays inert).
- Windows: drive copyProviderHooks as win32 in-process and assert the command
keeps the double-quoted absolute path (usable when the install path has
spaces; $(...) is inert on cmd.exe anyway).
Test-only; source quoting is unchanged.
Prepared with AI assistance (Cursor) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix Windows libuv abort in concept-seed after a successful roll
process.exit() with a live fetch keep-alive socket trips libuv's
UV_HANDLE_CLOSING assertion on Windows (nodejs/node#56645), aborting
the CLI with 0xC0000409 after complete output on the successful-roll
path. Destroy the global undici dispatcher before the explicit exit
so no socket is left to race; the hard exit stays, keeping the
no-linger guarantee on blackholed networks.
Fixes#504
Prepared with AI assistance (Cursor agent) under maintainer direction.
* Add regression test for the successful-API dispatcher teardown
The suite covered local rolls and the unreachable-API fallback but
never a successful roll, the one path where a pooled keep-alive
socket exists at exit (issue #504). Serve a real /api/roll from a
local server and assert the CLI destroys fetch's global dispatcher
before its explicit exit. Verified to fail without the fix.
Prepared with AI assistance (Cursor agent) under maintainer direction.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
The install completion message said to run /impeccable init "in your AI
harness", and users pasted it into their shell instead (bash: /impeccable:
No such file or directory). Say the command is typed in the AI coding
agent's chat, and give `npx impeccable init` a pointed redirect instead of
the generic unknown-command error. A real path named `init` still routes
to detect as before.
Prepared with AI assistance (Cursor agent), directed by @abdulwahabone.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: install missing explicitly selected providers without --force (#500)
An explicit --providers list now treats "already installed" per selected
target: providers with an existing install take the update path, providers
with none get a fresh install (skills + hooks) in the same run. Previously
any existing install (e.g. .claude) made `install --providers=grok` exit 0
without writing .grok, leaving Grok Build on the Claude-variant fallback.
Written with AI assistance (Cursor agent), reviewed and tested by maintainer.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: copy provider agents for freshly installed mixed-install targets
Bugbot caught that the mixed explicit-providers path installed skills and
hooks for missing targets but skipped copyProviderAgents, which both the
update and fresh-install paths run. Written with AI assistance (Cursor agent).
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Restore reduced-motion build guidance
Restores the accessibility requirement and verification step to the animation playbook, with a regression test that keeps it on the build path.
Implemented and validated with OpenAI Codex assistance under standing maintainer authorization.
* Harden reduced-motion guidance regression
Normalizes CRLF input and accepts either reduced-motion spelling so the contract stays portable and intent-focused.
Implemented and validated with OpenAI Codex assistance under standing maintainer authorization.
* Anchor skill reference test to its module
Resolve the repository fixture path from the test module so the regression test is independent of the caller's working directory.
This change was prepared with AI assistance under maintainer authorization.
* Clarify reduced-motion guidance
Replace the double negative in the canonical animation guidance and keep the source contract aligned with the clearer wording.
This change was prepared with AI assistance under maintainer authorization.
* Polish the decision page: raise cycler, declined height, canon order, full card anatomy
Field feedback from the first real rolls of the verdict-routed hand:
- Several raises stacked on the assigned card blew it out of proportion.
More than one raise now renders as a compact cycler: one visible, a
counter, click or Enter advances. A single raise stays inline.
- Declined cards inherited the row's stretch alignment, so a narrow card
stood at the tallest contender's height, a strange stilt beside the
hand. They now size to their content.
- Deck order becomes a gradient of standing: contenders, then the canon,
then declined dead last. The canon between full alternates and the
demoted row reads as the familiar door rather than the last resort
after the rejects.
- Root cause of bare-bones challenger and canon cards in the field: the
--schema example only gave the assigned card palette, materials, and
risk, and models author payloads by imitating the example, so the
"same anatomy on every card" instruction lost to it every time. The
example now carries full anatomy on every card and the schema note says
a card with no palette chips is an authoring gap, not a data gap.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Decision cards carry full-fidelity comps instead of sketches
Field verdict on the sketch contract: the sketches came back too simple
to inform the choice, and generation takes the same time at any
fidelity, so the deliberately-unfinished frame paid comp cost for sketch
quality. The decision card's image is now that direction's north-star
comp, produced under visualize.md's comp discipline (structure-led
prompt, real name and content, no invented commercial claims), saved
under .impeccable/mocks/ with its prompt sidecar. Fairness between cards
comes from equal fidelity in each card's own grammar rather than shared
unfinishedness.
The chosen card's comp is never spent by the choice: on a comp-led build
it enters the comp round as compositional option one (visualize.md now
generates two variations beside it; a round arriving with no decision
comp still renders all three), and on a code-led build it returns at the
finish review as the critique reference. Produce order still front-loads
a re-roll's spend onto the cards read first.
serve-question keeps the sketch field's wire name for payload
compatibility; docs, schema paths, shimmer labels, and the answer
directive (CHOSEN COMP) speak comp.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: address PR review bot findings on the comp round
- Producer still forced sketches (cursor, high): the asset producer's
Decision Sketches contract still mandated deliberately unfinished matte
sketches, so the parallel path would keep shipping sketch-era images.
The section is now Decision Comps: full-fidelity north-star comp,
structure-led prompt, equal commitment across siblings, no invented
claims, sidecar written.
- Mocks collided with the approval check (cursor, high): decision comps
now live under .impeccable/mocks/decision/, visualize.md scopes the
no-approval finding to comp-round output, new-work.md states the
unchosen hand implies no approval, and the code-led finish packet names
the chosen decision comp as the critique reference in the approved-comp
slot.
- Raise cycler announces (greptile, both P1s): a visually hidden
aria-live region reads out the newly active raise and its position on
advance; initial render stays quiet.
- Declined width in the vertical deck (cursor, medium): align-self:
flex-start shrank declined cards to content width in the portrait
column layout, where the cross axis is horizontal; they stretch there
and keep content height in the row layout.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: raise cycler tooltip and label name both input modes
Copilot: the tooltip said Click while the control also answers Enter and
Space; the title and a new aria-label now say activate/press Enter.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: finish reviewer exempts decision comps from the approval check
cursor[bot] follow-through: the reviewer's Persistence check still
treated any comps under .impeccable/mocks/ as approval-gated, and the
reviewer never reads visualize.md by design, so code-led and spent-hand
rounds could draw a false skipped-approval finding. The check now scopes
to comp-round comps, exempts .impeccable/mocks/decision/ as the direction
round's dealt hand, and defines how a code-led build's decision comp is
judged in the approved-comp slot: the critique reference, under the
no-approved-comp fidelity rules plus what the image dared that the build
did not.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: the critique reference is its own reviewer input, not the approved-comp slot
cursor[bot]: passing the code-led decision comp through the approved-comp
slot dragged in that slot's obligations (inventory-first reading, the
fidelity matrix, Truth's shipped-asset demand for every image-native
region), which contradicts code-led's premise. The input contract now
names it a separate labeled critique-reference input that nothing binding
"the approved comp" touches, and Fidelity defines its treatment where the
no-approved-comp rules live: provocation, not spec; no matrix, citations,
or asset obligations; its dares enter material_fixes as ordinary fixes.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
* Route the direction hand by verdict, add the pick card, enforce salience parity
The decision round previously rendered every dealt challenger as an equal
full card whatever the weighing said, so a world that fused poorly (an
underwater world dealt to a flower shop) sat at the same visual weight as
the assigned direction, and concept-level fusion had no surviving output.
Three changes, all presentation-layer; the dice, the assignment, and the
two-axis weighing are untouched:
- Verdict routing: the weighing closes with wins / competitive / declined
per challenger, decided before any borrowing. Declined challengers render
demoted (narrow, quiet, catalog art as a labeled thumb, "Adopt anyway"),
reordered to the end of the deck by the page itself, still adoptable,
never silently dropped. Donations return as named "raised by" lines on
the assigned card: a declined challenger donates ambition and system
discipline, never its clothes.
- The pick card: one card for the model's top-ranked grounded candidate
when the dice assigned another, kicker MY PICK, honest familiarity risk
on its face. One card, never a ranked list, never the lead position; the
anti-menu rule survives with exactly this carve-out.
- Salience parity: a card's imagery weight is capped by the assigned
card's. With a text-only assigned card (no image generation in the
harness), full-bleed catalog heroes demote to labeled thumbs, so what
looks important is the verdict's call, never rendering luck.
serve-question payload gains additive fields (verdict, kept, raised); old
payloads render unchanged. concept-seed's rendered instructions carry the
verdict/donation contract and the pick-card carve-out. Covered by two
Playwright tests in the new-work e2e suite (verdict routing + parity).
Design exploration and rationale were worked through with the maintainer;
research grounding is impeccable.style/research lessons 3-5.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Add Safer/Bolder re-roll registers to the direction round
The re-roll gains the user's steering wheel on the familiar-to-bold axis.
The decision page renders two register buttons beside the plain re-roll
(payload: reroll: { registers: ["safer", "bolder"] }; booleans still work),
the answer carries the chosen register, and concept-seed gains --register.
The design constraint that shaped the implementation: a register changes
only what a round INSTRUCTS, never what it DEALT. The same key and reroll
count reproduce the same deal whatever the register, so the exclusion chain
never forks and the reproduction contract holds with no API change.
- bolder: the dealt foreign forms become the whole hand, every challenger a
full card; the first-dealt challenger leads (assignment by deal order, so
the dice still choose). The pick card sits out; the canon stays.
- safer: the round's dealt hand is spent unseen and stays excluded; the
model presents its remaining conventional grounded candidates (at most
three) plus the canon executed against named competitors. This is the one
sanctioned lineup of the model's own ranked list, existing only by
explicit user request. Works degraded (needs no catalog); bolder degrades
to a plain grounded round, disclosed.
Registers are user steering, never the model's to pre-select. Covered by a
concept-seed unit test (same-deal invariant, validation) and a Playwright
test (button, answer field, REGISTER directive).
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Add the execution-contract round: comp-led or code-led, chosen after the direction
The build previously went comp-led for everyone, silently: a generated comp
led and the build chased it, which produces the boldest compositions and
also the measured worst-of-both-worlds failure (ambitious design landed
poorly, no motion, fix rounds after). Models already defect from it by
quietly skipping comp generation, which is unsanctioned code-led with no
contract to catch it. This makes the fork explicit and both paths
defection-proof:
- Comp-led: the comp is law and non-optional once chosen; visualize.md and
the comp-is-king build phases run as today.
- Code-led: no comp of this page, skipped by contract rather than drift.
The QUALITY BAR boards still calibrate finish, and the ambition moves
into the written direction contract (FIRST VIEWPORT plus a named
signature interaction and motion grammar), audited by the finish
reviewer in behavior. Not a discount on commitment.
Placement: a second round on the same open table, right after the
direction lands. Sketches stay in the direction round (they pick the
world); comps are what code-led skips (they bind the composition). The
chosen world sets the default lead; the user flips freely; a standing
preference recorded in PRODUCT.md skips the round on later surfaces; with
no image generation there is no fork, code-led is the only path.
Mechanism: serve-question gains payload-level followup: true, which keeps
the detached server alive after a pick (exactly like re-roll), swaps the
page to the loading hand instead of goodbye, marks the answer with
followup: true so --wait keeps the table, and prints a FOLLOWUP OPEN
directive telling the agent to deliver the next round via --update.
Covered by a Playwright test driving the full two-round flow.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: address PR review bot findings
- Degraded safer register no longer contradicts itself (greptile,
Copilot, cursor): the degraded template previously said "the assigned
index is suspended; the user picks" and then emitted ASSIGNED INDEX,
the mandatory build instruction, and the restated footer anyway. The
degraded safer path now suppresses the assignment machinery entirely,
matching the non-degraded safer round, and restates the user-picks
behavior for truncated readers instead.
- A declined card's declared sketch no longer renders a full media face
(Copilot): the renderer ignores sketch slots on declined cards
outright, so a stray sketch cannot buy back the salience the verdict
took away.
- Bolder rounds no longer carry the generic weighing instruction
(cursor): it measures against the assigned grounded direction, which
the bolder register suspends; a leader-relative variant weighs the
fused challengers against the first-dealt leader instead.
All three pinned by new assertions in tests/concept-seed.test.mjs and
tests/new-work-e2e.test.mjs.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: followup never arms the loading hand in blocking serve mode
cursor[bot] caught a client/server disagreement: the page interpolated its
FOLLOWUP constant from the payload alone, so a followup: true payload served
in blocking mode (no --start) would leave the browser on a loading hand that
nothing resolves, since a blocking server exits on any pick and has no
update channel. The page constant is now armed only when the server is
detached, blocking rounds get the goodbye screen as before, and new-work.md
states that followup belongs only on a detached round; blocking and
structured-tool channels run the build-path round as its own second
question. Pinned in tests/serve-question.test.mjs.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Add card-kind choice telemetry and the bolder routing disambiguation
The choice ping previously fired only when a dealt catalog challenger won,
so pick-share and canon-share had no denominator and the decision page's
new spectrum could not be measured. The ping now fires once per resolved
attended round on API-dealt rolls: --kind names which card class won
(assigned / pick / challenger / canon), --chosen carries the catalog id
only when a dealt challenger won, and --register rides along when the
round came from a steered hand. Grounded candidates' names never leave the
machine (the ping carries the kind alone), the legacy id-only shape stays
valid, and DO_NOT_TRACK / IMPECCABLE_NO_TELEMETRY still disable the ping
entirely. The seed's TELEMETRY block teaches the new invocation.
Also the naming-collision guard: "bolder" said while a direction round is
open routes to the Bolder hand register, never the bolder refinement
command; one line each in bolder.md and new-work.md.
The /api/chosen field additions land in a sister impeccable-site PR; the
API ignores unknown fields meanwhile, so this is safe to ship first.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: ping test survives a DO_NOT_TRACK shell
cursor[bot]: the pingChosen unit test cleared only IMPECCABLE_NO_TELEMETRY,
so a developer shell with DO_NOT_TRACK set failed the success-path
assertions. The test now clears both, restores prior values in finally,
and passes under DO_NOT_TRACK=1.
AI-assisted change.
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
The list of Live chrome surfaces was inlined into live-browser.js as a
function-scope const when live/ui-core.mjs was deleted for having zero
in-repo references. It had one out-of-repo reference. The private
impeccable-site repo imports it at build time: its Live UI lab must hold
a snapshot for every surface Live defines, and the site build fails with
the surface name when one is missing. Inlining put the list out of reach
of every Node importer, so the site had to regex it back out of the
browser script, and the guard only kept passing because the site's
materialized copy of skill/ was stale.
A guard that reads a list the site itself maintains guards nothing, so
the fix is a real export rather than a better parser.
skill/scripts/live/ui-surfaces.mjs is now the single definition. The
browser-runtime constraint is unchanged and satisfied the same way the
command palette already solves it: live-browser.js is served raw and
injected as a classic <script>, so it cannot import an ES module. The
/live.js assembler serializes the module into
window.__IMPECCABLE_LIVE_UI_SURFACES__ in the prelude it already writes
for the token, port and vocabulary, and live-browser.js reads the global.
assembleLiveBrowserScript defaults the value from the module rather than
taking it from live-server.mjs, so the bundle carries the canonical
inventory by construction instead of by a caller remembering to pass it.
The emitted inventory is byte-identical to the inlined one.
tests/live-ui-surfaces.test.mjs pins both halves of the seam: the module
is the definition (live-browser.js must not redeclare it), the prefix the
module builds ids from matches the PREFIX live-browser.js hardcodes, and
the assembled bundle still carries the list. live-server.test.mjs gets
the matching integration check against a served /live.js.
One existing assertion changed. live-browser-regression.test.mjs checked
that the steer Send control is registered as live chrome by matching the
text of the inline literal's last line. That encoded where the list was
written, not what it contains; it now asserts membership in the imported
LIVE_UI_COMPONENT_IDS, which is the behaviour it was after.
Verified with the full default suite plus a live-e2e fixture run
(vite8-react-modal), so the overlay is exercised end to end in a browser.
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Centralize CSS numeric token parsing and characterize every supported color unit while preserving config and filtering behavior.
AI-assisted: Codex implemented this refactor under pbakaus’s scheduled architecture-simplification authorization.
Make critique storage the single owner of snapshot discovery and frontmatter parsing, and keep context signals focused on summarizing the canonical result.
AI-assisted: Prepared by Codex under pbakaus's scheduled architecture-refactor authorization.
Emit Codex-compatible top-level keys while preserving the argument hint under metadata. Keep existing Claude-style pin frontmatter unchanged for other harnesses.
AI assistance: Codex implemented and validated this change under maintainer pbakaus's standing authorization.
* Fix Blade directory detection
AI assistance: Codex reproduced the issue, implemented the fix, and ran the validation described in the pull request.
* Fix compound scan suffix matching
AI assistance: Codex addressed review findings and ran the validation described in the pull request.
A 3-star held two tickets and a 1-star held none. On a pool this size that is
not a nudge, it is the shape of the draw. Measured against the live catalog:
3-star worlds absorbed 57% of the graphic draw from 65 of 163 eligible worlds,
46% of atmosphere from 13 of 43, and 75% of interaction from 15 of 25. The
reviewer's report that the same worlds keep returning is exactly what a rating
multiplier does to a corpus whose thinnest tier holds 25 worlds.
Now a 3-star draws level with a 2-star, and a 1-star draws at half rather than
not at all. Excluding a marginal keep made rating do a job breadth already does
properly: breadth still removes a niche world from the pool entirely, which is
the honest way to say "too narrow to challenge an arbitrary build", while a
1-star records "unexceptional" and is still worth showing sometimes.
Effect on the same catalog: the 3-star share falls to 39% on graphic, 30% on
atmosphere and 60% on interaction. That last one is no longer a weighting
artefact, it is simply what the tier contains, since 15 of its 25 eligible
worlds are rated 3.
Compositions get the same treatment; the two ticket functions had the identical
shape and no reason to disagree. Both tests asserted the old policy directly
and now assert the new one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Gracefully handle the no-image decision page
Tested the new-work path without image generation and fixed what broke:
- A text-only card's back face (First viewport, The case) was unreachable:
the Details flip chip only rendered inside the media block. Cards with no
imagery now render their full read on the front and skip the back face.
- A hero/board that fails to load (retired catalog URL, offline shell) sat
as a dark void with a zoom cursor. The slot now collapses to a field
painted from the card's own palette with an "artwork unavailable" pill;
broken inspiration PIPs remove themselves.
- Sketchless catalog art rendered unlabeled as the card's face, reading as
the promise of the build. It now carries the same "inspiration" label and
hover title the PIP uses.
- The --schema example pointed at catalog URLs that 404 (missing family
prefix); updated to the real asset paths and noted the text-only front
behavior in the schema prose.
Extends e2e test (e) with the front-read and label assertions and adds
test (f) for the broken-image fallback.
AI-assisted (Claude Code).
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: address PR review bot findings
- cursor[bot]: the unavailable-art scrim painted over the flip chips and
swallowed their clicks; it now passes pointer events through and the
chips render above it.
- Copilot: a palette-less card whose art failed still read as a dark void
and kept the stale Inspiration tooltip; the slot now falls back to the
graphite field in CSS and the tooltip is removed with the art.
Test (f) now covers both: a broken card with back facts must still flip
via Details, and a palette-less broken card gets the labeled fallback.
AI-assisted (Claude Code).
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
* test: harden the test strategy (triggers, runner speed, release guards)
Follow-ups from an end-to-end testing strategy review:
- Suite triggers are now auto-generated from each suite's own file list,
so change-based CI can never miss a test file again (four files were
unreachable by their own edits, and tests/lib/detector-bundle.test.js
triggered core while running in detector). Two new meta-tests pin the
invariant. Hand-written trigger patterns now carry only source paths
and fixture dirs; palette dropped from the live triggers since no
suite tests it.
- The node runner batches all files into one node --test invocation at
concurrency 4 instead of spawning per file. Default suite drops from
~159s to ~100s; the live suite soaked clean three times.
- scripts/release.mjs gets its first tests: 12 scenarios spawning the
real script inside a disposable git repo with a local bare origin,
covering every refusal guard plus notes/tweet rendering, all under
--dry-run.
- skill/scripts/live/ui-core.mjs deleted: zero references repo-wide,
superseded by the July live rewrite, yet still shipping to users.
cli/lib/download-providers.js annotated with its cross-repo consumers
(impeccable-site Pages Functions) so it is not mistaken for dead code.
- CLAUDE.md gains an area-to-suite table for the opt-in suites a change
owes; AGENTS.md syncs the plugin-e2e commands and obligations.
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: exclude peeled tag lines from release-test origin cleanup
Copilot: git ls-remote --tags emits ^{} peel lines for annotated tags,
which are not deletable refs; --refs filters them so the cleanup loop
survives a future scenario that pushes an annotated tag.
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
Volta's Windows shims exec through `cmd /C`, which re-parses the argument
list, so the `>=` inside the probe's `node -e` payload was read as output
redirection. The command died with "The filename, directory name, or volume
label syntax is incorrect" before node started, the guard read that as a
missing runtime, and the hook it exists to protect was disabled on every
PostToolUse and Stop. A user on a supported Node 24 got a one-time notice
telling them to install Node 22, then silence.
Clamping with Math.min is the same floor test in the same ES5-only syntax,
with no character cmd.exe can claim. Verified through the Volta shim on Node
24.16.0 and 22.18.0 (exit 0) and against a real Node 20.6.1 binary (exit 1),
so the floor is unchanged. Adds a regression test asserting no `<`, `>`, or
newline reaches any generated `node -e` payload.
Upstream cause: volta-cli/volta#1791.
Prepared with AI assistance (Claude Code).
* Fix: unescape YAML quote escapes in DESIGN.md frontmatter scalars (#428)
parseScalar() stripped a double-quoted scalar's outer quotes without
processing the backslash escapes inside, so a font stack that quotes a
multi-word family the CSS way, e.g.
fontFamily: "\"IBM Plex Sans\", system-ui, sans-serif"
reached allowedFonts as '\"ibm plex sans' and design-system-font flagged
fonts DESIGN.md declares. Also collapses the doubled-quote escape in
single-quoted scalars and keeps a lone quote literal instead of slicing
it to an empty string. Applied to both copies of the parser
(cli/engine/design-system.mjs and skill/scripts/lib/design-parser.mjs).
Co-authored-by: Cursor Agent (AI-assisted change, reviewed and directed
by a maintainer)
* Decode YAML hex and Unicode escapes in double-quoted scalars
Review follow-up: the escape scanner only handled the simple set, so
\xNN, \uNNNN, and \UNNNNNNNN sequences stayed encoded and an escaped
token like "\x23b8422e" never matched #b8422e in CSS. Decode validated
hex escapes in both parser copies; malformed or out-of-range sequences
stay literal. Regression coverage for all three forms.
Co-authored-by: Cursor Agent (AI-assisted change, reviewed and directed
by a maintainer)
* Complete the YAML 1.2 double-quote escape set
Review follow-up: the escape map omitted the escaped space (\ ) and
non-breaking space (\_) forms, so fonts declared with them kept a
literal backslash in allowedFonts and their CSS declarations were
reported as undeclared. Map the full spec 5.7 set (\a \b \v \f \e
\N \L \P included) in both parser copies instead of chasing one escape
at a time. Regression coverage for both named forms.
Co-authored-by: Cursor Agent (AI-assisted change, reviewed and directed
by a maintainer)
* Fix broken-image comment false positives
AI assistance was used to reproduce the issue, implement the fix, and add regression coverage.
* Harden JavaScript comment scanning
AI assistance was used to address automated review feedback, add regression coverage, and run validation.
* Handle comments in template expressions
AI assistance was used to reproduce and fix automated review feedback, add regression coverage, and run validation.
* Preserve JSX around URL and regex syntax
AI assistance was used to reproduce and fix automated review feedback, add regression coverage, and run validation.
* Fix regex keyword property context
AI assistance: Codex identified, implemented, and validated this review follow-up under maintainer authorization.
* Handle JSX slash edge cases
AI assistance: Codex addressed review findings and validated this follow-up under maintainer authorization.
* Ignore CSS-in-JS comments
AI assistance: Codex addressed top-level review findings and validated this follow-up under maintainer authorization.
* Handle remaining slash contexts
Fix JavaScript keyword separation and JSX protocol-relative URL classification so comment stripping preserves only live source. Add focused regressions for the reviewed edge cases.\n\nAI assistance: Codex implemented and validated this change under maintainer authorization.
* Handle generic styled templates
Recognize TypeScript generic arguments consistently in CSS-in-JS extraction and comment sanitization. Add focused regressions for extraction and comment-only styled templates.\n\nAI assistance: Codex implemented and validated this change under maintainer authorization.
* Handle nested styled generics
Teach CSS-in-JS extraction and comment sanitization to scan balanced nested TypeScript generic arguments before template literals. Add regressions for live and commented nested-generic styles.\n\nAI assistance disclosure: Codex implemented and validated this review follow-up under maintainer authorization.
* Handle nested source contexts
Keep regex detection correct after postfix operators, distinguish JSX expression comments from protocol-relative text, and scan nested template literals inside CSS-in-JS interpolations. Add focused regressions for each review finding.\n\nAI assistance disclosure: Codex implemented and validated these review follow-ups under maintainer authorization.
* Complete comment-safe source scanning
Recognize regex literals after for-of, comparisons, and block braces without confusing object-literal division. Route grid-background detection through the offset-preserving comment-neutralized source and add negative and positive controls.\n\nAI assistance disclosure: Codex implemented and validated these review follow-ups under maintainer authorization.
* Handle remaining lexer contexts
Recognize JSX attribute expressions and regex literals inside CSS-in-JS interpolations so comment stripping remains source-safe.\n\nAI-assisted: Codex implemented and validated this change under maintainer authorization.
* Align interpolation regex contexts
Match postfix-update and statement-block regex classification in CSS-in-JS interpolation parsing so templates remain extractable.\n\nAI-assisted: Codex implemented and validated this change under maintainer authorization.
* test: guard the plugin loader contract that PR #494 exposed
The agents manifest key shipped for months and silently loaded zero of
the four subagents; no validator looked at the generated plugin
manifest's shape and claude plugin validate never checks it. Three
layers now do:
- scripts/lib/validate-plugin-manifest.js pins the verified loader
contract (KNOWN_LOADER_KEYS allowlist, no agents key, trailing-slash
skills path from issue #86, every skill/agents/*.md shipped in
plugin/agents/), unit-tested in tests/validate-plugin-manifest.test.js
including a check of the real committed subtree.
- The same check gates bun run build next to the version-drift guard.
- tests/plugin-e2e.test.mjs installs the committed ./plugin subtree into
a real Claude Code (sandboxed via CLAUDE_CONFIG_DIR in a temp dir) and
asserts the component inventory: skill parses, all agents visible,
hooks discovered. In the default suite; runs in about a second and
skips cleanly when the claude CLI is absent, so CI is unaffected.
All three failed against the pre-#494 tree for the shipped reason
(Agents 0 of 4) and pass against current main.
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: address PR review bot findings
- Copilot: guard collectPluginManifestFindings against valid JSON that is
not an object (null, string, number, array) so a broken manifest is a
finding instead of a build crash; unit test added
- Copilot: update the plugin-e2e header comment, the suite is in the
default lineup rather than opt-in
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: harden plugin E2E sandbox isolation
Bugbot: create the sandbox CLAUDE_CONFIG_DIR up front and redirect HOME
and USERPROFILE into the temp workDir too, so a CLI code path that
derives config or cache locations from the home directory instead of
CLAUDE_CONFIG_DIR still cannot touch the developer's real Claude config
when the default suite runs.
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: agent parity check mirrors the build's emit rules
Bugbot: the shipped filename is `${claude-name || name}.md` and a
providers: list may exclude claude-code, so comparing raw source
basenames could fail the build on a renamed or provider-scoped agent
with a build:release hint that cannot fix it. The validator now derives
expected filenames the same way the transformer factory does (shared
parseFrontmatter, same providers gate) with unit coverage for renames,
name overrides, and provider-scoped agents.
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: run the plugin E2E through a shell on Windows
Bugbot: the claude CLI is a .cmd shim on Windows and Node refuses to
spawn those via execFile without a shell, so the availability probe
always failed and the suite silently skipped there. Windows now invokes
through a shell with every argument double-quoted (temp paths routinely
contain spaces); the POSIX path is unchanged.
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
Keep unmanaged detector fields when ignore-file updates the canonical detector configuration. Add a regression covering existing extension mappings.\n\nAI assistance: Codex implemented and validated this change under maintainer authorization.
Replace three duplicate matcher loops with one declarative pattern list while preserving import resolution behavior. Add Sass @use and @forward characterization coverage.\n\nAI-assisted change prepared under pbakaus's scheduled architecture-refactor authorization.
hook-before-edit.mjs kept its own string-based isInsideProject; it now
uses the shared isScanTargetInsideProject so all three hook passes
apply one containment semantic, symlink canonicalization included.
Because the before-edit hook gates proposed Writes whose target does
not exist yet, canonicalPath now resolves the nearest existing
ancestor and re-appends the remainder instead of falling back to the
raw resolved path — a new file under a symlinked root compares equal
to its canonical project.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Code <noreply@anthropic.com>
The per-edit and Stop deep passes gated on sensitive paths, generated
paths, extension, config ignores, and size, but never on containment.
Any file the session touched outside the project (harness scratchpad
dirs under the system temp root, sibling checkouts) was scanned and
judged against THIS project's config and DESIGN.md palette, producing
design-system findings that are wrong by construction.
Both loops now check isScanTargetInsideProject() (audit reason:
outside-project), matching the gate hook-before-edit.mjs already had.
Paths are canonicalized so a symlinked root doesn't split the
comparison. The Stop pass re-checks containment itself because caches
written by older hook versions can still list out-of-project paths.
Umbrella-dir launches (issue #305) are unaffected: their projectCwd
resolves to the edited file's own project root, so containment holds.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Review follow-up: the regex stopped at the literal ?token= and tolerated
anything after it, so removing the encoded token value from the URL
still passed. Requiring encodeURIComponent(TOKEN) right after the
prefix makes the mutation fail.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-up: the reflected-origin contract matters most on the
OPTIONS preflight, where a cached response authorized for one origin
must never be served to another.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the fix for the ddev breakage reported in #304: the live server
now reflects Access-Control-Allow-Origin for any request bearing the
valid session token, so dev servers on loopback aliases (https://*.ddev.site,
Valet's *.test, hosts-file entries) work again while tokenless remote
origins stay blocked. The server/browser source changes shipped in
b1c5707f; this adds the test coverage that was written alongside them:
- tokenless remote origins get no ACAO on any route, token'd or not
- a non-loopback origin with the valid token is reflected, with
Vary: Origin, on both the real request and its OPTIONS preflight
- the /manual-edit-stash source assertion tracks the token-bearing URL
Prepared with AI assistance.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Recognize both slash- and dollar-prefixed prescribed seed markers and exercise each variant in coverage tests.
AI assistance: Codex addressed Cursor and Copilot review feedback and reran validation under maintainer authorization.
Treat Components as optional only when DESIGN.md carries the prescribed seed marker, while retaining Colors and Typography checks.
AI assistance: Codex reproduced the issue, implemented the fix, and added regression coverage under maintainer authorization.
Join indented Markdown bullet continuations and keep them out of Overview philosophy text.
AI assistance: Codex reproduced the issue, implemented the fix, and added regression coverage under maintainer authorization.