Conversation
Records stdout/stderr/exit/files for every impeccable verb over a fixed corpus and replays them against an alternate implementation. Adds a loader hook that captures per-function call vectors from the pure engine modules. Prepared with AI assistance (Claude Code).
Prepared with AI assistance (Claude Code).
Prepared with AI assistance (Claude Code).
…ls/csp/seed/genimg/question cases and goldens Prepared with AI assistance (Claude Code).
…ept, session, manual edits, daemon) Prepared with AI assistance (Claude Code).
…binary Prepared with AI assistance (Claude Code).
process.exit() right after a large piped stdout write truncated JSON output at the pipe buffer boundary; found by the oracle harness. Re-record the six directory-scan goldens that had captured the truncation. Prepared with AI assistance (Claude Code).
…audit chars Prepared with AI assistance (Claude Code).
Every `node {{scripts_path}}/<name>.mjs` becomes `{{scripts_path}}/impeccable <verb>`
(context-signals -> signals, hook-admin -> hooks). Setup step 1 drops Node, points
Windows shells without sh at impeccable.cmd, and says the launcher runs a
self-contained binary. allowed-tools follows.
Prepared with AI assistance (Claude Code).
skill/scripts keeps command-metadata.json and the page JS; every .mjs entry point, lib/, and live/ are gone (the binary owns those verbs). Adds the POSIX launcher, impeccable.cmd, VERSION (copied from the new root ENGINE_VERSION), scripts/fetch-engine.mjs (bun run fetch:engine) to pull the pinned binary into skill/scripts/bin/<os>-<arch>/, and gitignores that bin dir. Prepared with AI assistance (Claude Code).
readSourceFiles no longer copies cli/engine into the skill; the scripts
payload is the launcher (executable bit preserved through dist, plugin/, and
universal.zip), impeccable.cmd, VERSION (synced from ENGINE_VERSION on every
build), the page JS, and command-metadata.json. Hook manifests call
`<scripts>/impeccable hook` behind an existence guard (Codex adds a
commandWindows sibling calling impeccable.cmd; Cursor runs hook-before-edit;
GitHub keeps the git rev-parse form; Grok mirrors Claude); the Node probe and
systemMessage notice are gone. build:release fetches the pinned engine for
every target (lenient) and stages bin/<os-arch>/ into the dist skill copies
after root harness dirs and plugin/ were synced, so git-delivered trees stay
launcher-only. The detection-rule count check reads the vendored
extension/detector/antipatterns.json and is skipped when absent.
build:browser is a stub; the codex prefix rewrite leaves
`{{scripts_path}}/impeccable` alone.
Prepared with AI assistance (Claude Code).
cli/engine, cli/lib, and cli/bin/commands are gone; their behavior lives in the engine binary. cli/bin/cli.js now resolves the binary from IMPECCABLE_BIN, the @impeccable/cli-<os>-<arch> optional dependency (templates under cli/platform-packages/, published by the engine release), the ~/.impeccable/bin/<version>/ cache, or a checksum-verified download, and execs it. package.json drops the engine dependencies and the library exports; puppeteer moves to devDependencies for the icon scripts. README.npm.md describes the shim. Prepared with AI assistance (Claude Code).
Unit tests of the deleted Node scripts and the JS detector are removed; their behavior is pinned by tests/oracle goldens (frozen JS behavior plus reviewed deltas) and the engine's own tests. tests/oracle.test.mjs replays the corpus against the binary (IMPECCABLE_BIN or skill/scripts/bin/<target>/, via tests/lib/engine-bin.mjs) and skips cleanly without one; the framework fixture sweep drives live-inject, live-wrap, and detect-csp through the binary the same way. record.mjs learns --bin. The function-level vectors under tests/oracle/vectors/calls are committed as the frozen snapshot they can no longer be regenerated from. Suites: core trimmed to build and transformer tests, oracle added to the default run, detector/live reduced to packaging and reference checks, the live-e2e helper tests move to the opt-in live-e2e lane pending its retarget, cli-remote-e2e is an empty placeholder. Prepared with AI assistance (Claude Code).
CLAUDE.md gains an Engine binary section (launcher lookup order, ENGINE_VERSION, untracked binaries, how tests get one, the oracle as behavior gate, what stays JavaScript) and drops the Node-script and JS-detector descriptions; the CLI and detection-rule sections point at the shim and the engine repo. README.md states the skill needs no runtime and lists the launcher-based hook commands; AGENTS.md follows. CLI-CONTRACT.md's intro notes the scripts it quotes are the recorded source, not the tree. Prepared with AI assistance (Claude Code).
Prepared with AI assistance (Claude Code).
… them in DELTAS.md Prepared with AI assistance (Claude Code).
IMPECCABLE_BUNDLE_ENGINE=1 opts in to staging the engine binaries into the dist skill copies. Bundling every target into every provider copy put dist/universal.zip near 340 MB, past the 25 MB Cloudflare Pages file cap that impeccable install downloads through. Prepared with AI assistance (Claude Code).
The session, fake-agent loop, steer test, and manual-edit probe spawn <binary> <verb> (live-server, live, live-inject, live-wrap, live-insert, live-accept, live-poll, live-complete) resolved by tests/lib/engine-bin.mjs instead of node skill/scripts/live-*.mjs; the completion typing the agent imported from the deleted live/completion.mjs is a small local helper. The live-e2e helper unit tests move back into the default live suite (the steer loop skips without a binary). Prepared with AI assistance (Claude Code).
…ate-image verbs Prepared with AI assistance (Claude Code).
…nary The bash tool exports IMPECCABLE_BIN so the staged skill's launcher runs without a download; scenarios assert on 'impeccable context' instead of context.mjs and skip without a binary. Prepared with AI assistance (Claude Code).
…output sync Prepared with AI assistance (Claude Code).
…viewed delta Prepared with AI assistance (Claude Code).
…tree The rebase onto origin/main brought changes whose JS engine halves left the tree with the swap. This commit reconciles what survives: - Suite map: register main's comp-fidelity unit tests (build-phase, comp-diff, font-match, hero-checks) in the core suite and live-browser-ignores in the live suite. - Payload guard: the skill scripts payload now allowlists the comp-fidelity build pipeline (comp-spec/comp-diff/build-phase/font-match and their libs), the one Node toolchain that has not moved into the engine. - Drop skill/scripts/live/project-ignores.mjs, lib/live-path-globs.mjs, and their test: they import hook-lib/live-inject/impeccable-paths, which the swap deleted, and their consumer (the JS live server) is the engine now. - skill text: the comp pipeline's calls to engine verbs (generate-image, embed-prompt) use the launcher spelling. - Oracle: re-record 17 detect goldens over the fixture set main changed (oklch #592, color-mix #578, 1D grid #615, the two comp-fidelity rules) and record the gap in DELTAS.md; those JS rule changes are not yet ported to the engine, and the goldens pin its current behavior. bun run test (oracle included) and bun run build are green on this tree. AI-assisted change: implemented with Claude Code. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaJv2c4oN8wS7Ttq4XRqyx
…I: drop stale path, add oracle job Byte-identical copies of the engine repo's launchers (engine main af7572c): the retired 3.x npm CLI on PATH or in ~/.impeccable/bin is rejected by the engine-probe handshake instead of hijacking every verb; impeccable.cmd's download path is rewritten as straight-line goto flow (the parenthesized blocks expanded %url%/%cached% at parse time, making it dead code) with certutil sha256 verification and a windows-arm64 -> x64 asset fallback; the final error points at the release download instead of npm i -g (npm still serves the 3.x CLI). ci.yml: the generated-output check no longer diffs the deleted cli/engine/detect-antipatterns-browser.js, and a new oracle job fetches the pinned engine (bun run fetch:engine) and replays tests/oracle/ against it. The job is continue-on-error with a loud warning until the first engine release exists; flipping it to required is a release-time toggle, documented in the workflow. Verified here: sh -n on both launcher copies, bun run build green, full oracle replay against the rebuilt engine binary green (770 pass, 0 fail), and a launcher behavior test proving a fake 3.x CLI on PATH is skipped while the download + checksum chain completes against a local file server. Prepared with AI assistance (Claude Code).
…e ports The Aug 17-31 detector fixes (oklch parsing, color-mix nested hex, 1D grid pass, comment stripping, root-relative linked stylesheets, URL userinfo redaction, inert ignore-value refusal) and the comp-fidelity rules organic-clip-path / buried-raster are ported to the engine. Re-records the gap-pinning detect goldens from the fixed binary (glow.html included: its .photo-opaque-grad column now carries the buried-raster finding it was written for), replays the frozen checkHtmlPatterns call vectors through the last JS engine state in history (db1462b^; args untouched, 14 of 101 results moved), and rewrites the DELTAS gap section into the landed-ports note. Each re-recorded json fixture golden byte-matches that JS state's output; oracle: 770 pass, 0 fail. Prepared with AI assistance (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaJv2c4oN8wS7Ttq4XRqyx
New cases: hook-session-grok-edit-then-stop (Grok Build camelCase envelope, end_turn/shutdown/stopHookActive Stop handling, 35ae073 + bfe634e + 3c442af, #646), hook-session-codex-stop-decision (Codex Stop emits decision/block, c9e7cd8, #603), and doctor-order-boot-and-deep (boot and deep findings keep their established artifact order, 8099766). Re-recorded goldens whose old bytes froze pre-fix behavior, with a DELTAS.md entry naming each upstream hash: the Stop finding-cache sync (3c442af), the Edit|Write manifests without the retired MultiEdit matcher (7d5c60d), and the failWithRollback field order (1f2c3f9). Prepared with AI assistance (Claude Code).
The verb-fix section landed twice when two porting sessions staged the same file; keep one copy. Prepared with AI assistance (Claude Code).
Three hadmin-ignore-value-inert-* cases record the engine's port of be87f5e (#662) to hooks ignore-value: an exact value for a rule whose findings can never extract one is refused with the wildcard-plus-file route (and no config write), while the wildcard scoped form for the same rule is accepted. Goldens recorded from the engine binary and verified byte-for-byte against the ea36002 hook-admin.mjs on the same sequences. No existing golden changes, so no DELTAS entry is owed. Prepared with AI assistance (Claude Code).
Byte-identical sync of the engine repo's launchers: a freshly downloaded engine binary now runs only after verifying against its .sha256 sidecar. A sidecar that cannot be fetched, or a machine with no sha256 tool, refuses the download instead of exec'ing an unverified binary; the wget-only path fetches the sidecar too. Binaries already on PATH or in the cache that pass engine-probe are unaffected. Prepared with AI assistance (Claude Code). Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaJv2c4oN8wS7Ttq4XRqyx
The launcher, npm shim, and `impeccable install` all resolve the engine binary for the pinned ENGINE_VERSION, so a skill/CLI release or a rust-swap merge published ahead of the engine release + platform packages dead-ends every install path. Add a mechanical guard: - scripts/check-engine-release.mjs: verifies all five dist binaries + .sha256 and the five @impeccable/cli-<os>-<arch> npm platform packages exist for the pinned ENGINE_VERSION; names missing assets, exits non-zero. Honors IMPECCABLE_DOWNLOAD_BASE. - release.mjs: hard-fails release:skill and release:cli when assets are missing; extension is exempt (vendored WASM detector, no engine exec). - CI engine-release-ready job: runs the check, continue-on-error with a loud ::warning until the first engine release exists (flip to false then). - CLAUDE.md Releases: documents the enforced ordering. Prepared with AI assistance (Claude Code).
The two test temp roots kept `canonicalize`'s `\\?\` verbatim prefix, and the kernel takes a verbatim path literally, so every `/`-joined path built under them was an invalid filename. Strip it the way Node's `realpathSync` does. The manifest, artifact and sibling-binary expectations hard-coded POSIX separators for paths the product joins with the host's semantics; derive them from `jsp::join` instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
Same verbatim-prefix strip on the test temp roots, plus expectations derived from the helpers the product uses: cache keys and scan targets from `jsp::join`, the config path in an admin message from the same relative form `path.relative` renders, and the footer hints from `quote_command_arg`, which deliberately switches to the double-quoted Windows form (#476 / #533). The env lock no longer poisons the sibling tests when one of them fails. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
The goldens pin the `<REPO>`-masked fixture path recorded on POSIX. Mask, then render the remainder with `/` so a Windows checkout's backslashes are not read as a finding difference. The goldens are untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
Timing only. The watchdog polls in 50ms steps against a ~15.6ms Windows system timer while the crate's tests run in parallel, so the later request takes its turn later there. The bound stays far under the 60s read timeout a deadline-less read would hold the ticket for, so the test still distinguishes the fix from the regression. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
Windows does not unblock a `recv` already parked in the kernel when another thread calls `shutdown` on the same socket, so the watchdog could not end a silent connection's read and it held its turnstile place for the whole 60s header timeout instead of the 10s deadline. Bound the read at the socket too, which enforces the same deadline everywhere; the watchdog stays as the backstop for a connection that trickles bytes without ever completing a request. POSIX behavior is unchanged: the watchdog already closed the socket at the deadline. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
The test temp helper's `write` returned a `PathBuf::join` result, which keeps the `/` inside the relative part and so does not match what the hook resolves a relative target to on Windows. Three more admin messages and the cache-root slug pinned the POSIX spelling of paths the product renders with the host's semantics (`path.resolve` also prefixes the current drive there). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
…form `os.homedir()` reads USERPROFILE on Windows, so a fixture home that named only HOME sent the global installs into the runner's real profile. The Windows hook command carries the JSON-quoted path, so a host path's backslashes arrive escaped; derive the expectation instead of pinning the POSIX spelling. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
A finding's snippet carries the scanned file's own bytes, and the goldens were recorded from a POSIX checkout, so a CRLF checkout of a linked stylesheet reads as a finding difference. The goldens are untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
The Windows hook command carries the JSON-quoted launcher path, so the path's backslashes are escaped once inside the command and again by the manifest file itself. Read the manifest as JSON and look for either quoting form instead of counting escaping layers in a raw substring match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
This was referenced Sep 3, 2026
Restores tests/live-browser-source.test.mjs from main: the page JS it pins is unchanged by the engine swap and the file passes as-is. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
Brings the leak guard from #718 onto the branch and makes its guarantee hold for the Rust engine instead of the Node scripts it was written against. Conflicts and how each was resolved: - tests/live-poll-stream.test.mjs, tests/live-server.test.mjs, tests/live-target-context.test.mjs (modify/delete): kept deleted. They drove skill/scripts/live-server.mjs, which does not exist here; the verb behavior they covered is the oracle's job now. Their entries came out of test-suites.mjs along with the rest of main's live list, which is Node-script coverage this branch already retired. - scripts/test-suites.mjs: took main's two new entries that still apply, process-group.test.mjs into core and live-server-leak.test.mjs into live, plus the infra trigger patterns for the three new scripts/lib modules. Dropped main's pin.test.mjs (no such file here). - package.json: kept test:cleanup, dropped test:cli-e2e (no cli-e2e suite here). - scripts/run-tests.mjs: rewritten to hold both sides rather than picking one. From #718: the createGroupShutdown state machine, the per-suite run-id marker env, the post-suite leak check, and --cleanup. From 47f1871: the per-command wall-clock cap with its per-suite wallClockMs override and IMPECCABLE_TEST_WALL_CLOCK_MS, plus the killed-by-signal report. The two agree on the detached process group, so they compose: the cap SIGKILLs that group when a command wedges, the shutdown handler ends it on a signal, and both now sweep for leaked servers before exiting. #718's handler replaces the old raw signal forwarding, which sent one signal and never escalated. - tests/live-e2e/session.mjs: kept both sides. The binary-driven boot (runEngineSync, requireEngineBin, engineEnv) stands, with armLiveServerReaper at module scope and trackServerChild around the fixture dev server. Ported to the rest of the branch: - tests/oracle/lib.mjs arms the reaper and tracks the daemon child. Its daemon steps spawn live-server detached, so a SIGKILLed oracle run used to strand one; buildInvocation already inherits process.env, so the marker reaches it. - tests/live-server-leak.test.mjs now boots the engine binary through tests/lib/engine-bin.mjs and skips cleanly without one. No crate change was needed. The daemon spawn does env_clear().envs(env) against Io::stdio()'s env, which is std::env::vars(), so the detached Rust process carries the parent environment and the markers reach it. Verified against a real --background daemon: found by run id and by repo marker, not found by an adjacent checkout's marker. CLAUDE.md now says so, since narrowing that env would make the guard silently blind. Verified with a fresh cargo build --release -p impeccable: - IMPECCABLE_BIN=... bun run test green end to end: core 90, oracle 1 (zero unreviewed differences), detector 1, live 159 (157 pass, 2 skipped), framework 186, plugin-e2e 4. Zero servers left. - SIGKILL repro against impeccable live-server --background: 1 daemon up, 0 after with the reaper, 1 surviving with parent pid 1 under IMPECCABLE_NO_TEST_REAPER=1. bun run test:cleanup then kills exactly that one. - The leak test fails under IMPECCABLE_NO_TEST_REAPER=1 and passes with it. - IMPECCABLE_E2E_ONLY=vite8-react-plain bun run test:live-e2e 4/4. - bun run build green. AI assistance: prepared by Claude Code under pbakaus's direction. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
Owner
Author
The skill launcher and `impeccable install` both fail closed when a release binary's `.sha256` sidecar cannot be fetched or carries no hash: they refuse rather than cache an unverified binary. The npm shim did not. It only compared when a hash was present, so a 404, an empty sidecar, or a truncated one all wrote the payload straight into `~/.impeccable/bin/<version>/` and exec'd it. It now refuses in the same cases, with wording that matches the launcher, and writes nothing until the hash matches, so a refusal leaves the cache dir empty. IMPECCABLE_BIN and the optional-dependency lookup are untouched: neither downloads. tests/cli-shim.test.mjs runs the real shim against a throwaway HTTP server and covers missing, empty, and mismatched sidecars, plus the matching-sidecar and IMPECCABLE_BIN paths. The two refusal cases fail against the old shim. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
`live-workspaces/apps/web/vite.config.js` imports `@vitejs/plugin-react` but the workspace's package.json listed only `vite`. No oracle case installs or evaluates that config (the three `live-boot-workspaces-*` cases stop at root resolution), so the fixture was never wrong at runtime, only self-contradictory to read. Adding the devDependency keeps the goldens byte-equal. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
The recorder deduplicated by arguments per run, not across runs, so the frozen call snapshot arrived with 12,208 lines (43% of 28,266) that repeat an earlier line byte for byte. Every one re-asserts what its first occurrence already asserts, and `crates/core/tests/vectors.rs` replays line by line with no count anywhere, so removing them changes nothing it checks: the replay still reports 8,321 pass, 0 fail. Duplicates were removed with `awk '!seen[$0]++'`, keeping first occurrences and file order, and every changed file was checked to equal that transform of its old contents. No line was added, reordered, or rewritten, and no vector file gained or lost a distinct call. The tree drops from 9.2 MB to 5.7 MB. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
The JS engine applied value-level ignore waivers at the tail of
collectBrowserFindings: `_disabledValues` read the entries the live
overlay resolved for the page (skill/scripts/live-browser-ignores.js
sends them as config.disabledValues), and filtered the assembled
findings by the value each one reported, with design-system-color
compared by color value rather than by spelling so a hex waiver
suppressed a finding the browser reported as rgb(...). The Rust port
dropped that stage: `disabledValues` appeared nowhere in the workspace
or in browser-bundle, so a project entry like
[detector]
ignoreValues = [{ rule = "overused-font", value = "geist mono" }]
stopped reaching the overlay. The rules the CLI and the edit hook waive
kept drawing markers and counting toward the badge.
Restore it end to end:
* BrowserConfig gains `disabled_values`, parsed leniently so a
hand-edited __IMPECCABLE_CONFIG__ entry of the wrong shape is dropped
rather than failing the whole config, the way the JS filter did.
* The driver applies the waivers after every pass, so a rule pack's
findings are covered the same way the built-in ones are, honoring the
entries only in extension mode exactly as the JS read them. The
normalizer, the value extractor (including the rule that bounce-easing
without a direct ignoreValue offers no value) and the hex/rgb color
key are ported alongside it.
* collectConfigJson in the in-page bundle and configJson in the
offscreen bundle forward the field. The extension never sends it, so
its behavior is unchanged.
Coverage: two driver unit tests (suppression by font value, by hex
waiver across the rgb spelling, and the extension-mode gate; plus the
config parse and the normalizers), a skipScan test that pins the empty
shape for every stage the core produces, and
crates/wasm/tools/disabled-values-check.mjs, a browser-backed check
ported from the retired tests/detect-antipatterns-browser.test.mjs case
that the swap left without a replacement. Against the previous bundle it
fails on exactly the three waiver assertions and passes the skipScan
one, which is the shape of the regression.
Two related review findings were checked and are not defects. skipScan
is gated on extension mode in both the driver and the bundle, which is
what the JS did (index.mjs#skipScanActive), and the live overlay runs in
extension mode: live-browser.js sets `s.dataset.impeccableExtension` on
the injected /detect.js tag, and the overlay's whole detect toggle
travels over the postMessage loop that 50-scan.js installs only under
EXTENSION_MODE. The visual contrast stage is not leaking either:
collectBrowserFindingsAsync and scan() both consult skipScanActive(),
and the offscreen path skips its visual pass on config.skipScan.
The tracked live asset is regenerated (cargo xtask bundle). The oracle
replays with zero unreviewed differences: the new field defaults empty
and the filter is inert without it, and no CLI path sets extension mode.
AI-assisted change: implemented with Claude Code under maintainer
direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
The three fail-closed cases cleared IMPECCABLE_BIN and pointed IMPECCABLE_HOME at a temp dir, but locate() prefers an installed @impeccable/cli-<os>-<arch> before the cache or a download. Those platform packages ship with every engine release and are a merge prerequisite, so as soon as one is installed under the repo the cases would resolve it and go green without fetching anything. Confirmed by hand: with a platform package staged in node_modules, running the shim against an unreachable download base still exits 0 from the package. The shim now runs from a throwaway copy at <tmp>/cli/bin/cli.js beside a copy of the repo's package.json, with no node_modules on the lookup path above it, so require.resolve of the platform package fails the way it does on a machine without the optional dependency. Production code is unchanged; there is no test-only branch in the shim. The fixture server also records every request now, and each download case asserts the asset and sidecar URLs were actually requested, so a future lookup shortcut fails loudly instead of passing on an untested path. A sixth case installs a fake platform package next to the staged shim and asserts the shim prefers it with the server untouched, which pins the precedence the other cases depend on being absent. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
The overlay could sit in its generating shader over a DOM that already held all three variants, and only a page refresh cleared it (#719). The server's generation preflight runs live-wrap with --defer-source-write, so the wrapper and every variant reach the DOM in a single HMR batch. The deferred-wrapper scout is constructed at init and the variant MutationObserver at Go; observer callbacks run in construction order, so on that batch the scout resumes first and resumeSession, not the observer, is the transition into CYCLING. It set the state and the bar but never called hideShaderOverlay(), so the frozen capture of the original stayed painted over the variants. It also reported browser_resumed, which does not count as publication progress, and then disconnected and re-created the observer, dropping the records that observer had already queued for the same batch, so variants_ready never fired at all. resumeSession now finishes the same transition the observer does (shader down, inline edit off, insert session finalized, params panel rebuilt) and reports variants_ready when it already holds every variant. The deferred scout names itself in the journal as browser_resumed_deferred_wrapper, so the two resume paths are no longer indistinguishable. Wrapper resolution goes through findVariantsWrapper, which prefers a wrapper that actually holds non-original variants. A target inside a .map() renders one wrapper per item, and an agent that relocates the wrapper out of the shared primitive live-wrap scaffolded leaves an empty one behind; first match could pin either and strand the session at 0/N. With zero or one match this is the querySelector it replaces. Tests: waitForCycling now asserts the generating shader is gone once the bar cycles, across every runtime fixture (it failed on vite8-react-plain before this change and passes after), marked no-retry so the reload recovery cannot hide it. Source-shape tests pin the transition, the variants_ready report, and the wrapper preference. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
Two Rust-only regressions found while investigating #719, both of which can leave a tab waiting on a broadcast that never comes. /stop ran shutdown() but never set shutting_down, and the accept loop only breaks on that flag or a signal, so a stopped server kept its port and kept answering while its server.json was already deleted. The next `impeccable live` then booted a second server on another port and a tab could reattach to the zombie. Node's shutdown() ended in process.exit(0). The flag is now set after the response is written, so `stop` still reads "stopping" instead of a reset connection, and the accept loop (already non-blocking) exits on its next pass. GET /events took a turnstile ticket and waited its turn before registering, even though handle_sse releases that ticket two statements later and needs no arrival ordering. A peer that stalls mid-request holds the lane for the whole READ_REQUEST_DEADLINE, so a reconnecting stream could sit unregistered for up to 10 seconds (measured 9.71s against 0.00s on Node); broadcast is fire-and-forget, so a `done` landing in that window reaches an empty client set and is gone. Registering early can only make a stream see more broadcasts. The one cost is that the connected frame's activeSessions snapshot may miss a mutation still in flight, and the browser treats that snapshot as a hint. Preflights still take a turn: answering those out of order reorders the POSTs the browser issues behind them. The route classification moved into releases_ticket_up_front so it can be unit tested. tests/live-server-leak.test.mjs gains a guard that a stopped server's pid is gone and its port is free. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 26cb1f0. Configure here.
The new cycling assertion caught a real defect on CI: vite8-react-insert
reached CYCLING with #impeccable-live-shader still painted over the page.
showShaderOverlay is async. It appends its canvas synchronously, then
awaits createImageBitmap and finishes the GL setup before it publishes
shaderState. hideShaderOverlay returned early on a null shaderState, so a
teardown that landed inside that window did nothing, and the construction
then published itself over a session that had already left GENERATING,
with no teardown left to run. The scroll tick kept repositioning it,
which is why the CI page.html shows the canvas sized from the capture
rect but styled to the cycling anchor.
Every teardown now bumps a shader epoch before it does anything else, and
a construction pins the epoch it owns and abandons its canvas (releasing
the GL context) at every point past an await and before any publish,
including both bitmap-fallback publishes. A teardown also drops a shader
node that no shaderState owns, so an already-orphaned canvas cannot
survive one.
Reproduced by widening the append-to-publish window: with a 400ms delay
after uiAppend, vite8-react-insert failed with the CI error and the probe
showed the teardown arriving at CYCLING with shaderState still null.
The same run passes with this change, as does a 1500ms window on insert
and plain. Locally that window is about 4ms, which is why it only showed
on a slower runner.
The four remaining setLiveState('CYCLING') sites that did not lower the
loader now do: the SSE done handler (the one route that can reach CYCLING
from GENERATING), the Svelte republish remount, and the two accept
failure recoveries.
The e2e assertion already waits up to 5s for the shader to clear, so it
was never racing a legitimate teardown; it is left as it is.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
Cursor Bugbot on #720: findVariantsWrapper alone was not enough. resolveBarAnchor, the visible-variant element, mountedParameterCount, readVisibleVariantFromDOM, showVariantInDOM, the source injection, and the whole accept path still took the first [data-impeccable-variants] match, so in the relocated-wrapper case Tune never bound and the bar kept anchoring to the empty scaffold even after the resume reached CYCLING. Thirteen call sites now resolve through findVariantsWrapper. The resolver split in two so a missing id cannot silently widen the lookup to any session: findVariantsWrapper(sessionId) returns null without an id, and findAnyVariantsWrapper() is the entry point for the two resume paths that have no id yet. Both share pickPopulatedVariantsWrapper, which is the old querySelector whenever there are fewer than two matches. Discard cleanup now hides every duplicate wrapper rather than the first, since a target inside a `.map()` renders one per item and hiding one left the rest of the discarded variants on screen. What still takes a raw first match is deliberate: bare existence checks, selector strings for stylesheets and observers (which want to cover every match), querySelectorAll sweeps, the parsed source document, and the Svelte component wrapper, which holds no variant children at all. The source-shape test pins that exact set by name, so a new raw lookup fails until it is either routed through the resolver or justified there. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
Bugbot on #720: the non-restoreOriginal discard now hides every matching wrapper, but the delayed fallback still released only the first querySelector hit. A target inside a `.map()` renders one wrapper per item, so the rest stayed at display:none and their original content never came back on the static and missed-HMR flows that fallback exists for. The hide, the existence checks, and the release now all speak about the same set. discardedWrappers(sessionId) is the one place that collects it; releaseDiscardedStaticWrappers takes the stylesheet down once and releases each wrapper; releaseDiscardedStaticWrapper drops its sessionId argument and just unwinds the node it is given. The HMR-ownership decision still reads the first wrapper, which is fair: duplicates all render from one source element, so ownership is uniform across them. The reload branch is unchanged because a reload restores every original at once. Covered by a source-shape test rather than an e2e scenario: hasFrameworkHmrOwnership is true for every React, Vue, and Svelte runtime fixture, so all of them take the watcher path and none can reach the static release. The existing framework-ownership guards in the same file move to the new shape and keep their intent, including the one that says only non-discard cleanup may blank the wrapper while waiting for HMR. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
Every command the skill runs used to be a Node script, and the anti-pattern detector was a JS package. That put Node in the path of every user on every harness: Windows users hit path and dependency problems, hooks paid Node's startup on every edit, and any environment without Node (or without egress to install it) got no detector at all. The JS engine also existed twice, once for the CLI and once as the in-page bundle the extension and live mode inject, and the two had to be kept in step by hand.
This PR replaces all of it with one Rust binary,
impeccable <verb>, and one Rust rule core that compiles both to that binary and to WebAssembly for the browser surfaces.What that buys:
sh/.cmdlauncher that finds or downloads one static binary per platform.npx impeccablestays available as a thin shim, but nothing the skill does needs Node, npm, or a package install.context, which runs at the start of every session. The measured before and after is in the next section.RulePackextension point without forking (the first user is a private rule pack in a sister service).tests/oracle/replays 830 recorded verb invocations and 16,058 recorded function-call vectors against the binary and compares byte for byte; the reviewed exceptions are listed intests/oracle/DELTAS.md.Before and after
Same corpus, same machine, medians from
hyperfine. The old JS engine is main at the swap point; the Rust column is this branch's release build. Full method, machine spec, the exact commands, and caveats are in the benchmark comment.detectover the fixture corpusTwo results cut against the swap: the URL path shows no gain because headless Chrome is the whole cost, and the wasm module is slower than the JS engine on the median single file (it wins on the corpus total by being far better on the slowest files) while its in-page payload is about 5x larger gzipped.
What is in here
crates/:cli,common,context,hook,live,skills,comp,comp-verbs,detect,html,browser,foundation,core(the rules),wasm,bundle,xtask;rust-toolchain.toml; rootCargo.toml.docs/ENGINE.mdis the map.skill/scripts/impeccable(+.cmd):IMPECCABLE_BIN, a sibling binary, the user cache, or a sha256-verified download of the pinnedENGINE_VERSIONfrom this repo'sengine-v<X>release.cli/bin/cli.jsbecomes the npm shim, with platform packages undercli/platform-packages/.browser-bundle/(the page-side JS) andcargo xtask bundle, which builds the in-page bundle (tracked atcrates/live/assets/) and the extension's detector pieces.release-engine.ymlonengine-v*tags,bun run release:engine, the release-order guard, and CI jobsrust,rust-windows,oracle(the oracle also replays on Linux).docs/ENGINE.md,docs/CLI-CONTRACT.md,docs/RUNTIME-ENV.md,docs/PORTING-GUIDE.md, and the CLAUDE.md engine sections.Verification
As of a2cd029:
cargo test --workspace384/0 on macOS and Windows; oracle 830 cases with zero unreviewed differences on macOS and Linux (platform-gated cases skip where they do not apply);bun run buildandbun run testgreen;bun run build:extension+web-ext lint0 errors; the live-e2e smoke groups run in CI on every push, and the full live-e2e, new-work-e2e and skill-behavior (15/15 on the baseline model) opt-in suites were green on the branch.Before merge
The launcher and the npm shim download the pinned
ENGINE_VERSION, so theengine-v0.1.0release and the npm platform packages have to exist first;bun run check:engine-releaseand CI'sengine-release-readyjob enforce the order.Reviewing
The port is deliberately literal (
docs/PORTING-GUIDE.md), so a behavior question is best asked as "which golden pins this". The generated provider directories (.claude/skills/...and friends) are rewritten by the sync workflow on merge and are not worth reading here.Prepared by Claude Code under pbakaus's direction.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
Note
High Risk
This is a full runtime swap (hooks, CLI, detector, live mode) with binary distribution and release-order dependencies; regressions affect every install path until
engine-v<ENGINE_VERSION>and platform npm packages are published and oracle/CI stay green.Overview
Replaces the skill’s Node script runtime and the JS anti-pattern engine with a root Cargo workspace that builds the
impeccableCLI binary, compiles the same rules to WASM for the extension and in-page bundle (cargo xtask bundle/bun run build:extension), and pins behavior viatests/oracle/goldens plus new CI jobs (rust,rust-windows,oracle).Distribution and integration:
ENGINE_VERSIONdrives downloads;skill/scripts/impeccable(and.cmd) resolve or fetch the binary;cli/becomes an npm shim with platform optional deps;release-engine.ymlandengine-release-readyguard release order. Hooks and docs now invokeimpeccable <verb>instead of*.mjs.CI and repo hygiene: Extension builds install Rust,
wasm-pack, and drop standalonebuild:browser; live E2E and remote CLI jobs build the engine from the PR checkout;.gitattributeskeeps oracle/fixture LF; engine binaries andskill/scripts/bin/are gitignored.Reviewed by Cursor Bugbot for commit a2cd029. Bugbot is set up for automated code reviews on this repo. Configure here.