Commit Graph
1238 Commits
Author SHA1 Message Date
Paul BakausandClaude Code 0c18cbc9ef fix: stop treating the child combinator as a prelude boundary when pruning
removeSelectorAt walked backward to find the rule prelude and stopped at
any '>', added so the walk would not escape past the <style> open tag.
That same character is the CSS child combinator, so pruning one unused
selector from a list like '.wrap > .orphan, .orphan' cut the prelude
mid-list; when every remaining fragment equaled the flagged selector, the
whole-rule branch then deleted from the cut point and left a dangling
'.wrap >' in source. A '>' now bounds the walk only when it actually
closes a <style ...> tag; combinators are walked through.

Regression tests cover a mid-list combinator prune and the dangling-
fragment shape (verified failing on the previous code).

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 18:05:41 -07:00
Paul BakausandClaude Code 20213a6817 fix: bound CSS seeding to real matches and ownership before supersession removal
Addresses two cursor findings on extractMatchingSourceCss plus an adjacent
hazard in the same removal machinery:

- Class matching is token-bounded, never substring: .btn no longer seeds
  .btn-primary and .stage no longer seeds .stages. A falsely seeded
  selector was an accept-time deletion of a hand-written rule, since any
  seeded selector the variant does not re-declare is removed as
  superseded.
- Tag rules that style the pick (h1, a, p) now seed the preview stub, so
  unclassed selections start from the real cascade. They are excluded
  from the supersedable set: tag rules style shared elements across the
  route and must never be removal candidates.
- Supersession removal is now bounded by ownership: a seeded class
  selector whose class is still used by markup OUTSIDE the replaced
  region survives the accept, because removing it would strip styling
  from markup the accept never touched.

Tests cover substring non-matches, tag seeding with a tag-free
supersedable set, and a shared-class accept where .card is used both
inside the pick and elsewhere.

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 17:55:13 -07:00
Paul BakausandClaude Code a83d767cf9 fix: stop cross-project live session leakage and stale-adapter 401s
Field session on a nested SvelteKit app surfaced a self-reinforcing leak:
localStorage is per-origin, two projects reused 127.0.0.1:5174, and a
React project's leftover cycling session was resumed inside the Svelte
project. Its checkpoints then materialized a ghost session in the new
project's durable store that kept reattaching after every discard, and a
stale adapter module 401'd on live.js, hiding the picker.

Four fixes:

- Server: only session-creating events (generate, steer) may mint a
  journal. Progress events (checkpoints, mount acks, accept/discard) for
  unknown ids are refused with 404 unknown_session and never enqueued, so
  foreign browser state cannot create ghost sessions. Browser sends are
  gated so progress never overtakes its own creating POST (the Go-time
  checkpoint and generate are concurrent fetches; the first sweep caught
  the out-of-order arrival breaking every SvelteKit flow). Steer
  checkpoints now follow the steer event for the same reason.
- Browser: saved sessions carry the server's appRoot; a session stamped
  by another project is dropped at load time. Unstamped legacy state is
  caught by the unknown_session refusal, which clears local state and
  re-arms the picker with an explanatory toast.
- SvelteKit adapter: the layout import carries a token-derived revision
  query so a helper restart changes the module specifier and no Vite
  client/SSR cache can serve an adapter with a rotated-out token;
  live-inject --port reads the running helper's token from server.json
  instead of writing an unauthenticated live.js URL; script load failures
  log an actionable console error; and adapter removal is byte-exact
  (the old regex swallowed the next line's indentation).
- live.mjs resolves surface briefs from appRoot, then contextRoot, then
  repoRoot, matching context.mjs in nested-app repos.

Tests: server unknown-session rejection units, adapter revision/
byte-exact-removal units, and a foreign-session e2e scenario that seeds
another project's localStorage state and asserts it is cleared, no ghost
journal materializes, and picking still works.

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 17:38:46 -07:00
Paul BakausandClaude Code 69456364b2 fix: self-discard orphaned variant sessions instead of freezing the picker
Fixes #439. When a cycling session is abandoned and the wrapped region is
then edited or regenerated out of the source file, the resumed page used
to sit in GENERATING forever with the picker disarmed; the only recovery
was a manual live-complete --discarded. Now a resumed CYCLING session
whose wrapper cannot be found in source retries the read a few times
(HMR or an agent write may be mid-flight), then discards itself, clears
local state, and re-arms the picker with a toast. GENERATING restores
are exempt: deferred-wrapper flows legitimately have no wrapper in
source until the agent's write lands.

The browser tags the discard event orphaned:true; the server terminalizes
that session directly (phase discarded) and keeps the event out of the
agent poll queue, since there is no source cleanup left to perform and
the normal discard flow would just fail against the missing scaffolding.

New e2e scenario on vite8-react-plain drives the full repro: cycle,
revert source externally, reload, assert self-discard, terminal durable
phase, and a working picker afterward.

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 15:57:33 -07:00
Paul BakausandClaude Code b9c1d86d68 fix: reject a valueless --target instead of falling back to implicit selection
A trailing --target, an empty --target=, or --target followed by another
flag used to degrade into implicit root selection, letting a mutating
helper (poll, accept, complete) act on the most recent live app instead
of the one the caller tried to name. consumeTargetArg now throws on those
shapes and enterLiveRoot exits with a clear error before any session
state can be touched. Unit tests cover the malformed shapes and a
subprocess test proves the helper body never runs.

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 15:57:33 -07:00
Paul BakausandClaude Code 39f233ac24 fix: hydrate attribute-bound each values and guard style directives
Addresses two cursor review findings:

- {#each} bodies whose bound values appear in attributes (href={link.href},
  src={item.img}) now record attr slots; the browser hydrates them from the
  rendered attribute so component previews no longer mount with empty links.
  Single-expression attributes hydrate exactly; mixed values stay unhydrated
  as before. A new slot classifier also refuses shapes that would crash a
  shallow hydration item (deep paths, method calls, bare item renders) and
  routes them to source-preview mode instead.
- Style directives now run the mixed loop/outer identifier check before the
  free-identifier param check, so style:width={base + r.pct} falls back
  instead of minting a broken param.

Tests: attr-slot analysis units, crashy/lossy fallback units, an attribute-
bound anchor in the stateful SvelteKit fixture asserted through accept, and
a mountedDomProbe e2e hook that reads the hydrated href off the mounted
variant DOM (verified to fail when hydration is disabled).

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 15:33:16 -07:00
Paul BakausandClaude Code 6997e4bdb5 fix: no unauthenticated path in live-server liveness
greptile-apps[bot]: the legacy fallback (server.json without port or
token) accepted a pid-only record on Windows without identity. Every
server.json this codebase has ever written records port and token, so a
record without them is malformed or foreign; it now classifies as not
live and resolution falls to the durable-session tier, the correct
recovery path for a crashed helper. The ps-based identity heuristic is
gone with it: authentication or nothing.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 15:03:10 -07:00
Paul BakausandClaude Code 16a84bc390 fix: authenticate the live-server liveness probe
greptile-apps[bot] escalated the identity ladder to a pid AND port both
coincidentally reused by different processes. The definitive terminator
was available all along: the helper serves an authenticated endpoint and
server.json records the token, so the probe now requires a 200 from
/status?token=... over HTTP. Nothing but our helper can answer that,
which closes the entire misidentification class rather than the next
rung. The regression test hosts its responder in a child process (the
probe is execFileSync, so a same-process responder can never accept
while the parent's event loop is blocked; production helpers are always
separate processes).

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 14:50:28 -07:00
Paul BakausandClaude Code 9a3f5aa34b fix: portable port probe for live-server liveness
greptile-apps[bot]: the win32 branch skipped the port probe entirely
(bash /dev/tcp is not portable), so a reused pid on Windows still
classified as a running helper. The probe is now a spawned node
one-liner that behaves identically on every platform, which also drops
the bash dependency for minimal Linux environments; the ps identity
check remains only for legacy server.json records without a port.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 14:33:11 -07:00
Paul BakausandClaude Code 24d69675e0 fix: mixed loop/outer expressions fall back; globals are neither free nor bound
cursor[bot]: an expression mixing loop bindings with outer free names
(fmt(r.label) where fmt lives in the route script) was left verbatim, so
the detached preview referenced an undeclared identifier and failed at
mount, past the compile gate, because globals make it legal to the
compiler. Such expressions now mark the analysis unsupported and the
session takes source-preview mode. A globals allowlist makes Math/JSON
and friends count as neither free nor bound, which also fixes a latent
bug where a pure-global expression minted a nonsense prop.

Won't-fix on the same pass: the live-setup.md filename cross-reference
matches the repo's established reference-link convention.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 14:22:57 -07:00
Paul BakausandClaude Code dc5420b64f fix: compile-check svelte variants at publish time
Field failure (Codex session, 2026-07-28): the agent kept the seeded
stub style block and appended its own second top-level style element in
all three variants. Svelte forbids that, so the user saw a red Vite
compile overlay; the mount-ack loop then self-healed (failure event,
repair, republish, clean accept), but the overlay window is exactly the
kind of thing the user should never see.

The publish gate closes the class: a done reply for a component session
now compile-checks every variant with the app's own compiler BEFORE the
revision bump and the browser broadcast. Failures bounce as a 422 with
file, line, and message plus _instructions; live-poll surfaces the
details in the thrown reply error. The browser never imports a variant
that cannot compile.

Also: the stub guard comments warn that all CSS belongs in the single
existing style block, worded to never contain the literal "<style"
sequence (a mention inside a CSS comment truncates the string surgery
agents use to find the block; the fake test agent caught exactly that).
The JIT svelte instructions carry the same warning.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-28 14:13:47 -07:00
Paul BakausandClaude Code da68678e7e fix: app discovery uses the same criterion as the upward walk
cursor[bot]: discoverAppCandidates only matched dev-config markers while
the upward walk also honors an existing .impeccable/live/config.json,
so booting from a repo root without --target missed a nested
live-configured static site and fell through to the wrong root. Both
paths now share isAppRoot; regression test covers the static-site shape.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 19:38:56 -07:00
Paul BakausandClaude Code 880199697e fix: Cursor notify pattern covers every dispatchable event type
cursor[bot]: the background-terminal notify regex predated
variant_mount_failed (and manual_edit_apply / prefetch), so on Cursor a
failed mount exited the one-shot poll without waking the agent and the
error card sat unanswered. The pattern now lists every type the
dispatch loop handles.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 19:32:30 -07:00
Paul BakausandClaude Code 7fa25da98e fix: probe the recorded port for live-server liveness
greptile-apps[bot] re-raised the residual with a repro: a stale
server.json pid reused by an unrelated node process passed the
command-name check. The decisive signal is the recorded PORT: a real
helper is listening on it, a pid squatter is not. hasLiveServer now
probes 127.0.0.1:<port> (bash /dev/tcp, sync, ~ms, win32-guarded with
the previous behavior); the multi-app preference test runs a real
listener instead of faking liveness with a bare pid.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 19:29:33 -07:00
Paul BakausandClaude Code 26f54d15c2 feat: just-in-time event instructions + frontier default for the LLM e2e agent
Field feedback from two more Codex sessions drove both changes.

JIT instructions (live/instructions.mjs): every event live-poll prints
now carries _instructions, the authoritative next step for that exact
situation with real ids, paths, and line numbers substituted, and only
the active path's rules (a svelte-component session never sees JSX
guidance). The boot payload carries loop instructions the same way.
Instructions are versioned with the scripts, so they cannot drift from
behavior, and live.md's plumbing can keep shrinking toward contract plus
craft guidance. The Codex poll-discipline failure observed in the field
("the long poll was started, but I yielded the task instead of actively
servicing its result") gets a named anti-pattern in both the harness
policy and the boot instructions.

LLM e2e agent: default provider/model moves from Claude Haiku 4.5 to
OpenAI gpt-5.6-terra at medium reasoning effort via an Anthropic-shaped
shim over the ai SDK (the three call sites stay provider-agnostic;
Anthropic and DeepSeek remain selectable). The harness should exercise
the model tier that actually drives live sessions. Both the react and
sveltekit fixtures pass end to end with terra driving the trimmed
live.md and the new _instructions.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 19:20:02 -07:00
Paul BakausandClaude Code b4f1c1786e docs: trim live.md hot path from 740 to 330 lines
First-time setup (config schema, framework table, adapters, drift, the
whole CSP flow) moves to reference/live-setup.md, loaded only when the
boot reports config_missing/config_invalid or cspChecked is absent.
The per-session prose is compressed without dropping any pinned phrase,
MUST rule, schema, or example; the boot payload documentation now names
the inlined surface brief. All live-reference pins and both prose gates
pass.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 19:03:39 -07:00
Paul BakausandClaude Code 5b6b331785 fix: preview-truth CSS supersession + cascade ordering on Svelte accept
Field failure from a real Codex session: accepting a variant into
Pitch.svelte appended 23 selectors and removed none, so the source's old
.decisions grid rules re-attached through the kept root class and forced
the accepted board into a stale three-column layout; some appended base
rules also landed after the source's media block, weakening the mobile
cascade.

Two mechanical fixes:
- Preview truth: the scaffolder records the seeded selectors (the source
  rules that styled the replaced selection, which the isolated preview
  never applied). On accept, any seeded selector the variant does not
  re-declare is removed; the selector-loss postcondition treats those
  removals like compiler prunes. A regression test reproduces the exact
  Pitch shape end to end.
- Cascade order: reconciliation inserts new base rules BEFORE existing
  top-level media blocks instead of appending after them.

Init-latency reductions from the same transcript:
- live.mjs inlines the resolved surface brief (removes three
  surface-brief.mjs round-trips including a --help miss before first poll).
- The wrap/scaffold payload carries componentStubMarkup, and live.md
  instructs editing stubs in place (the session read the manifest + stub
  back and then deleted/recreated the files).
- live.md notes that a busy default port usually means the dev server is
  already running (the session spawned a duplicate).

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 18:45:07 -07:00
Paul BakausandClaude Code 031e170d3e fix: harden live-server liveness against pid reuse
greptile-apps[bot] repro: a helper that died without removing
server.json leaves a pid the OS can hand to an unrelated process, which
kill(pid, 0) classifies as a running server and routes repo-root helpers
onto the stale app. The liveness check now also requires the pid's
command line to look like a node process (ps-based, platform-guarded),
removing reuse by arbitrary processes; the residual node-reuse case is
covered by the multi-app warning and the --target escape hatch.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 17:07:25 -07:00
Paul BakausandClaude Code 5a85050230 fix: give the nightly schedule its own CI concurrency group
cursor[bot]: the schedule run shared github.ref with pushes to main, so
cancel-in-progress let the nightly full matrix and a main push cancel
each other. Scheduled runs now use a dedicated group.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:55:18 -07:00
Paul BakausandClaude Code baed04a52b fix: helpers honor --target for multi-app disambiguation
greptile-apps[bot] repro: the multi-app warning recommended --target,
but the helper CLIs never parsed it, so live-poll --target appB still
re-anchored onto the pointer's first choice. enterLiveRoot now consumes
a --target argument (removing it from argv so downstream flag parsers
never see it) and resolves roots against it, making the documented
escape hatch real on every helper. Regression test drives a two-live-app
repo through a child process and asserts both the chdir target and the
argv scrubbing.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:52:01 -07:00
Paul BakausandClaude Code f1d450e6ab fix: sixth review round (verify precision, base-path @fs fallback)
cursor[bot]:
- verifyAcceptedSource anchors its param patterns to the exact shapes
  live mode writes (data-p-x= / [data-p-x] attributes, var(--p-x, ...)
  references) instead of bare prefixes, shrinking the false-positive
  class near the completion gate. Note: the reported examples (data-page,
  var(--primary)) did not actually match the previous hyphenated
  substrings; the tightening removes the residual class (e.g. a user's
  own data-p-* attribute) regardless.
- With a non-root Vite base, the /@fs/ fallback is tried both under the
  base and at the server root, covering Vite versions that serve @fs at
  either location.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:40:08 -07:00
Paul BakausandClaude Code e5f6d27a9c fix: fifth review round (durable mount failures, {#key} hydration slots)
cursor[bot]:
- variant_mount_failed now sets the session's pendingEvent (without
  clobbering a still-pending generate), so a helper restart replays it
  onto /poll and a repair --reply resolves instead of returning
  unknown_poll_reply_id. live-resume's next action names the real event
  id instead of a literal EVENT_ID placeholder.
- Contract v2 text hydration strips {#key} DELIMITERS from the zip
  source (content stays; it always renders), so key blocks can no longer
  shift expression slots against the live DOM.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:33:34 -07:00
Paul BakausandClaude Code 39df25ee5a fix: fourth review round (mount-failure truth, toggle baking, root ambiguity)
cursor[bot]:
- enqueueEvent dedupes variant_mount_failed per variant, so a second
  broken variant is no longer swallowed while the first is queued.
- Every component (re)injection resets the mount-failure dedupe, so a
  republish that is still broken at the same URL reports again instead
  of silently convincing the agent the repair landed.
- Toggle baking now mirrors preview truth exactly: the runtime sets
  data-p-<id>="on" or removes the attribute, so presence and "on" forms
  survive only while on, and any other valued branch (never matched at
  preview) is dropped in either state.

greptile-apps[bot] (both P1 repros):
- When several apps qualify at the same resolution tier (two live
  servers, or two stopped apps with interrupted sessions), the choice
  stays deterministic but is now loud: a stderr warning names the chosen
  app, the alternatives, and how to target a specific app. Silent
  wrong-app routing was the failure in both repro harnesses.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:22:09 -07:00
Paul BakausandClaude Code 40b2a80653 fix: restrict server-session adoption to comparison phases
The CI-only astro accept hang: the carbonize source edit triggers a
framework reload, and on a slow runner the reloaded page rehydrated the
still-non-terminal carbonize_required session back into GENERATING,
stranding the bar over a decided comparison. Adoption now uses a
positive allowlist of comparison phases (generate_requested,
variants_ready, generating, cycling); accept/carbonize/steer/manual
phases are agent-side work and never adoptable. Regression guard pins
the allowlist.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 16:11:52 -07:00
Paul BakausandClaude Code f27bea5bc0 fix: third review round + unmask and fix the astro-vite7 e2e failure
cursor[bot]:
- variant_mount_failed joins EVENT_TYPES_NEEDING_AGENT_REPLY so stream
  mode waits for the repair reply instead of moving on mid-lease.
- The fake agent's mount-failure repair no longer forces
  sourceEventType generate; the server maps the done reply onto the
  pending failure event, which acknowledges it instead of leaving it to
  be redelivered on every poll.

greptile-apps[bot]:
- With every helper server stopped, repo-root resolution now prefers the
  app whose durable store holds a non-terminal session (the interrupted
  session the user is recovering) over the most recent boot.

astro-vite7 (pre-existing CI failure, root-caused): Astro 7 auto-detects
AI-agent environments and daemonizes `astro dev`; the detached server
holds a lock, outlives the harness, squats dev ports across runs, and
makes the parent exit 0, which the harness read as a crash. The fixture
now sets ASTRO_DEV_BACKGROUND=1 (disables the agent detection) plus
--ignore-lock, and the harness supports per-fixture runtime.env. The
core cycle now passes for the first time; the missed-done recovery
scenario fails identically at origin/main with the daemon bypassed, so
it is marked as a per-scenario known limitation with that rationale.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 15:55:03 -07:00
Paul BakausandClaude Code a6f965e8bf fix: address second round of PR review bot findings
cursor[bot]:
- style: directives with dynamic values now fall back to source-preview
  instead of being scaffolded as boolean condition props that falsified
  the style in the detached preview.
- class: directives carry a className probe, so v2 hydration answers the
  condition from the live DOM instead of always defaulting to false.
- The existing-wrapper remount path now checks the mount result; a failed
  remount keeps the error card instead of advancing to a CYCLING bar over
  a page where nothing rendered.

greptile-apps[bot]:
- The repo-root live pointer records every booted app (most recent
  first) and resolution prefers the app whose helper server is alive, so
  a helper run from the repo root of a two-app monorepo can no longer be
  redirected onto the wrong app's session store by the last boot. Legacy
  single-value pointers still read.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 15:34:57 -07:00
Paul Bakaus 2d66c9acf1 Merge origin/main into live-v2-rewrite
Resolves bun.lock (regenerated) and package.json (both sides' devDependency
changes kept: main's @babel/parser bump, this branch's svelte addition).
2026-07-27 15:22:51 -07:00
Paul BakausandClaude Code 4ac54bebee fix: address PR review bot findings
cursor[bot] findings on #433:
- Nightly schedule no longer enables the paid opt-in suites: a schedule
  event has no diff base, so the change-detection fallback flagged every
  file-triggered suite, which would have billed the skill-behavior,
  accept-cleanup, and deepseek LLM suites nightly. The plan now pins the
  schedule event to deterministic suites plus the full live-e2e matrix,
  with a regression test.
- Dismissing the mount-error card no longer strands the session: while
  the bar is hidden in GENERATING the card is the only recovery surface,
  so dismiss now returns the state machine to PICKING (session and
  server truth survive for a later republish).

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 15:19:58 -07:00
github-actions[bot] d7d07cb0d6 Sync generated provider output 2026-07-27 22:14:03 +00:00
Paul BakausandClaude Fable 5 c9213835e7 Review fidelity against the comp itself, not the builder's summary of it
A codex run turned an approved comp into a related second art direction
and the finish reviewer passed it: the review anchored on the direction
contract, a lossy abstraction the builder wrote, and every element that
abstraction dropped passed silently. Four changes close that chain. The
reviewer inventories the comp's salient elements before reading the
contract and classifies each one (match, adaptation, missing,
contradicted, added without approval), with adaptations citing the
answer, brief, accessibility need, or product truth that forced them,
and fidelity failures outranking craft in material_fixes. The visualize
inventory gate records compositional commitments alongside asset media,
since the 150-word contract cannot carry them. The north-star allowance
now says what it permits: translation, never recomposition. And the
finish sequence recaptures the same viewports once after the fix batch,
so what the documenter records is what actually shipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:13:27 -07:00
Paul BakausandClaude Code 17dabf4b7e Live v2: root manifest, mount-ack protocol, AST scaffolder, mechanical accept
A ground-up hardening of live mode, driven by a production session in a
nested-app monorepo that hit six distinct failure classes. Full design
rationale in docs/LIVE-REWRITE-PLAN.md; every Codex-reported failure now
has a mechanical fix and a regression test.

Roots: live/roots.mjs resolves appRoot/repoRoot/contextRoot once at boot
(keyed on dev-server configs, not monorepo brand markers), persists a
manifest, and every live CLI re-anchors onto it at startup, so a helper
run from the wrong directory can no longer fork session state. Context
files are discovered upward to the git root.

Render truth: variant_mounted / variant_mount_failed events give the
journal per-variant mount state; failures reach the agent's poll queue,
raise a persistent error card with Retry (no more localStorage wipe), and
an attach probe names root/dev-server mismatches explicitly. The browser
rehydrates from the server when localStorage is gone.

Svelte: the scaffolder now parses with the app's own svelte 5 compiler.
Control flow survives (an each collection crosses the contract as one
structured prop), keyed each blocks hydrate synthetic keys, and anything
a detached preview cannot support falls back to source-preview instead of
shipping a wrong scaffold. Preview modules live in per-publish revision
directories, defeating stale transform caches.

Accept: CSS is reconciled, not appended. Matching selectors are replaced,
params bake from params.json kinds, the compiler's unused-selector pass
prunes superseded rules (pre-existing dead rules protected), a selector-
loss postcondition refuses any write that would drop hand-written rules,
and live-complete refuses to finish while live plumbing remains in source.

Also: framework registry (live/frameworks/) with a crash-safe injection
journal, session-store snapshot caching with read-only reads, protocol
enum consolidation, steer Send button, honest DESIGN-panel empty states.

Testing: new unit suites (roots, AST scaffolder, accept CSS, accept
pipeline, framework conformance); e2e now fails on preview-tree 404s,
proves computed-style mount for every variant, drives the Tune panel
through baked params, and injects failures (broken mounts, republish,
storage loss). New runtime fixtures: monorepo-nested-vite (repo root !=
app root) and vite8-sveltekit-stateful (each blocks + state). Nightly
full-matrix cron. An independent adversarial review pass preceded this
commit; its blocker and major findings are fixed and regression-tested.

This work was produced with AI assistance (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-27 15:09:40 -07:00
github-actions[bot] d52077414c Sync generated provider output 2026-07-27 21:50:35 +00:00
Paul BakausandClaude Fable 5 9e4990765f Give the reading-heavy subagents turn budgets that survive their inputs
A finish review reads the artifact, two full-page screenshots, the
approved comp, the quality-bar cards, and the contract before it may
write a word; at max-turns 12 the SDK reaps it mid-read and the parent
receives the opening sentence as the whole review. Observed twice in a
row (spawn and respawn) on the first real subagent run. The documenter
reads at least as much, and the asset producer pays per asset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:50:03 -07:00
dependabot[bot]andGitHub e0144ed585 Bump the bun-minor-and-patch group with 9 updates (#429)
Prepared with AI assistance from OpenAI Codex under maintainer automation instructions.
2026-07-27 10:24:55 -07:00
github-actions[bot] 839dd10079 Sync generated provider output 2026-07-27 17:07:25 +00:00
Paul BakausandGitHub 9b613ef931 Merge pull request #419 from pbakaus/diff-base-detection
Detect the diff base in context-signals instead of assuming main/master
2026-07-27 10:06:48 -07:00
Paul BakausandClaude Code 01d5d357c5 The develop candidate leads with an advertised develop default rev
Round eight closes the stale-local class completely: the develop
candidate sits before the remote-default entries, so when origin/HEAD
itself points at develop, its name claim let a stale local develop win
over the fresher origin/develop. The candidate now leads with any
remote-advertised develop rev, exactly as the remote-default and
upstream candidates already lead with theirs. main/master were already
covered since their remote-default entries come first in the order.
Failing-first test forces local develop two commits behind.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 19:04:48 -07:00
Paul BakausandClaude Code e2c1c43ee7 Remote defaults lead with their own rev, like upstreams already do
Round seven: a remote-advertised default candidate tried the local
branch first, so a stale local main outranked the fresher origin/main
the symref points at and refilled changedFiles with the divergence.
The candidate now leads with the advertised remote rev, mirroring the
upstream candidate's reasoning. Failing-first test: local main forced
two commits behind the remote default, feature delta stays clean.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:56:33 -07:00
Paul BakausandClaude Code a470fc777a Read the upstream as a full symbolic ref instead of guessing at prefixes
Round six, and the upstream-parsing ambiguity dies at the root: @{u} is
now resolved via rev-parse --symbolic-full-name, where refs/heads/...
IS a local upstream and refs/remotes/<r>/... IS remote-tracking. The
previous remote-membership heuristic still misread a local feature/foo
upstream when a remote literally named "feature" existed. The
adversarial test now configures exactly that remote and passes.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:48:14 -07:00
Paul BakausandClaude Code f89b6c10b1 Only strip a remote prefix that names a configured remote
Round-five bot findings, one real root cause: splitRemoteRef treated the
first slash in any ref as a remote separator. A local upstream named
release/2.0 was truncated to "2.0", and feature/foo tracking from branch
foo collapsed to the current branch's own name and was self-skipped,
discarding a valid base both times.

The split now happens only when the prefix names a configured remote;
otherwise the whole ref is one local branch name. The per-remote HEAD
symref loop strips its own queried prefix directly (that remote may be
fabricated in tests or partial clones without appearing in git remote).
The reported pruned-upstream shape already resolves via the multi-remote
rev lists from the previous round; its test now guards that.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:39:20 -07:00
Paul BakausandClaude Code 386d3e7051 Cover every remote in each candidate's rev list
Cursor and Greptile converged on one root cause from the previous round:
candidate revs stopped at origin (develop tried only develop and
origin/develop; a remote-default entry carried only its own rev), so the
name-level dedup discarded a same-name base living on another remote. A
fork-parent layout with develop only as upstream/develop, or a pruned
origin/main beside a live upstream/main, lost its base entirely.

revsFor(name) now expands to the local branch plus <remote>/<name> for
every remote (origin first), and all named candidates use it, which is
exactly what makes the dedup safe. Two failing-first tests cover the
upstream-only develop and the pruned-origin/live-upstream main shapes.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:23:56 -07:00
Paul BakausandClaude Code 46f29ca8b3 Guard detached HEADs and non-origin remote defaults
Two more real gaps from the post-rebase review round: a detached
checkout reads its branch as the literal HEAD, so the integration guard
never fired and candidate selection could diff a detached tip on main
against develop; and the remote-default check only consulted origin, so
a fork-parent layout whose only remote is upstream lost the guard on
its default branch entirely.

The guard now treats a detached HEAD as no-diff-base, and default-branch
symrefs are collected from every remote (origin first), feeding both the
guard and the candidate list. Two failing-first tests cover a detached
tip beside a diverged develop and an upstream-only trunk default.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-26 18:14:34 -07:00
Paul BakausandGitHub 5e572c8b8a Merge pull request #423 from pbakaus/hook-guard-unsupported-node
Stop the design hook erroring on a node too old for ESM
2026-07-26 18:12:52 -07:00
Paul BakausandGitHub 6ce0f94298 Merge pull request #421 from pbakaus/doctor-test-rm-retries
Retry the doctor-test scratch cleanup to kill a Node 22 CI flake
2026-07-26 18:10:48 -07:00
Paul BakausandGitHub 46e759b4db Merge pull request #420 from pbakaus/sync-output-push-retry
Sync workflow: retry the generated-output push when main advances mid-sync
2026-07-26 18:10:00 -07:00
github-actions[bot] 7380ecb153 Sync generated provider output 2026-07-27 01:09:00 +00:00
Paul BakausandGitHub cdcce9116e Merge pull request #418 from pbakaus/accept-failure-recovery
Live mode: recognize a late accept failure after the optimistic teardown
2026-07-26 18:08:30 -07:00
fd9076f4f0 Enforce the engines floor in the probe instead of a capability check
The probe asked whether node could load ESM, while the notice promised a
Node 22 floor and package.json engines declares >=22.12.0. Reviewers kept
flagging the gap, and they were right to: a 14.18-to-21 runtime passed the
probe on the strength of one import while the hook and its detector bundle
are only ever exercised on the engines floor, so "can load our code" was a
weaker claim than the one being made for it.

Check the floor directly: parseInt(process.versions.node) >= 22, in
ES5-only syntax that parses on any node old enough to fail it. Probe and
notice now derive from one NODE_MAJOR_FLOOR constant, so they cannot
disagree, and the archaeology about node: scheme support and pre-15
unhandled-rejection semantics goes with the import it explained.

Add the missing contract test: every generated hook command carries the
probe, the notice appears exactly where a harness can render it (Claude
and Codex, project and plugin), and the expected floor is read from
package.json engines rather than repeated by hand.

Verified against a fake pre-22 node, no node, and a real node: one notice
then the marker holds it silent, exit 0 in every failure shape, and the
hook's own exit code still passes through on a supported runtime.

Co-Authored-By: Claude Fable 5 (via Cursor) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 22:17:19 +05:00
Abdul WahabandClaude Opus 5 86cdf528c5 Probe the import the hook actually uses, and fail closed on rejection
Greptile flagged that the probe does not enforce the Node 22 engines floor.
Two parts to that, and they land differently.

The real defect is narrower and worse than stated: the hook closure imports
`node:fs`, `node:os`, `node:path` and `node:url`, and the `node:` scheme needs
14.18, so a bare `import('fs')` probe passed on 12 and 13 and those runtimes
then died on the real import, which is the banner this branch exists to remove.
Probing `node:fs` closes that. The added `.catch(()=>process.exit(1))` is load
bearing rather than tidiness: before Node 15 an unhandled rejection is only a
warning and the process still exits 0, so a rejected probe would have read as a
pass on exactly the versions in question.

Not enforcing 22 is deliberate and stays. The probe asks whether this runtime
can load our code, not whether it is a supported one, so a 14.18-to-21 runtime
that works today keeps working rather than being silently switched off. The
notice names 22 because that is the version worth installing, and it only ever
reaches someone whose runtime already failed the probe, so no user is shown a
threshold that contradicts what ran.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 21:20:26 +05:00
Abdul WahabandClaude Opus 5 4f999ceff8 Give Codex the notice too; its hook reference documents systemMessage
Commit 8397d532 took a reviewer's word that Codex expects hookSpecificOutput
and dropped its notice on that basis. Codex documents `systemMessage` for
PostToolUse and Stop as text shown as a warning in the UI or event stream,
the same field Claude Code reads, so the notice belongs there and the earlier
comment asserted something unverified.

Checked the rest against their own references while here. Cursor's preToolUse
output is permission-shaped and its user_message renders only when the action
is DENIED, so warning would mean blocking the edit. Grok treats PostToolUse
and Stop as passive events and ignores stdout outright. Copilot's contract is
unconfirmed. Those three keep the probe alone, which is a verified limit now
rather than an assumption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 21:10:41 +05:00