mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-21 18:47:02 +03:00
0fea696a7fc8817b990fe73931812cf49e1ab093
32
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
71a3341289 |
bake: the anchor must have matched one element on the page
The lasting rules a bake appends apply to every element the anchor matches, so a `tag.class` anchor shared by siblings (three cards from one JSX element, say) restyled all of them, not the element the user picked. The overlay now journals, with the generate event, the anchor it would bake on (`element.anchor`: the id, else the tag with its classes) and how many elements matched it when Go fired (`element.anchorMatches`). The planner bakes only when the source anchor is that same selector and the count is one; otherwise it leaves the carbonize block with the count in `bakeSkipped`, and the agent integrates the variant by hand. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
2d2009d7c1 |
live-server: a departed overlay's report is not a roll-call word
A claim carrying a clientId no connection holds any more (the page
unloaded between the broadcast and the claim landing) was still recorded,
so a departed tab's busy or no_match report could complete the roll call,
or set its verdict, against the overlays that remain. Such a report is
now answered `{granted:false, pending:true}` and not recorded, while any
connection that sent no clientId keeps every id counted as connected.
Eligible claims are left as they were: a lease a departed page holds
lapses and a rescuer takes it, and refusing them would also refuse the
renew a live overlay sends inside an EventSource reconnect gap.
Written with AI assistance (Claude).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
4b3a0e932d |
bake: a :scope child compound merges into the element anchor
`:scope > .card` describes the wrapper's only child, the accepted element itself, so the lasting rule is the anchor with what the compound adds (a class the anchor lacks, an attribute, a state), never bare `.card`, which after the append would style every card on the page. A type in the compound must be the anchor's own; an id anchor takes any. Pinned in the rewrite and accept tests; contract updated. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
695d1bd515 |
generate: plan, tune, and accept exactly like live
The lane's variants were tamer than the ones a live session makes on the same element: its poll instructions replaced live.md's planning method with a cheat sheet, its reference forbade knobs, told the agent to copy the markup verbatim and to treat DESIGN.md as a hard boundary, and its accept appended anchored overrides instead of integrating the design. Measured on the same page with Opus, live runs promoted a tier, broke the grid, and declared knobs; lane runs restyled three equal boxes. Now a Go the generate verb fires gets the same _instructions as a user's Go (the action's reference, section 4 planning, knobs per section 7), generate.md hands the design work to live.md's Handle generate and its Required after accept, Setup runs as for any command, the Tune chip behaves as in any session, and the mechanical bake is opt-in (--bake) instead of the lane's default. The start verdict points at live.md, and `browser` (the config key the opener reads) is a recognized key. Goldens re-recorded for the accept help and the recognized-keys line. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c36e37808e |
live-generate: stop waiting when the dev server dies
A Cursor run reused a dev server another chat had started; that chat's terminal was reaped mid-session, so the page never reloaded into the overlay and --wait-for-browser ran out its 60 s budget before the agent found an error page and restarted the server by hand (about three minutes lost). The wait now watches the dev URL it knows (the one it opened, else the boot's, else the caller's hint) with a TCP connect every third tick; two misses in a row end it with dev_server_gone, whose instructions name the harness's way to start the dev script and rerun with --dev-url. generate.md lists the verdict; an integration test kills a stand-in server mid-wait and sees the verdict inside seconds. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
04eaefcf00 |
bake: a component root refuses the bake
A JSX component root (<PricingGrid className="pricing-grid">, <Card.Root>) renders whatever it likes, and its className or id prop may never reach that element, so anchoring rules on the prop could persist CSS that matches nothing or the wrong nested elements while accept reports success. The bake now refuses such a root (carbonize fallback, bakeSkipped names the component); only a lowercase element name anchors, custom elements included. Pinned in the anchor and plan unit tests; contract updated. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
df4740bad9 |
review: holder-only generate events, wrapper states and breakpoints in the bake
A generate event may open a session only from the page that holds the target's lease, or held it last while its lease lapsed or its page went away; a page that never claimed, or a lapsed holder once a rescuer has claimed, is refused as before. The pending target remembers its last holder for that. The bake now lands a state written on the wrapper (`:scope:hover > .x`, `:scope[open] > .x`) on the element that takes the wrapper's place, and rewrites Astro's prefixed rules wherever they sit, `@media` and `@supports` blocks at the top level included, instead of dropping the variant's breakpoints. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e06c152ad2 |
generate lane: one-shot start, event in hand, mechanical bake
The generate command's fast lane spent most of its time on agent round trips, not on the engine. Four engine changes take them out, all behind the lane's own flags so a plain `live` session is untouched: - `live-poll --reply <id> done --then-poll` replies and waits for the next event in one call; the reply's ack rides along as `_replyAck`. - `live-generate` collects the session's own generate event into its output (`GET /poll?types=generate&id=<sessionId>`, a new id filter the parked-poll flush honours too), so the pickup poll is gone. - `live-generate --boot` runs the lane's boot in-process and reuses a running helper; `--dev-url <url>` names the dev server the agent already knows and leads the probe; with no page connected the verdict is `browser_needed` with the harness's own way to open the page (Cursor browser_navigate, Claude Code's Browser pane, Codex --open or the user). `--open` launches the system browser only on a harness without one: on cursor and claude-code it is ignored unless IMPECCABLE_BROWSER or the config's `browser` names a browser, so a second window never opens beside the harness's. The served /live.js carries the helper-wide bar preference in its prelude. - The accept of a session the generate verb started (journaled with origin "agent", or `--bake`) is baked mechanically: the accepted variant's @scope rules are re-anchored on the element's own selector and appended to the stylesheet that names it, the wrapper is unwrapped, the source verified clean. Knobs, plumbing inside the variant, no stylesheet, or a selector the rewrite cannot decide fall back to the carbonize block with `bakeSkipped`. `--no-bake` refuses. Goldens re-recorded for the three help texts and the no-browser case. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
65c8089182 |
Only the lease holder may answer an agent target; leave a plain bar alone
Review found that /agent-target-result checked the shared helper token and nothing else, so any connected overlay could resolve a target it never claimed. A result post now names the overlay (`clientId`), and while the target is pending only its lease holder's word lands: a bystander gets 409 (not_holder, or unclaimed when nobody holds it) and the request stays pending. The overlay sends its client id with every result. Every protocol case now answers from the tab that actually holds the claim. Also: the helper-wide bar preference is applied on every connected frame, and restoring wrote an empty display value, which dropped the bar's own inline flex layout for a plain live session that never asked for anything. Hiding remembers the bar's display value, restoring puts exactly that back, and a restore on a visible bar is a no-op. A plain boot through the launcher keeps display: flex after connect and its payload carries none of the lane's keys. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1a699913e4 |
Make the generate lane's bar preference helper-wide
The maintainer still saw the bottom bar: two tabs were connected to the helper, the tab that won the roll call hid its bar, and the tab on screen never did. The hide was also applied only at Go, so the wait before it showed the bar as well. The helper now owns the preference. `impeccable live --no-live-bar` posts `/live-bar` right after the helper is up, so the bar never appears in any tab; an agent target carrying `hideLiveBar` sets the same flag before the target goes out. The helper broadcasts `live_bar` to every connected tab, answers `hideLiveBar` on every `connected` frame (reloads, later tabs) and on `/status`, and the flag lives as long as the helper. The overlay just follows: no per-tab memory, no session scoping, the variant controls still show. The same preference skips the overlay's "No PRODUCT.md found" connect notice, which sent the user to init inside a lane that runs without context by design. Verified in a real Chromium session with two tabs, screenshots at each stage: idle (bar in both), after Go (bar gone in both), after reloading both, after the accept during the bake, after a reload after that. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e3a121d081 |
Generate lane: settle the Tune state without knobs, and hide the live bar on request
Two things the maintainer hit testing the lane. The Tune chip spun forever after a generation whose variants declared no knobs (the lane's default). The overlay flips the parameter phase to pending at Go and only settled it when the wrapper mounted; the page reloads on the JSX write, the resumed session restores "pending" from its cache with the variants already mounted, and the agent's done reply never re-checked. Now the done reply completes the phase once every variant is mounted, and a resume with a pending state asks the helper's session record whether that generation already finished. A generation with no knobs shows no chip; one with knobs shows them. `live-generate --no-live-bar` (body `hideLiveBar: true`, forwarded on the agent_target payload) keeps the helper's global bar hidden for the session it starts; the variant controls still show, the choice survives a reload through the session cache, and the bar returns the moment that session ends on any path. generate.md passes the flag. Verified in a real Chromium tab: no chip before and after a reload, bar hidden through the reload, bar back after the accept. Rust and protocol cases for the flag, a CLI parse test, contract pins for both fixes. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f29636a3d5 |
Keep the generate lane's boot extras behind flags
The speed pass had every `impeccable live` boot probe for the dev server and print three new keys (devUrl, contextMissing, contextNote), which moved an oracle golden and cost a plain live session a probe it never asked for. The lane's extras are opt-in now: `--dev-url` runs the probe and reports devUrl; `--allow-missing-context` reports the context keys. Without either flag the boot's work and payload are byte-identical to before, which the restored golden and a new boot test pin. generate.md passes both flags; the contract doc says so. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1220f26d08 |
Make the generate lane snappy: self-contained playbook, fast-path poll instructions
The maintainer's field run took five and a half minutes from the prompt to variants on screen. Two baseline runs on the same repo reproduced it (356 s mean): 68 KB of skill text read before the first variant (a 36 KB live.md among it), six to ten tool calls spent finding the dev URL and the selector, 9 to 10 KB of variants carrying tune knobs, and a document read plus a detect pass after the accept. generate.md is now the whole contract for the lane and never sends the agent to live.md, craft-floor.md, or the action reference on the happy path; the floors are inlined. The engine carries the rest: a generate started by live-generate is journaled and queued with origin "agent", and its poll instructions hand out the fast path (identity from the event's computed styles and custom properties, the action's three dimensions, no knobs unless asked, one edit, reply done) instead of the interactive planning pointer. `impeccable live --allow-missing-context` boots without PRODUCT.md or DESIGN.md, naming what is missing, so the lane never falls into the init interview; the boot also reports devUrl, the origin whose page carries the injected tag, so the agent opens the page instead of reading terminals. Accept is a bake and live-complete is its verification: no detect pass, no document read. Three trimmed runs (one without any context files) averaged 179 s from prompt to variants, 21 tool calls and 106k tokens against the baseline's 356 s, 30 tool calls and 144k tokens; the accept bake went from 67 s to 41 s. Method and numbers: tmp/questionaire/plan41-field-tests/SNAPPY-REPORT.md in the maintainer's checkout. Tests: dev_url probe unit tests, a fast-path instructions unit test, the origin marker in the protocol suite, and tests/live-boot-fastpath.test.mjs (flag, contextMissing, devUrl through a stand-in dev server); contract doc updated. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d579ecb2f2 |
Address review: a Go for a target the helper does not hold is refused
The bounded record of answered targets evicted its oldest entry, and a generate event naming an unrecognized target was admitted, so a Go delayed past enough later resolutions could still open a session for a request the CLI had reported as failed. The admission rule is now positive: a generate event naming an agent target is welcome only while that target is pending without a rival lease, or when it comes from the session that answered it. Unknown targets, evicted or never issued, are refused like any other superseded Go, so eviction can never reopen a request. The record keeps 256 entries for the answering session's sake. Tests: a Rust integration case and a Node protocol case (an envelope naming an unheld target is refused and journals nothing; the same event without an envelope is an ordinary Go); contract doc updated. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
76db418212 |
Address review: every answered agent target fences a late Go
Only targets answered with a session were fenced against a delayed generate event. A request that timed out (or ended in another failure verdict the CLI already reported) was simply forgotten, so a Go whose capture outlasted the timeout still opened a session nobody was told about. `resolve_agent_target` now records every terminal resolution, with the answering session when the verdict carried one, and `agent_target_refusal` refuses a generate event for any answered target unless it comes from the answering session itself. The browser_timeout instructions no longer send the agent to live-status for a session that can no longer start. The overlay's refusal toast covers both causes. Tests: a Rust integration case and a Node protocol case (claim, time out, late Go refused with 409 and nothing journaled), a unit test for the timeout instruction; contract doc updated. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7adb81672d |
Address review: a superseded Go never opens a second session
An overlay renews its lease right before Go, then captures and uploads the element before its generate event leaves. When that outlasts the lease and its result post is lost, a rescuer can claim the target and Go, and the helper accepted both generate events: two sessions for one request. The generate envelope now carries this page's clientId, and the helper refuses a generate event for a target that another page holds under a live lease, or that was already answered with a different session (`served_agent_targets`, recorded on every ok resolution): 409 `agent_target_already_served`, nothing journaled. The overlay treats that refusal like a foreign session and hands the surface back. The answering session's own event stays welcome, so the common path (result post first, then the event) is unchanged. Tests: two Rust integration cases (rival lease, answered elsewhere, welcome for the serving session) and a Node protocol case, contract pins for the envelope and the refusal handling, contract doc. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b5210471fb |
Address review: the generate event resolves the agent target it serves
A winning overlay could reload after handleGo() minted a session but
before its result post landed. The close released its lease, the
server replayed the still-pending target, and another tab (or the
reloaded page, once it abandoned the unknown session) could claim it
and fire a second Go for a request that already had a session.
The overlay now names the target on the generate event it fires for it
(`agentTarget: {targetId, result}`, the same result it posts), and the
helper resolves the pending request the moment that event is accepted,
stripping the envelope before journaling. Whichever of the event and
the result post lands first answers; a page that dies between Go and
its result cannot leave the request pending, and a request whose event
never reached the helper is served exactly once by the rescuer.
Tests: a Rust integration case and a Node protocol case (claim, Go
event without a result post, verdict carries the session, a late claim
finds nothing pending, the journal carries no envelope), contract pins
for the handoff, and the contract doc.
Written with AI assistance (Claude).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
a3bb21cbde |
Address review: one Go per tab, declines for a granted miss, and grace per overlay
Four review threads on the agent-target protocol and the hook stand-down. Overlay: a tab acting on one target is busy for every other target (`agent_target_in_flight`), so two held generate requests can never both be claimed by one tab and the second Go can never overwrite the session the first one minted. Every exit from actOnAgentTarget ends the acting state, and teardown clears the target ledger, so a Go that never happened does not refuse the next connection's targets. A miss after a granted claim now declines (handing the lease back so another page or a remount can serve) instead of posting a result that ended the request for every tab. Hook: the live-preview marker probe runs before the per-session edit cap, so a file already past the cap stands down for a variants wrap instead of emitting the suppression notice. Server: each overlay's first no_match word extends the resolution grace by the full window (its watch re-reports do not), so an overlay that reports after another page's grace lapsed still gets its late-mount watch instead of completing the roll call with a no_match verdict. Tests: a Rust and a Node protocol case for the late overlay's grace, a hook case for the cap-then-wrap order, and contract pins for the busy check, the decline on a granted miss, and the teardown clear. Written with AI assistance (Claude). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
79a27051a2 |
Address review: hold an all-no_match roll call open for a resolution grace
When the unresolvable page's decline was the last word, the roll call completed on it, the answer said pending:false, and the page's watcher never started, so an element that mounted a moment later was still answered no_match. A page's no_match is a provisional word: the server now keeps an all-no_match roll call open for IMPECCABLE_AGENT_TARGET_RESOLVE_GRACE_MS (default 3000) after the first such report, re-judging when the grace lapses, so a page that keeps watching can still claim (the stale report is dropped on its eligible claim); a busy report still answers at once. The overlay reports a miss immediately and re-checks every half second for as long as the answer says pending. A genuine no_match now takes about the grace instead of tens of milliseconds, inside the server's hold. Rust integration case for the late mount claiming within the grace, the protocol case, and the contract assertions updated; the contract documents the grace and its env override. AI-assisted: implemented and tested with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bd4ec3a11c |
Address review: the server ends the resolution watch, and a reconnect re-participates
Two ways a page's word could go stale after the resolve-before-claim change: an element that mounts later than the quick re-checks, and an EventSource reconnect that did not overlap the old connection (the server drops that page's word on the close, replays the target, and the replay guard ignored it, so the roll call waited on a word that never came). A decline's answer now carries pending, like a denied claim does, so a page that could not resolve the target reports the miss after the quick re-checks (the roll call can complete on the other overlays' words) and keeps re-checking once a second for as long as the server says the request is pending, claiming the moment the element mounts; the server drops the stale report on an eligible claim and ends the watch by answering pending:false once the request resolved or timed out. The overlay tracks its participation per target: a replayed target is ignored only while this page is acting on it, and is otherwise handled again, so a busy or unresolvable page re-declines (idempotent) and an idle page claims. Unit, protocol, and contract cases updated; the decline answers now say whether the request is still pending. AI-assisted: implemented and tested with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
25263adc51 |
Roll call: a page that cannot resolve the target declines instead of claiming
Field-testing with two pages open showed the first-wins claim letting the wrong page answer: a tab whose page lacks the element won the claim, resolved the selector locally, and replied no_match while another page had the element. The overlay now resolves the selector before any claim and, when its page cannot resolve it, declines with reason no_match and the resolution verdict; the same check runs on the busy-to-idle re-claim. The server records that verdict on the report and, once every connected overlay has declined, prefers a report that could serve later (a tab mid-session or with an apply in flight, which answers busy so the agent retries) over no_match, and returns the resolution verdict only when no page can serve; the timeout uses the same precedence. Also from the same field tests: a tab on another page of the app was resuming this page's session from the per-origin localStorage cache after a dev-server reload re-initialised it, then sat in GENERATING for a wrapper it never renders and declined every later target. restoreSessionWithoutWrapper now resumes a cached session only on the page that saved it, the check the server-adoption branch beside it already applied. Covered by two Rust integration cases, two protocol cases, and contract assertions for the resolve-before-claim path and the page gate; the cross-page scenario of the field harness passes on a two-page site. AI-assisted: found by field tests and fixed with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8fb7f7fec5 |
Hook: stand down for the whole edit when the primary carries live markers
Field-testing the generate command on a Vite React app showed the per-file stand-down was not enough: an edit to the wrapped App.jsx skipped that file but still co-scanned the stylesheet it imports and spoke up about it (a clean ack or findings) mid-session, which is exactly the noise the stand-down exists to prevent. When the edited primary file carries the markers, the whole PostToolUse event now returns skipped: live-preview with the audit naming that primary, co-scanned stylesheets included; a marked file that is only co-scanned still skips alone. A unit case covers both, and the contract documents the event-level stand-down. AI-assisted: found by field tests and fixed with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
da403a3410 |
Address review: the overlay, not the connection, is the roll-call participant
An EventSource reconnect opens a replacement connection under the same page-level clientId before the old connection is seen to close, so the close handler used to retire the reconnected overlay's report and hand its lease back mid-flight. remove_sse_client now retires a client's word only when no other connection still carries its id, the roll call counts distinct overlays (plus id-less connections) instead of raw connections, and the overlay ignores a replayed target it already handled, so a reconnect never starts a second claim or a second Go. Covered by two new HTTP cases in crates/cli/tests/agent_target.rs, a protocol case in tests/live-agent-target.test.mjs, and the overlay contract suite. AI-assisted: implemented and tested with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d397140a77 |
Port /impeccable generate to the engine crates
The Node-era server, CLI, hook, and pin halves of the generate command move into the Rust workspace, with the protocol unchanged: - crates/live: POST /agent-target is held open on a channel plus a timer thread (the manual-apply deferred pattern), releasing its turnstile ticket before it parks like /poll; /agent-target-result resolves it; /agent-target-claim is the roll call with its renewable lease. SSE connections carry the overlay's clientId: a late overlay is replayed every pending target, and a disconnect retires that overlay's report, releases its lease, and re-judges each roll call. Shutdown drains held requests with server_stopping. - crates/live/src/live_generate.rs: the live-generate verb (the router already forwards every live* verb), same flags, verdicts, and _instructions, spelled with the engine's self command. - crates/hook: every entry stands down on live preview markers (skipped: live-preview), checking the proposed content and the file on disk for hook-before-edit. - crates/context: pin accepts generate; the crate's command-metadata.json copy carries its entry. Tests: crates/cli/tests/agent_target.rs (six HTTP cases with an SSE reader), tests/live-agent-target.test.mjs rewritten to drive the binary (28 cases, registered in the live suite), hook stand-down cases, oracle goldens for live-generate plus the re-recorded pin list goldens, the e2e prompt assertion waiting for the journaled event, and the contract documented in docs/CLI-CONTRACT.md. AI-assisted: implemented and tested with Claude Code under maintainer direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
67d018fe05 |
Fix: print JSON on live-poll --reply success (#800)
Successful --reply was exit 0 with empty stdout, so agents could not tell delivery from a hang. Prepared with AI assistance. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
599de0e949 |
Fix per-app design resolution in Rust hooks (#781)
Fixes #367. Resolve design rules per target file in the Rust post-edit, before-edit, and Stop hooks, preserving repository fallback and session state. Credit to tylerjryan for the original report and proposed approach. AI assistance: Codex, under maintainer direction. |
||
|
|
bd6964c35b |
Trust the OS certificate store for engine HTTPS requests (#757) (#759)
The engine verified TLS against the Mozilla roots bundled through webpki-roots only, so behind a TLS-inspecting proxy (Aikido, Zscaler, Netskope) whose root lives in the OS trust store, `impeccable update` and `install` failed with `invalid peer certificate: UnknownIssuer` while curl and npm on the same machine succeeded. crates/context/src/http.rs builds one rustls ClientConfig per process: the OS trust store (rustls-native-certs: Keychain, Windows store, the OpenSSL paths on Linux) merged with the bundled roots. A union, not a replacement, so a container without ca-certificates or a store that fails to load still verifies exactly as before. SSL_CERT_FILE and SSL_CERT_DIR replace the OS store the way they do for OpenSSL and curl. Every HTTPS call site (bundle and signature downloads, /api/version, /api/commands, the roll API, image generation) builds its agent from this module; the plain-HTTP live-server calls on localhost are untouched. Verified against a local HTTPS server signed by a throwaway CA: trusted through SSL_CERT_FILE the update check reaches it; without it the same server is rejected as UnknownIssuer; with SSL_CERT_FILE pointing at that CA or at a missing file, impeccable.style still verifies through the bundled roots. cargo test --workspace and the oracle corpus (832) pass. Written with AI assistance (Claude Code). Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
93bfe70c58 |
Fix truncated surface-brief slug collisions (#774)
* Fix truncated slug collisions Prepared with AI assistance under maintainer-authorized automation. * Preserve legacy long-slug reads Prepared with AI assistance under maintainer-authorized automation. * Harden legacy slug compatibility Require target metadata before reading collision-prone legacy brief and critique paths. Add regressions for two long targets with the same pre-hash suffix.\n\nPrepared with AI assistance. * Keep explicit access to legacy critiques Allow identity-less pre-hash snapshots to be read by their exact legacy slug while keeping path and URL fallback identity-gated. Document the compatibility boundary and extend collision coverage.\n\nPrepared with AI assistance. |
||
|
|
c4a4f035ed |
Fix: distinguish existing Stop findings from new debt (#754)
Use verified first-edit baselines to distinguish pre-existing text findings from new or unknown Stop findings. Preserve dirty worktrees, bound notice rendering, and keep explicit scans unchanged. Verified with the full Rust and Bun/Node suites and real Claude Code edit-to-Stop sessions. Related to #522; keep it open until an engine release ships the fix. AI assistance: Codex, under maintainer direction. |
||
|
|
6ebc24ad66 |
Add DeepSeek Harness as a supported skills provider (#746)
* Add DeepSeek Harness as a supported skills provider npx impeccable install now detects ~/.dsh (or $DSH_HOME when it sits under home) and installs into ~/.dsh/skills, the user-level skill root DeepSeek Harness scans, with project-level .dsh/skills on the same layout as other providers. Aliases: dsh, deepseek, deepseek-harness. Engine: PROVIDER_DIRS / aliases / display / input order / global hint, $DSH_HOME-aware user skills dir, provider id resolution from the skill dir, pin harness dirs, bundle path normalization for hashing. Build: dsh transformer target emitting the frontmatter DeepSeek Harness reads (user-invocable, license, compatibility, metadata; unknown keys are ignored there) with no emitHooks (DSH hooks are in-process plugins, not on-disk manifests) and no agentFormat (no documented on-disk subagent format); placeholders (AGENTS.md config file, ask_user_question tool, / command prefix), provider block tags, universal README entry. Docs: HARNESSES.md row and frontmatter column, CLI-CONTRACT constants, README/DEVELOP/AGENTS provider lists. Validation: cargo test --workspace; node scripts/run-tests.mjs core (138 pass); bun run build (19 providers, dist/dsh artifact verified); engine smoke against a fake HOME with a local bundle: install --providers=dsh --scope=global, auto-detected install, and update all resolve the .dsh provider. Generated provider output intentionally omitted per repo policy; the sync workflow regenerates tracked .dsh/skills after merge. Prepared with AI assistance (DeepSeek Harness coding agent). * Address review: DSH_HOME-only detection, generated-output pathspecs - Detect DeepSeek Harness through the resolved $DSH_HOME (fallback ~/.dsh) instead of gating on a fixed ~/.dsh path, so a DSH_HOME-only setup is offered by a provider-less install; generalize the two env-relocated config-dir hints (OpenCode, DSH) into one shared probe. - Add .dsh to the sync workflow's GENERATED_PATHS and CI's generated drift check so the tracked .dsh/skills payload is committed and validated. - Cover both behaviors: new install_detection_tests (DSH_HOME-only, default ~/.dsh, refused outside-home override) and a CLI-CONTRACT note on the resolved detection path. Validation: cargo test --workspace; node scripts/run-tests.mjs core (138 pass); engine smoke: DSH_HOME-only fake HOME installs globally into the resolved skills dir. Prepared with AI assistance (DeepSeek Harness coding agent). * Fix DeepSeek Harness home paths on Windows Use native relative-path containment, cover case and drive boundaries, and verify relocated global install/update without changing project skills. Add DSH output coverage and correct the install documentation. AI assistance: Codex, under pbakaus maintainer direction. * Document the CLI limit on external DSH homes Clarify that outside-home manual copies are not detected or updated by the CLI. AI assistance: Codex, under pbakaus maintainer direction. --------- Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com> |
||
|
|
8dac6ae7e0 |
Verify signed skill bundles before extraction (#734)
* Verify signed skill bundles before extraction Sign release ZIPs locally with an Ed25519 key from 1Password and pin the public trust root in the Rust installer. Reject unauthenticated downloads before extraction and preserve existing installs on failure. Document the signature-first rollout and explicit local trust paths. AI-assisted implementation prepared by Codex at Paul Bakaus’s request. * Fix signed-bundle review guardrails Make keyring loading failures fatal before any download, accept standard release redirect statuses while retaining URL pinning, and require the signature sidecar before tagging. Add regressions for all three review findings. AI-assisted changes prepared and tested by Codex at Paul Bakaus’s request. |
||
|
|
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,
|