The hook_event_name mapping (and its pre_tool_use entry) had no reader:
hook.mjs routes on the raw stdin via isStopEvent, and nothing downstream
keys on the normalized event name. The trailing-slash strip duplicated
the path.resolve every consumer already does. Pin the one commit-2
behavior that had no test: a Stop detector failure must leave the
remembered set alone.
Prepared with AI assistance.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A clean Stop never replaced remembered keys, so a finding that was fixed and then reintroduced stayed silent. Remember the live scan, including empty, and persist that write.
Prepared with AI assistance.
Co-authored-by: Cursor <cursoragent@cursor.com>
Grok was classified as GitHub Copilot, so the design hook skipped every
edit with no-file-path and never ran Stop. Normalize toolInput/sessionId
and treat Stop additionalContext as the Grok product.
Prepared with AI assistance.
Co-authored-by: Cursor <cursoragent@cursor.com>
Grok was skipped by the hook-command rewrite, so a global skill install left .grok/hooks/impeccable.json pointing at a project-relative hook.mjs that does not exist.
AI assistance: Cursor Grok 4.6 implemented this change.
Co-authored-by: Cursor <cursoragent@cursor.com>
Centralize the shared boot artifact checks so doctor adds only its deep checks while preserving the existing finding order and CLI contracts.
AI-assisted: prepared by Codex under maintainer pbakaus scheduled-refactor authorization.
Quote-bearing regexes made the frontmatter closer miss the closing ---, and url(//…) plus interpolations were treated as SCSS line comments that hid live font-family. Prepared with AI assistance.
Co-authored-by: Cursor <cursoragent@cursor.com>
Standalone SCSS/Sass/Less files already ignored // comments, but <style lang="scss"> in Astro/Vue/Svelte still scanned them as live CSS. Prepared with AI assistance.
Co-authored-by: Cursor <cursoragent@cursor.com>
The unit suite already covered dashed rules; this adds an isolated HTML fixture so the page-level one-finding cap cannot hide a regression.
Prepared with AI assistance (Cursor agent), directed by @abdulwahabone.
Co-authored-by: Cursor <cursoragent@cursor.com>
jsdom does no layout; Greptile asked for width/height on .mix-hex-brand so the static fixture stays deterministic. Prepared with AI assistance.
Co-authored-by: Cursor <cursoragent@cursor.com>
codex-grid-background treated any 2D px background-size as a grid, so a single hairline tiled as a dash or rail false-positived. A finding now requires two hairline gradients plus a px tile.
Prepared with AI assistance (Cursor agent), directed by @abdulwahabone.
Co-authored-by: Cursor <cursoragent@cursor.com>
Naive HTML/CSS comment regexes were swallowing live markup between script-string delimiters, SCSS/Sass/Less line comments still reached the matchers, and indexOf treated --- inside a frontmatter template literal as the closing fence. Prepared with AI assistance.
Co-authored-by: Cursor <cursoragent@cursor.com>
parseGradientColors treated #000 inside color-mix() as a stop, so low-contrast scored text against phantom black. Prepared with AI assistance.
Co-authored-by: Cursor <cursoragent@cursor.com>
detectText only blanked comments for JS extensions, so broken-image still fired on <img> inside Astro/Vue/Svelte comments, CSS comments, and extracted style blocks. Prepared with AI assistance.
Co-authored-by: Cursor <cursoragent@cursor.com>
The neon-text path is browser-only, so the matrix lives in the Puppeteer suite rather than the static fixture runner.
AI-assisted (Cursor agent).
Co-authored-by: Cursor <cursoragent@cursor.com>
A page-supplied _instructions suppressed the locally generated next step and was presented as authoritative over live.md. Drop reserved poller-owned fields at ingest and always overwrite them locally.
AI assistance: implemented with Cursor Grok 4.6.
Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve pending Tune controls when the completed variant set contains no tunable parameters, while preserving deferred parameter publications.
AI assistance: implemented and validated by OpenAI Codex under maintainer authorization.
Keep the committed marketplace repair script aligned with Claude Code's supported Edit and Write tools, and strengthen regression coverage after automated review.\n\nThis change was prepared with AI assistance under maintainer authorization.
Claude Code now folds multi-edit behavior into Edit, so keep generated and repaired hook manifests aligned with the current Edit and Write tools. Grok keeps its compatibility matcher unchanged.
AI assistance was used to implement and validate this change.
Centralize provider fixture, hook, and agent-launch contracts while preserving provider-specific verification behavior. Reuse the shared CLI argument parser and characterize the public usage contract.
AI-assisted: prepared by Codex under pbakaus's scheduled architecture-refactor authorization.
Normalize hook command separators before matching Impeccable-owned entries so updates replace legacy Windows guards instead of duplicating them.\n\nAI assistance: Codex implemented and validated this change under maintainer authorization.
* Fix: stop the direction page hanging forever after a re-roll (#469)
The re-roll leg of the decision-page protocol was documented only in
serve-question.mjs's own header, so agents never ran --update and the
open tab polled a round that could never arrive. Compounding failure
modes: the page poll swallowed every error, the daemon's --timeout was
an absolute guillotine that killed the server under a still-open tab,
a choice posted to a dead server confirmed nothing, and refresh or
Reload on an unresolved round resurrected heartbeats that held the
daemon alive indefinitely.
- new-work.md documents the re-roll leg: rerun concept-seed with
--from/--reroll, deliver with --update on the same key, never --start
a second server.
- The page poll terminates and says why: eight consecutive fetch
failures means the server is gone; the delivery deadline (the
server's own --idle-grace, inlined into the page) passing means the
hand never arrived. Both stop heartbeating.
- The daemon's --timeout bounds only the wait for a page to open; once
the page heartbeats, the server lives while the page does and exits
after --idle-grace (default 600s) without a beat, including under
--timeout 0.
- Build this and Re-roll against a dead server fail loudly instead of
silently swallowing the click.
- The server tracks the window between a collected re-roll answer and
the --update that replaces the round, and serves the page in waiting
mode there, so a native refresh re-enters the same bounded wait
instead of resurrecting dead cards; the in-page Reload button only
revives a delivered hand.
- --update is exempt from the headless gate and its liveness probe
trusts a fresh heartbeat over a failed kill probe (sandbox EPERM is
not death).
Squash of the six review-round commits on this branch, rebased onto
main after the decision-page revamp.
AI assistance: prepared with an AI agent operating under maintainer
instruction (abdulwahabone).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review findings: persist the replacement deadline, refuse unloadable hands
A browser-native refresh of the waiting page re-entered the bounded wait
with a fresh delivery deadline and an immediate heartbeat, so refreshing
before each deadline expired could hold the daemon alive and keep --wait
on WAITING indefinitely. The server now records when the re-roll or
followup answer was collected, each served waiting page inherits only
what remains of that one allowance, and a page served after the deadline
renders stalled immediately and never starts its heartbeat.
And a next hand the round could not load used to reload-loop the tab:
GET /'s catch kept the file on disk, so /next-status stayed ready:true
forever. --update now refuses a payload without a non-empty options
array at the sender, and GET / discards an unloadable next file so the
bounded wait resumes.
AI-assisted (Cursor agent) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review finding: a stalled page recovers a late hand without a click
The stall silenced heartbeats so the idle grace could reclaim the
daemon, but that silence read as a closed tab: after a late --update,
--wait saw the stale beat and reported PAGE CLOSED while the user sat
on the Reload screen, so the agent abandoned the browser path the
recovery UI exists for. The stall screen now keeps a beat-free
/next-status watch that reloads into a delivered hand on its own
(GET never beats, so an abandoned flow is still reclaimed), and --wait
no longer concludes closure from a stale beat while an undelivered
next hand sits on disk.
AI-assisted (Cursor agent) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review finding: a delivered hand must not mask a closed page
The mid-delivery suppression keyed on the next file existing, but a
closed tab never claims that file, so an unconsumed delivery held
--wait on WAITING indefinitely instead of reporting the closed flow.
The suppression is now age-bound: a stalled page's watch reclaims a
delivered hand within seconds, so a file still unclaimed after a 10s
grace means no page is coming back and the stale beat reads as the
closed page it is.
AI-assisted (Cursor agent) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review finding: stamp the delivery clock at --update, not the copy
--wait's mid-delivery grace reads the next file's mtime, but
copyFileSync's timestamp behavior is the platform's business: a copy
that preserves the source payload's older mtime would start the grace
already spent and report PAGE CLOSED under a live stalled tab. --update
now touches the delivered file itself, so delivery time is delivery
time everywhere.
AI-assisted (Cursor agent) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review findings: disable canon during the wait, validate --timeout
The waiting and stall screens disabled only the re-roll buttons; the
footer canon action stayed clickable, and a canon pick posted after
--wait had consumed the re-roll could never be collected: it overwrote
the answer, marked the table closed, and exited the daemon under the
agent. Both disable sites now take the canon exit down with the re-roll
buttons; a delivered hand reloads the page and serves it live again.
And --timeout reached the lifetime timer unvalidated: NaN or a negative
value disarmed the no-page exit and the daemon leaked. It now takes the
default unless the value is a finite non-negative number, keeping 0 as
the explicit wait-forever.
AI-assisted (Cursor agent) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review finding: a second click must not renew the delivery deadline
dealAgain left the re-roll and canon controls live through the answer
POST and the 700ms fly-out, so a second click posted another re-roll
and the server restamped awaitingNextSince, renewing the deadline this
PR made non-renewable on refresh and on the stall screen. The controls
now go quiet at the click itself, in dealAgain and in answer(), and the
server stamps the allowance only on the transition into the wait, so a
duplicate answer racing the disable keeps the first stamp.
Regression coverage on both sides: the unit deadline test posts a
duplicate re-roll mid-allowance and asserts the budget shrank instead
of resetting, and the e2e stall test asserts both controls are disabled
immediately after the click, before the fly-out.
AI-assisted (Cursor agent) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review finding: a late delivery must survive its claim window
--update could land a replacement hand after the stalled page went
silent but moments before the daemon's idle deadline: the daemon exited
before the page's 1.5s watch could claim the hand, orphaning a delivery
--update had confirmed, and the next --wait reported a server failure.
The idle exit now defers while an unclaimed next hand is younger than
the claim grace --wait already reads (extracted as one shared
constant), so the page's watch deals it and heartbeats resume; a file
unclaimed past the grace still ends the daemon, bounded as before.
Regression test: deliver at idle-deadline-minus-a-beat, assert the
daemon survives past the deadline and serves the late hand.
AI-assisted (Cursor agent) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review finding: the claim itself must hold the daemon
The idle-exit hold read only the next file's freshness, but GET /
deletes that file when it serves the claimed round, before the
reloading page can post its first heartbeat: a lifetime tick in that
gap saw no pending hand and a stale beat, and exited under the hand
just claimed. GET / now stamps the claim when it consumes a pending
hand, and the idle exit honors the same bounded grace from that stamp,
so the reloading page gets its seconds to beat while an abandoned claim
still ends the daemon at the grace.
The claim-window regression test now also fetches after the claim, past
another lifetime tick, and asserts the daemon survived the gap;
verified it fails on the previous commit.
AI-assisted (Cursor agent) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review finding: --wait must ride out the claim gap too
The claim deletes the next file --wait's mid-delivery grace watches,
and the reloading page has not beat yet, so --wait in that gap read the
stale beat as PAGE CLOSED while the daemon was alive serving the dealt
round, and the agent abandoned a browser session that had just
recovered. GET / now persists the claim stamp into the per-key state
file, and --wait's suppression honors it under the same bounded grace:
a fresh claim stays WAITING, a claim nobody followed with a beat still
reads as the closed page it is.
Regression test drives --wait through the gap (claim with a stale beat:
WAITING, not exit 4) and past it (backdated claim stamp: exit 4);
verified it fails on the previous commit.
AI-assisted (Cursor agent) under maintainer instruction.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
Consolidate explicit and inferred surface route canonicalization behind one private rule, with characterization coverage for equivalent and invalid inputs.\n\nAI assistance: OpenAI Codex prepared this change under pbakaus's scheduled architecture-simplification authorization.
* Bump skill-behavior google lineup to gemini-3.7-flash
gemini-3.7-flash replaces gemini-3.6-flash in DEFAULT_MODELS. The
README notes that the recorded gemini baseline cells were measured on
3.6-flash (or 3.5-flash where marked) and count as unmeasured on 3.7
per the suite's own cross-version rule, to be re-run on the next Setup
or routing change.
AI-assisted change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
StaticElement.closest() handed the raw selector string to css-select's
is() on every ancestor step, recompiling the same selector N times for
an element N levels deep. StaticDocument now caches one compiled
matcher per selector (failed compiles cached as rethrowers so bad
selectors still return null). Findings are byte-identical across the
fixture corpus; scan time drops to ~62% on the fixtures and ~7x faster
on deep-DOM pages.
Prepared with AI assistance (Cursor agent), directed by @abdulwahabone.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify local detector dispatch
Centralize HTML-versus-text file routing for stdin, directory, and direct-file scans. Add CLI characterization coverage for both stdin paths.
Prepared with AI assistance under maintainer pbakaus's standing scheduled-refactor authorization.
* Strengthen detector dispatch characterization
Put the HTML-only finding in a linked stylesheet so the text engine cannot satisfy the static-engine assertion.
Prepared with AI assistance under maintainer pbakaus's standing scheduled-refactor authorization.
Three review findings on #582, all real and all mine.
Delegating `.pip` and `.media` to `document` separately meant they could not
stop each other: stopPropagation ends bubbling, not siblings on the same
target. Clicking the corner inspiration opened the inspiration and then the
media handler replaced it with the comp, so the corner was unusable on exactly
the cards this PR set out to fix. All three targets now resolve in one
delegated listener in priority order, corner before chip before slot, and a
chip that is not expand keeps its own click instead of falling through.
Flip-back restored the face without clearing what the pending state had added,
so a slot that reached stand-in came back carrying "comp pending" beside a
fresh label, and one whose art had failed came back still marked unavailable.
Restore now clears both, and a slot with no art to restore returns to the
honest "artwork unavailable" treatment rather than being labeled inspiration.
Converting in place means the same node is reused across flip cycles, and the
old poll closure outlived its cycle: a probe from the first flip could settle
the second one, stripping the new shimmer and stopping the live poll while the
comp stayed hidden. Each run now carries a generation stamp that flip-back
bumps, and both probe callbacks bail when it moves.
The test covers the corner click against the landed comp, and I confirmed it
fails when the priority ordering is removed.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings on #581, both fair.
The scratch app symlinked the whole of the repo's node_modules, so the
scaffolder's output directory, `node_modules/.impeccable-live`, resolved to the
REPO's copy. Variants were written there and survived `afterEach`, which only
removed the temp dir; the next case reused the session id, and the scaffolder
keeps existing variant files, so a case could parse a previous case's source
against a fresh manifest. Now only `svelte` is linked, into a node_modules the
workspace owns, and each case gets its own session id. Svelte's own
dependencies still resolve, because node follows the link to its real path
before looking for them.
The comment also pointed at a `PROPS_SCRIPT_SHAPES` symbol that does not exist
in the test file. Dropped the name and kept the file reference.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two defects in the same few lines, both from `enterComp` hand-building a media
slot after the deal instead of reaching the shape a comp-first render serves.
A code-led card carrying catalog art shows that art as its face. Flipping to
comp inserted a fresh shimmer slot above the body and left the face alone, so
the card rendered the inspiration full-bleed with the rendering comp stacked
under it: two images of equal weight, which is the one thing the corner
treatment exists to prevent. The flip now converts that slot in place, moving
the art into the `figure.pip` and dropping the face label, and flipping back
restores it, so a round-trip leaves the card as it was dealt.
The slot it built also carried no chips, and the zoom handlers were bound per
element at load, so a comp that streamed in after a flip could not be opened at
all: no expand affordance, and no click handler on the art. The three lightbox
handlers are now delegated, which is what makes any later-built slot work, and
a converted slot keeps the chips it already had. Polling learned to stop on a
slot that stays in the DOM but loses its pending state, which only happens now
that a flip back can restore rather than remove.
The existing toggle test covered a wireframe card, where the schematic is
hidden and a fresh slot inserted; that branch was fine, which is why this went
unseen. The new test drives the art-carrying card and fails on the stacking
assertion without this change.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Live mode scaffolds each Svelte variant with a props script that annotated the
declaration:
/** @type {{ title: string; }} */
let { title } = $props();
A JSDoc `@type` written directly before a value is also JSDoc's cast syntax,
and esrap 2.3.3, the printer Svelte emits JS through, moves that annotation
onto the template's own declaration:
var /** @type {{ title: string; }} */ (h1) = root();
`var (h1) = ...` does not parse. The .svelte source is valid, the compile
succeeds, and the failure lands in the browser's dynamic import as "Unexpected
token '('": the variant never mounts and the session shows nothing. `@typedef`
carries the same shape without being a cast, so both builders emit that.
This is not test-only. Every Svelte variant we generate carried the construct,
so live mode was broken for any user whose install resolved esrap 2.3.3.
Svelte declares `esrap: ^2.2.12`, so a fresh install takes it; this repo's
lockfile pins 2.3.0, which is why unit tests stayed green while the fixture,
which installs into a temp dir, did not.
Two reasons the existing pre-publish guard could not have caught it, now
recorded next to it:
- `compileCheckVariants` compiles with `generate: false`, so there is no
emitted JS to inspect.
- `loadSvelteCompiler` resolves the compiler through createRequire, which
Svelte's export map routes to a prebuilt CJS build. A dev server imports
`src/compiler`, and only that path runs the app's installed printer. The
guard was checking a different compiler than the browser runs.
The new suite therefore imports the compiler as ESM and asserts the emitted
JavaScript parses, rather than pinning the comment style: a future printer that
mangles some other construct fails it too. The first draft used createRequire
and reported green against the exact input that breaks in a browser, which is
the mistake worth not repeating.
Verified against svelte 5.56.9 with esrap 2.3.3. Full live-e2e sweep green,
26 fixtures.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round two of review findings, all four valid.
`doctor` builds its own finding list and never called `checkBuildPathUnset`,
so `config-build-path-unset` could not appear in the report even though
doctor.md documents it. That is also the only path left once stalenessCheck is
off, which is exactly when someone is looking for it.
The lookup chain included `process.cwd()`, which lets an ambient invoking
directory decide another project's workflow: run from workspace A with
--target resolving onto workspace B, and B inherited A's buildPath ahead of
the repository default. The chain is now the resolved project then the repo
root, matching `checkBuildPathUnset` exactly; cwd stands in only when no
project resolved at all.
Two prose contradictions, both mine. new-work said to write the value "when
the user says yes" and then to "record the answer either way", which reads as
persist-on-yes-only and leaves the decline to be asked again next session. It
now says the write always happens and the answer picks the value. The README
still pointed existing projects at re-running init, which is the problem this
PR exists to solve; it now names the toggle as the migration path.
The workspace-isolation test earned a correction of its own: the first version
passed a relative --target, which resolves against the caller's cwd and puts
projectRoot back on the calling workspace, so it asserted nothing.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two findings from Greptile on #579, both about the same key seen from
different roots.
`appendBuildPathDirective` searched projectRoot and cwd but never repoRoot,
while `checkBuildPathUnset` reads both. In a monorepo that committed the
preference once at the root, the two disagreed in the worst direction: the
staleness finding stayed silent because a value existed, and the directive
never named it, so nothing on screen explained why the recorded default was
not being honored. Roots are now ordered nearest first, workspace over repo
root, with regression tests for both the fallback and the override.
The ANSWER line for a flipped path said "never write it to settings". The
page indeed never writes it, but the sentence read as a rule and applied
itself to new-work's one-time offer, which exists for exactly the case a flip
creates: a project with no recorded default, asked once after the round
closes. It now states what the page does and names the exception.
The same report's first issue also named context.mjs, and that part does not
hold: its directive is emitted only when a value is already recorded, which is
precisely when session-only is the correct instruction. Left as is.
Written with AI assistance (Claude Code).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>