mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
eebfb7c2ce65c21d8682c0e2de2c2865b68de7bd
541
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
eebfb7c2ce |
Release: CLI 4.0.2 and engine 0.1.1
Ship signed skill-bundle verification, fix annotated-session checkpoint ordering, and pin all published engine platform packages. Validated with Rust, Node, browser, and provider-backed end-to-end tests. AI assistance: prepared and validated with Codex under Paul Bakaus direction. |
||
|
|
e2ff625b63 |
The Rust engine: one binary replaces every script and the JS detector, fully open (#714)
* Add oracle harness: verb goldens and function-level vectors
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).
* Oracle: hook, hook-before-edit, hook-admin cases and goldens
Prepared with AI assistance (Claude Code).
* Add docs/CLI-CONTRACT.md: observable behavior of every impeccable verb
Prepared with AI assistance (Claude Code).
* Oracle: context/doctor/pin/surface-brief/critique/palette/embed/signals/csp/seed/genimg/question cases and goldens
Prepared with AI assistance (Claude Code).
* Oracle: live-mode cases and goldens (roots, inject, wrap, insert, accept, session, manual edits, daemon)
Prepared with AI assistance (Claude Code).
* Oracle: mask the binary path before HOME; export launcher env to the binary
Prepared with AI assistance (Claude Code).
* detect: set process.exitCode instead of exiting after the final write
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).
* Oracle: normalize the hook-admin command in both runtimes' forms and audit chars
Prepared with AI assistance (Claude Code).
* Skill text: invoke the impeccable launcher instead of node scripts
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).
* Scripts dir: replace the Node scripts with the impeccable launcher
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).
* Build: ship the launcher instead of bundling the JS engine
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: turn the impeccable npm package into a platform-binary shim
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).
* Tests: gate behavior on the oracle and the engine binary
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).
* Docs: describe the launcher, the engine pin, and the oracle gate
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).
* Tests: tighten the hook command guard assertion
Prepared with AI assistance (Claude Code).
* Oracle: re-golden 46 cases for the engine's own command names; record them in DELTAS.md
Prepared with AI assistance (Claude Code).
* Build: ship launcher-only release zips by default
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).
* Tests: drive the live-e2e orchestrator through the engine binary
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).
* Tests: run new-work-e2e through the engine's serve-question and generate-image verbs
Prepared with AI assistance (Claude Code).
* Tests: point the skill-behavior harness at the launcher and engine binary
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).
* Tests: note what plugin-e2e validates before and after the generated-output sync
Prepared with AI assistance (Claude Code).
* Oracle: record the engine's 'wasm-unsafe-eval' CSP meta patch as a reviewed delta
Prepared with AI assistance (Claude Code).
* Rebase reconciliation: fold main's post-freeze work into the swapped 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
* Launcher: engine-probe PATH validation, working .cmd download path; CI: 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).
* Oracle: restore detector goldens to post-fix behavior after the engine 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 (db1462b9^; 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
* Oracle: pin the Aug 17-31 verb fixes ported to the Rust engine
New cases: hook-session-grok-edit-then-stop (Grok Build camelCase envelope,
end_turn/shutdown/stopHookActive Stop handling,
|
||
|
|
524fb8c950 |
Live: a discard releases every wrapper it hid
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 |
||
|
|
f240348cc5 |
Live: every active-session wrapper lookup goes through the resolver
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 |
||
|
|
f7c92d9eb9 |
Live: the shader teardown can no longer race its own construction
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
|
||
|
|
6d5f78eebf |
Live: the loader now hands off when the resume is the arrival
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 |
||
|
|
3f815865ab |
Self-discard orphaned JSX live sessions again (#716)
* Self-discard orphaned JSX live sessions again (#715) #694 stopped the source fallback from fetching and DOMParser-injecting raw JSX, which was painting {expressions} and comment markers into the page. The JSX gate it put in front of the fetch decided everything from the live DOM alone, and an unmounted wrapper looks exactly like a wrapper that was deleted from the file, so it treated both as "wait for mount": the orphan branch counted down its retry budget and then fell out of the function with no terminal action. A resumed CYCLING session whose region had been edited out of source therefore never reached discardOrphanedSession, the durable snapshot stayed out of the discarded phase, and the picker stayed frozen, which is the #439 regression the live-e2e scenario pins. The fix restores the decision without restoring the parse: probeJsxWrapperForOrphan reads the file as plain text and matches the session marker, so no DOM is ever built from JSX. Marker present means the component is simply not mounted and the observer keeps waiting; marker absent after the same retry budget the HTML path uses means the file moved on, and the session self-discards. AI assistance: prepared by Claude Code under pbakaus's direction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY * Orphan probe: a source read that fails also retries, then discards Review on #716 (Greptile, Copilot): the probe's empty catch swallowed a failed /source read, so a session whose file had been renamed or deleted (404), or that hit a transient fetch failure, neither retried nor reached a terminal action, which is the frozen-picker failure the probe exists to end. A read that cannot answer now shares the retry budget with a read that answers without the marker, and after the budget the session is discarded with a reason that names the failure. Unit test pins that the probe has no empty catch and that the failure path discards. AI assistance: prepared by Claude Code under pbakaus's direction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY * Orphan probe: only evidence that the wrapper is gone may discard Review on #716 (Greptile, second pass): after the previous change a transient /source failure that outlasted the 3.6 s retry budget discarded a valid session, and a discard is durable. Now a read that answers without the marker, or a 404 (the file renamed or deleted), retries on the budget and then discards; any other failure retries on the budget and then keeps the session, warns, and tells the user it is checked again on the next event. The unit test pins both halves. AI assistance: prepared by Claude Code under pbakaus's direction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
8b326fc81e |
Fix: resolve unique --target names in monorepos (#700) (#706)
* Fix: resolve unique --target names in monorepos (#700) Bare child names such as Cantaro.Web now match a unique workspace candidate instead of being reported missing. AI assistance: Cursor Grok 4.6 implemented this change. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix: resolve --target once in the context CLI Reuse the resolved path for loadContext so a bare name does not walk workspace candidates twice. AI assistance: Cursor Grok 4.6 implemented this change. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix: match unique --target names after cwd absolutizing Live and other helpers resolve --target against cwd before context.mjs sees it. Treat a missing single-segment path the same as a bare workspace name so those callers still select the unique child. AI assistance: Cursor Grok 4.6 implemented this change. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com> |
||
|
|
fa44839f72 |
Fix detector URL scans and advisory handling (#709)
* Fix detector URL and advisory handling Recover joined URL arguments without splitting local paths, derive advisory behavior from registry severity across consumers, inspect readable linked CSS in URL scans, and report only the dominant primary font. AI assistance disclosure: Implemented and verified with Codex under maintainer direction. * Filter linked CSS to rendered selectors Flatten linked stylesheet grouping rules and collect only selector rules that target the live DOM, preventing unused grouped and selector-less patterns from leaking into URL findings. AI assistance disclosure: Implemented and verified with Codex under maintainer direction. * Fix detector review edge cases AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction. * Preserve unresolved linked CSS selectors AI assistance disclosure: Codex implemented and verified this fix under maintainer direction. * Fix linked CSS selector filtering Resolve pseudo-element selectors to live hosts, reject unresolvable linked CSS findings, and make the regression assertions independent. Also ignore comment delimiters when recovering CSS rule selectors. AI assistance disclosure: This commit was prepared with Codex under maintainer direction. * Skip unresolved container query CSS Exclude linked container-query groups when their current applicability cannot be resolved, with a browser regression proving inactive styles do not leak. AI assistance disclosure: This commit was prepared with Codex under maintainer direction. * Detect active container query CSS Use a temporary custom-property probe so the browser decides whether a nested style rule actually applies in the current container layout. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. * Filter inactive linked CSS states Keep valid empty pseudo-class matches authoritative and omit selector-less linked at-rules that cannot be tied to rendered nodes. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. * Parse pseudo-elements without rewriting literals Preserve quoted attribute values and escaped identifiers while resolving real pseudo-elements to live hosts. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. * Restore live linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Handle grouped linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Respect keyframe definition order AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Resolve effective linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Fix keyframe easing detection Serialize effective per-keyframe easing back into the linked stylesheet corpus so overshoot motion is detected. Add a browser regression with a neutral animation name.\n\nAI assistance disclosure: Codex helped implement and test this fix under maintainer direction. |
||
|
|
672ca29642 |
Fix Next.js 16 CSP and parent hook discovery (#710)
* Fix CSP and hook ancestor discovery Recognize Next.js 16 proxy files when detecting runtime CSP and mirror harness ancestor lookup when locating active hook manifests for nested projects. AI assistance disclosure: Implemented and verified with Codex under maintainer direction. * Tighten hook and proxy discovery AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction. * Honor ancestor hook disable config AI assistance disclosure: Codex implemented and verified this fix under maintainer direction. * Keep hook discovery within target repository Stop manifest discovery at the target repository boundary instead of re-adding an outer workspace root, with regression coverage for nested Git targets. AI assistance disclosure: This commit was prepared with Codex under maintainer direction. * Detect proxy CSP in nested Next apps Recognize proxy files at root or src placement relative to nested Next project markers while continuing to ignore unrelated proxy helpers. AI assistance disclosure: This commit was prepared with Codex under maintainer direction. * Resolve external targets from their own repository Scope explicit sibling targets to their own Git root so caller context and hook manifests cannot suppress required detector guidance. AI assistance disclosure: This commit was prepared with Codex under maintainer direction. * Isolate explicit targets at Git boundaries Keep nested repositories and external targets out of caller and home-level context or hook discovery. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. |
||
|
|
482368511a |
Fix Codex skill version metadata (#703)
Move Codex and .agents skill versions under metadata while keeping all version readers compatible with legacy top-level frontmatter.\n\nAI assistance: prepared with Codex under maintainer direction. |
||
|
|
9736a9f6e9 |
Fix OpenCode slash command bridge (#483)
Add a first-class OpenCode command bridge across builds, installs, updates, linked installs, and pinned shortcuts. Preserve current provider behavior while backfilling missing or drifted command files.\n\nAI assistance: contributor and maintainer work used AI tools as disclosed in the PR discussion and commits. |
||
|
|
38e102f0b2 |
Fix: never inject raw JSX in live-mode fallback (#454) (#694)
* Fix: never inject raw JSX in live-mode fallback (#454) On React/JSX targets, missed HMR used to fetch source and DOMParser-inject it, painting {expressions} and comment markers as page text. Adopt a live wrapper that already has variants, otherwise leave HMR alone. AI assistance: Cursor Grok 4.6 implemented this change. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix: wait for unmounted JSX variants instead of tearing down (#454) A missing live wrapper on React is often a closed modal or other route, not a failed generation. Leave the observer armed so mount can still reach CYCLING. AI assistance: Cursor Grok 4.6 implemented this change. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix: recover empty JSX replace wraps after fallback retries (#454) Insert scaffolds still wait for HMR. A replace wrapper with no variants after retries is a failed generation and should leave GENERATING. AI assistance: Cursor Grok 4.6 implemented this change. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix: align live-reference setup assertions with current SKILL.src.md #689 shortened Setup step 2, but the live-reference tests still expected the old playbook sentence, which kept CI red on main and this branch. AI assistance: Cursor Grok 4.6 implemented this change. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
6bc4f242c7 |
Fix live cleanup races with framework HMR (#695)
Guard delayed accept and discard DOM fallbacks when framework/HMR ownership is present, while preserving static-page cleanup. Add unit/source regressions for both paths and refresh stale Setup wording assertions from #689. AI-assisted: prepared with Codex under @pbakaus direction. |
||
|
|
6fe900dbb4 |
Improve incumbent evidence and direction fusion (#689)
Prefer committed visual goldens when the app cannot run and make assigned-system translation explicit when a pinned register conflicts with literal materials.\n\nAI-assisted: prepared with Codex under @pbakaus direction. |
||
|
|
632912b5ae |
Fix live script response encoding (#690)
Declare UTF-8 on the generated live and detector JavaScript responses and cover both endpoints with integration assertions.\n\nAI-assisted: prepared with Codex under @pbakaus direction. |
||
|
|
187790826d |
Fix concept seed under symlinked installs (#686)
Resolve the CLI entry path through realpath and cover linked skill directories on Unix and Windows junctions. AI-assisted change: implemented with Codex under @pbakaus direction. |
||
|
|
5211bdf4b1 |
Fix: critique snapshot close path (#660)
Preserve critique trend history while closing the exact processed snapshot. Track target identity and content freshness for local files and URLs, isolate colliding streams, and prevent legacy snapshots from resurfacing after a modern close. Validated with 50 focused tests, a clean 17-provider build, 834 core tests, passing GitHub CI and Cursor Bugbot, Greptile 5/5, and zero unresolved review threads. AI-assisted maintainer repair: implemented and reviewed with Codex under @pbakaus direction. |
||
|
|
4fb66a15e7 |
Fix prompt embedding for install paths with spaces (#676)
Resolves the embed helper with a filesystem path, reports fallback accurately, and adds regression coverage while leaving generated harness output to the post-merge sync.\n\nAI-assisted maintainer repair, review, and validation by Codex under maintainer direction. |
||
|
|
8c59bc7fb0 |
Centralize image prompt parsing (#641)
Centralize raster prompt lookup and PNG chunk parsing while preserving read, scan, replacement, and sidecar behavior. AI-assisted merge: reviewed and executed by Codex under explicit interactive maintainer direction. |
||
|
|
ac64a2fe12 |
Fix grammar in skill introduction (#679)
Correct the article before award-winning in the source skill introduction. Generated provider copies will refresh through the post-merge sync. AI-assisted merge: reviewed and executed by Codex under maintainer direction. |
||
|
|
1130d00ebc |
Fix: keep direction contracts out of production output (#673)
Store direction contracts in development-only surface briefs and guard against browser-delivered leakage with regression coverage. AI-assisted merge: reviewed and executed by Codex under maintainer direction. |
||
|
|
fc442be223 |
Fix: make hooks reset remove installed manifest entries (#668)
Fixes #512 and resolves the verified reproduction in #667.\n\nAI-assisted change: reviewed and merged by Codex under maintainer direction. |
||
|
|
f1e8d4e70e |
Simplify named rule extraction
AI-assisted by OpenAI Codex under maintainer pbakaus's scheduled-refactor authorization. |
||
|
|
9434dde9af |
Merge main: skipScan visual-contrast coverage, live overlay waivers, generated output sync
The generated browser bundle is rebuilt from the merged engine sources in the next commit's build step (both branches had regenerated it). AI-assisted (Claude Code). |
||
|
|
10f7c7b6f8 |
The unreferenced-plate refusal names its own escape: --artifact <page> when the scan cannot see the reference
Closes the residual page-inference edge (no recorded artifact, no index.html, several root HTML files) by making the conservative refusal self-correcting instead of adding more inference; the gate never falsely passes in that configuration, only asks for the page. AI-assisted (Claude Code). |
||
|
|
af109a85ae |
gateHero resolves the page before the unreferenced-plates check
Greptile's fourth finding on the seam: the no-artifact path was depth-limited. The page default (index.html, or the one .html at the root) now applies before unreferencedPlates, so the link-following path, which is exact and unbounded, handles every build that has a page; the bounded walk is only the no-page fallback. AI-assisted (Claude Code). |
||
|
|
0d2df39339 |
Root-relative stylesheet hrefs resolve against the project, not the drive root
Bugbot on #599: path.resolve treated /assets/hero.css as filesystem-absolute. Both the working directory and the artifact's directory are tried; unreadable candidates skip. AI-assisted (Claude Code). |
||
|
|
64001fe213 |
unreferencedPlates follows the artifact's linked stylesheets by name
Greptile's third P1 on the same seam: a stylesheet linked from the artifact but outside the bounded walk's root, depth, or file limit was still invisible. The hrefs the artifact itself declares are resolved against its directory and joined to the corpus, which closes every variant. AI-assisted (Claude Code). |
||
|
|
18e8c287b5 |
unreferencedPlates: an explicit artifact joins the source corpus instead of replacing it
Greptile's follow-up P1 on #599: with --artifact set, only that HTML file was read, so a plate referenced exclusively from a linked stylesheet still read as unused. The bounded source walk now runs either way. AI-assisted (Claude Code). |
||
|
|
09ddc1758e |
sourceFiles walks assets/: a stylesheet there may be the one reference to a plate
Greptile P1 on #599: unreferencedPlates read a plate referenced only from assets/hero.css as unused and the hero gate refused a valid build. The extension filter already keeps binaries out of the walk. AI-assisted (Claude Code). |
||
|
|
3818a5655b |
Address the Bugbot and Copilot findings on #599
- keyChroma re-encodes with the PNG's tEXt chunks intact (the embedded prompt survived generation but not keying)
- organic-clip-path counts relative curve commands too (path data letters are only commands, so the match is case-insensitive)
- buried-raster normalizes percentage alphas (parseFloat('80%') read as 80) and reads 4- and 8-digit hex alpha instead of treating #rrggbbaa as opaque
- the extension-injected-node skip in checkQuality runs before any finding is pushed (a low-opacity injected raster was recorded, then returned by the skip)
- fake-mode plates carry impeccable:fake tEXt and the plates gate's crop-identity refusal skips them (fake mode IS the crop by design; the refusal is for models shipping the comp's pixels as artwork)
Findings by cursor[bot] and Copilot on PR #599; detector engines rebuilt (build:browser, build:extension).
AI-assisted (Claude Code).
|
||
|
|
7edc5a43da |
font-match: a browser module without its binary is the same as no browser
CI resolves playwright but has no downloaded chromium; launch threw instead of falling back to the catalog ranking, and every spec gate downstream failed. Launch failures now return the no-browser path (and the browser test skips instead of asserting). AI-assisted (Claude Code). |
||
|
|
c75f9f1086 |
Above the bar, hero readings advise instead of block; spec escape hatches persist and announce; font-match tolerates an unwritable /tmp
Paul's decision on the tenth sweep's design question: hard vetoes (missing region, contradicted plate or text, SVG illustration, clipped plate, invented ink) stay unconditional; at overall >= HERO_MIN the numeric readings (ink colour, letter-spacing, line pitch, strip heights, box positions) print as advisories with the pass and belong to the polish pass. Every sweep-10 sample closes its hero under this condition, which settles 07 without another paid round. Ninth-sweep defects: codeDrawn / container / bleed now persist into spec.json with WARN lines (an overridden refusal used to vanish from the record); font-match probes os.tmpdir() and points TMPDIR at .impeccable/tmp when the sandbox /tmp is unwritable (every ninth-sweep rank silently fell back to the catalog). AI-assisted (Claude Code). |
||
|
|
46f13989eb |
Fix: write the build-path flip before answering the POST
serve-question answered POST /build-path with 200 and only then wrote the flip file. The caller is a separate process, so the response could reach it while the server was still preempted before the write landed: a poller that trusted the 200 could look for the flip file and miss it. Measured on a loaded machine, the old order lost that race 29 times out of 40; writing first and answering after loses it 0 times out of 40. This is what made tests/serve-question.test.mjs fail intermittently in CI on the Node 22 job while passing on Node 24. AI-assisted change: diagnosed and implemented with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
152d6940b0 |
Fix: harden live overlay detector waivers (#639 follow-up)
Read waiver config from every live root (appRoot, contextRoot, repoRoot), so monorepo projects whose config lives at the repo root reach the overlay; serialize served roots and page identities repo-relative there. Resolve each page URL to its actual serving file via the inject config's resolved page list before applying file-scoped waivers; ambiguous URLs keep the conservative common-ancestor fallback (PR #645 review discussion r3840011436). Honour detector.ignoreFiles: a wholly waived page now scans to zero findings in the overlay, matching the CLI and the edit hook. Guard the resolver call so a throwing resolver degrades to an unfiltered scan instead of breaking the detect toggle. Match design-system-color waivers by color value across hex and rgb() spellings, and stop extracting font values for bounce-easing findings, mirroring extractFindingIgnoreValue. Regenerate the browser bundle. AI-assisted change: reviewed, planned, and implemented with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cbd7870159 |
Address review: collision-resistant slugs, os.homedir() tilde expansion
- The per-project state dir key is now the readable separator-mapped slug plus an 8-hex sha256 of the resolved project path. The readable part alone is lossy (/x/my.app and /x/my-app both mapped to -x-my-app and shared hook state); the digest keeps distinct projects' cache and pending state apart while the dir name stays human-scannable. - Tilde roots now expand via os.homedir() instead of HOME/USERPROFILE with a '.' fallback. When no home dir can be determined, expansion is rejected and state falls back to the project-local default rather than anchoring under the hook process's working directory. - Tests updated to the digest-suffixed slug via a mirrored slugFor() helper, plus two new cases: colliding readable slugs get distinct state dirs, and the tilde form resolves identically to the explicit homedir-joined form. Prepared with AI assistance (Claude Code) under direction of 0xDarkMatter, per the maintainer-approved issue #422. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
30b3628f5b |
Expand a leading ~ in IMPECCABLE_CACHE_ROOT against the home dir
Env files and settings JSON hand '~/caches' to Node unexpanded; without this it would resolve to a literal '~' directory under the process cwd. Mirrors the exact treatment IMPECCABLE_HOOK_LOG already gets in writeAuditLog (HOME || USERPROFILE fallback), plus the Windows '~\' spelling. Prepared with AI assistance (Claude Code) under direction of 0xDarkMatter, per the maintainer-approved issue #422. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5c82d58b7e |
Harden IMPECCABLE_CACHE_ROOT edges: normalization, opt-in gate, failure path
- hookStateDir now trims the env value (stray whitespace in env files) and path.resolve()s both the root and the cwd, so trailing separators and relative segments slug to the same per-project dir. - The #344/#305 persist gate also treats an existing (possibly redirected) cache file as the opted-in marker. Without this, once state relocated, clean-edit editCount bumps stopped persisting because the project-local .impeccable/ dir never appears. No-op under stock paths, where the cache file lives inside .impeccable/. - New tests: slug normalization equivalences, whitespace trim, graceful persistCache failure on an unusable root, and three runHook end-to-end cases (findings persist + dedup through the redirect, clean-edit editCount persistence, and the no-footprint no-op gate holding under redirect). Prepared with AI assistance (Claude Code) under direction of 0xDarkMatter, per the maintainer-approved issue #422. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
77a2eae861 |
Add IMPECCABLE_CACHE_ROOT to relocate hook state out of project roots (#422)
Honor an optional IMPECCABLE_CACHE_ROOT env var in getCachePath() / getPendingPath(): when set, hook.cache.json and hook.pending.json land under $IMPECCABLE_CACHE_ROOT/<project-slug>/ (slug = project path with [:\/.] mapped to hyphens, mirroring Claude Code's ~/.claude/projects/ convention). Unset or blank env keeps stock project-local behavior. User-authored config (config.json, config.local.json, design.json) deliberately stays project-local - only disposable state relocates. Also clears ambient IMPECCABLE_CACHE_ROOT at the top of hook.test.mjs so a developer running the suite with the redirect active still gets deterministic stock-path assertions; the new suite sets and restores the var explicitly. Prepared with AI assistance (Claude Code) under direction of 0xDarkMatter, per the maintainer-approved issue #422. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
08b03e8763 |
Centralize live path glob matching
AI-assisted change prepared by Codex under scheduled architecture-simplification authorization from maintainer pbakaus. |
||
|
|
45943c3b1f |
Fix: assert only the common ancestor of the glob roots as a URL prefix
Cursor's review caught the previous commit over-correcting. One tree listed at two depths (prototype/*.html plus prototype/library/**/*.html) derived two roots, and requiring a waiver to match under both stopped a normal project-relative waiver like prototype/index.html from applying anywhere. The rule both reviews were circling is simpler: one live session is served by one server, so a single document root must sit at or above every configured page. The only prefix the resolver can safely assert is the deepest common ancestor of the glob roots. Nested roots collapse to their shared tree, so normal waivers keep applying. Disjoint roots (src/ and public/) share nothing, so no prefix is asserted and only the URL path itself matches, which keeps the earlier fix intact: a src/foo.html waiver still cannot hide a finding on a page served from public/foo.html. This also deletes the match-under-every-root machinery from the previous commit; with a single asserted prefix, plain matching is enough. Also switches the new test file to derive the repo root from import.meta.url rather than process.cwd(), per review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ce1c9f8dad |
Fix: a waiver scoped to one served root must not hide findings on another
Greptile's review found a real bug in the new resolver. When the live config lists pages under more than one folder (src/**/*.html and public/**/*.html), the overlay treated a URL like /foo.html as src/foo.html and public/foo.html at the same time. A waiver written only for src/foo.html could then hide a finding on the page actually served from public/foo.html. That fails in the worst direction: a real finding disappears and nothing says so. The overlay can never look up the right file. The live server does not serve the pages; the project's own dev or static server does, and its URL-to-file mapping is invisible from here. So the fix stops guessing: a file-scoped waiver now applies only when it matches the URL path itself, which is true whichever folder serves the page, or when it matches under every configured folder, so no possible reading disagrees. Anything ambiguous shows the finding, which is also what the CLI reports for the file really being served. With a single configured root, the common case, nothing changes: the new rule reduces to the old behaviour exactly. Multi-root projects keep three ways to write a waiver that still applies: name the file under each folder, use the bare path, or use **/. Two new unit tests pin the ambiguous case and the safe spellings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5330fa358e |
Fix: honour .impeccable detector ignores in the live overlay (#639)
The live overlay's detect scan ran unfiltered: requestDetectScan() posted
only { scanId }, so detector.ignoreRules and detector.ignoreValues in
.impeccable/config.json reached impeccable detect and the edit hook but
never the surface a designer actually watches.
The server now serializes the project's detector waivers into the /live.js
prelude (window.__IMPECCABLE_PROJECT_IGNORES__), read per request through
hook-lib's readConfig so config.local.json wins and edits land on the next
tab reload. A new script part, live-browser-ignores.js, resolves that
config against the page URL when a scan starts: ignoreRules suppress
outright, wildcard ignoreValues suppress their rule in the files their
globs name, and the remaining entries ride along as disabledValues for the
detector to match on each finding's own value. The detector bundle applies
those where the findings are assembled, since the overlay draws its own
markers from the collected findings.
Scope resolution mirrors cli/lib/impeccable-config.mjs deliberately: the
same glob dialect (globToRegex, including {a,b} alternation), the same
path-suffix matching as findingMatchesScopedIgnoreFile, and the same
refusal to apply an unscoped wildcard entry. The served-root prefixes that
bridge project-relative globs and site-relative URLs come from the inject
config's own files globs, never from the ignore globs; deriving them from
the ignore globs lets one entry scoped to prototype/library/** lend its
prefix to every page and suppress site-wide, which looks like success
because the numbers go down.
Known gaps, recorded in the detector comment: the motion value extractor
is not mirrored, so a value-scoped bounce-easing waiver only matches when
the finding carries ignoreValue directly, and design-system-color matches
on the normalized string without the CLI's color-equality fallback.
Tests: unit tests for the resolver part (stale globals, string ignoreRules,
malformed entries, directory URLs, percent-escapes, glob metacharacters,
the roots trap), an extension-mode puppeteer test that disabledValues
suppress exactly the waived findings, and the live-browser regression pin
now asserts the new scan config shape instead of { scanId }.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
2896e28613 |
Hero code scans read index.html when start recorded no artifact; painted-note regex learns 'geometry', 'leader lines', 'thumbnail'
A sol build named its two carburetor drawings 'countable ... geometry' chrome regions, drew them in inline SVG, and the SVG ban never ran because state.artifact was null. AI-assisted (Claude Code). |
||
|
|
bef185360f |
build-phase.mjs scaffold: the measured layout as CSS custom properties and a reference page
A reference, not the page: --r-<id>-x/y/w/h in % of the comp (plus cap height, font-size, family, weight where measured) to bind to any markup, and hero-reference.html with every region at its box and every plate placed with object-fit: contain, as a check on positions. Attacks the most common execution failure of weaker builders (badly positioned, overflowing, pushed below the fold) without dictating structure to strong ones; overlapping boxes are overlapping boxes and the gate reads pixels regardless. AI-assisted (Claude Code). |
||
|
|
2debb7078f |
Hero refuses inline SVG illustrations; finish cannot record ship over an open phase; the comp-led path names its model tier
From the human review's most repeated pin ('terrible svg instead of asset', on every model) and from sessions that wrote 'ship' with the hero open. Icons, arrows, chevrons, and runtime data charts stay code; diagrams, notation, and leader lines are plates.
AI-assisted (Claude Code).
|
||
|
|
38170564c6 |
Plates gate refuses a comp crop shipped as a plate; text readings include letter-spacing
From the final review batch: 'bad asset crop (crops are never allowed)' twice, 'letter spacing way too wide'. A crop resampled to the region scores 99.8% structure against the raw region; a produced plate scores 30-60. AI-assisted (Claude Code). |
||
|
|
77acd73cd3 |
Hero readings: sibling regions fold into one line; ink colour only on type at cap 16+
A build reached hero 81% and stalled on eight staff rows read one at a time (and both ways on ink colour). The session asked the user and was told to build as written; the force was legitimate and recorded. AI-assisted (Claude Code). |
||
|
|
7cea970c6c |
comp-spec snaps text and control regions to the largest ink mass in their grid span
A session's own note said its hero sat at 67 because the 10x10 grid boxes straddled two elements each, and it was right: every downstream measurement (cap height, line count, structure) inherited the slop. Text and control regions now snap to the dominant connected ink inside the span (page-ground threshold, dilated cells, masses touching the span's sides lose to inside masses), keep the span on the record for coverage, and can opt out with snap: false. AI-assisted (Claude Code). |