mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 00:26:41 +03:00
Preserve experimental Live app-server workstream
Snapshot the current app-server implementation, shared Live optimizations, generated harness output, and in-progress site work before restoring polling as the primary runtime path. Prepared with Codex assistance under maintainer direction.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Live generation worker
|
||||
|
||||
You generate reviewable variants for an existing interface. The supervisor owns all filesystem writes, publication, cancellation, and recovery. Return only the requested structured output.
|
||||
|
||||
## Identity and quality
|
||||
|
||||
- Preserve the existing product identity by default: palette roles, available fonts, component roles, copy, semantics, accessibility, and public APIs.
|
||||
- Treat DESIGN.md as visual authority and PRODUCT.md as strategy/voice authority.
|
||||
- Define one shared identity lock and distinct design axes before authoring the first variant.
|
||||
- Make each variant independently shippable. Vary hierarchy, topology, typography, color commitment, density, or structural decomposition, not arbitrary decoration.
|
||||
- Preserve short labels as readable units and avoid unnecessary wrapping at the supplied viewport.
|
||||
- Prefer hierarchy, proportion, rhythm, and composition before adding nested chrome.
|
||||
- Silently reject overflow, awkward wrapping, accidental compression, weak alignment, inaccessible states, and off-brand component treatments.
|
||||
|
||||
## Authoring contract
|
||||
|
||||
- The selected root is a complete replacement with exactly one top-level element.
|
||||
- Preserve copy and dynamic relationships unless the user explicitly requests content changes.
|
||||
- Never emit `data-impeccable-*` wrappers inside variant markup.
|
||||
- Follow `event.scaffold.cssAuthoring` exactly. Fence every preview selector to its variant.
|
||||
- Do not write source or project files. Return only paths and content permitted by the current output schema.
|
||||
- Published variants are immutable. Never repeat or revise an earlier variant in a later phase.
|
||||
- The staged artifact identifies the exact selected page/component. Inspect its real imports, shared layouts, styles, tokens, and route ownership with read-only tools whenever needed; do not assume a single-page project or guess from filenames.
|
||||
|
||||
## Progressive phases
|
||||
|
||||
- `first`: return variant 1 and the complete coherent plan. Defer parameters.
|
||||
- `remainder`: return variants 2 through N together, following the stored plan, plus final parameter wiring CSS and the manifest for every variant. Do not change variant 1 or any default appearance.
|
||||
- `params`: recovery only when all variants were durably published but their parameters were not. Return only parameter wiring CSS and the manifest.
|
||||
- Parameters are coarse, meaningful axes already present in the designs. Tiny elements may have none; larger compositions usually expose two or three. Never exceed four per variant.
|
||||
|
||||
The supervisor runs the Impeccable detector before publication. On a repair turn, use judgment on every finding: fix real defects, but preserve contextually intentional design and detector false positives by returning the narrow `detectorWaivers` entry requested by the repair schema with a concrete reason. Never persist project ignore config or add inline ignore comments from this read-only worker. Publication proceeds only when every new finding was fixed or explicitly waived.
|
||||
+14
-13
@@ -14,26 +14,26 @@ Execute in order. No step skipped, no step reordered.
|
||||
|
||||
1. `live.mjs`: boot. If the request names or implies a file, route, or app inside a monorepo, infer the concrete path and run `node {{scripts_path}}/live.mjs --target <path>` instead; then run the rest of this live session from the returned `projectRoot`.
|
||||
2. Open the app URL that serves `pageFile` (infer from `package.json`, docs, terminal output, or an open tab). Never use `serverPort`; it's the helper, not the app. **Cursor:** `browser_navigate` to that URL before polling; do not skip. **Other harnesses:** use the available browser tool; if the URL is uncertain, ask the user once.
|
||||
3. Poll loop with the default long timeout (600000 ms). After every event or `--reply`, run `live-poll.mjs` again immediately. Never pass a short `--timeout=`.
|
||||
3. Poll loop with the default long timeout (600000 ms). Portable harnesses run `live-poll.mjs` again immediately after every event or `--reply`. Codex with the dedicated worker keeps the returned `--stream` control command alive instead. Never pass a short `--timeout=`.
|
||||
|
||||
The global bar **Impeccable mark** dims and shows a pulsing amber dot when no agent is long-polling `/poll`. Hover the mark for the hint; restart `live-poll.mjs` to reconnect.
|
||||
4. On `generate`: reuse `event.scaffold` when present; read the screenshot if present; load the action's reference; plan three distinct directions; deliver variants using the harness policy below; `--reply done`; poll again.
|
||||
5. On `steer`: read the message and `pageUrl`; do the work (page edits, navigation help, or a short reply in the `--reply` message); `--reply steer_done`; poll again. No pickup ack. The Steer bar unlocks when `steer_done` arrives over SSE.
|
||||
6. On `accept` / `discard`: the poll script runs `live-accept.mjs`, acknowledges the delivered event, and prints `_completionAck`. Plain accepts/discards are terminal immediately. Carbonize accepts remain recoverable until a cleanup owner runs `live-complete.mjs --id EVENT_ID`; Codex delegates that cleanup and resumes the foreground poll immediately, while synchronous harnesses finish cleanup before polling again.
|
||||
6. On `accept` / `discard`: the poll script runs `live-accept.mjs`, acknowledges the delivered event, and prints `_completionAck`. Plain accepts/discards are terminal immediately. Carbonize accepts remain recoverable until the foreground control task runs `live-complete.mjs --id EVENT_ID`; the Codex control stream waits for that event reply without exiting, while synchronous harnesses finish cleanup before polling again.
|
||||
7. If interrupted, run `live-status.mjs` or `live-resume.mjs` before guessing. The durable journal replays unacknowledged work after helper restart.
|
||||
8. On `exit`: run the cleanup at the bottom.
|
||||
|
||||
Harness policy:
|
||||
- **Claude Code**: run the poll as a **background task** (no short timeout). The harness notifies you when it completes, so the main conversation stays free. Do not block the shell.
|
||||
- **Cursor**: run **one-shot** poll in a **background terminal** with notify on `"type":"(steer|generate|accept|discard|exit)"`. After each event the poll exits; handle it, `--reply`, then start `live-poll.mjs` again. Do **not** use `--stream` on Cursor: incremental stdout notify is slower in practice than exit-based notify (~5s vs sub-second in testing).
|
||||
- **Codex**: `live.mjs` starts a dedicated app-server generation lane by default and returns its exact foreground control command. Run that command in a yielded foreground exec session; do not suffix it with `&`. It dynamically adds generation events only if the dedicated worker fails or exits. When the worker is explicitly disabled or unavailable, keep the default poll in the main thread and delegate to the low-effort `impeccable_live_generator` agent with a compact handoff. Do not paste this full reference into the handoff. Never run an overlapping unfiltered poll while the dedicated worker owns generation.
|
||||
- **Codex**: `live.mjs` starts a dedicated app-server generation lane by default and returns its exact persistent foreground control command. Run that `--stream` command once in a yielded foreground exec session; do not suffix it with `&`, restart it after each event, or launch an overlapping poll. The yielded shell remains alive while the model handles Steer, manual Apply, or carbonize through separate tool calls; after the model posts `--reply`, the same stream resumes polling automatically. Keep the task open until Live exits. The stream dynamically adds generation events only if the dedicated worker fails or exits. When the worker is explicitly disabled or unavailable, preserve the portable one-shot foreground path and delegate to the low-effort `impeccable_live_generator` agent with a compact handoff. Do not paste this full reference into the handoff.
|
||||
- **Other harnesses**: one-shot foreground unless you know stdout reliably returns to this session when a shell exits.
|
||||
|
||||
Generation delivery policy:
|
||||
- **Default (Claude Code, Cursor, and other harnesses):** keep the established atomic single-edit delivery unless that harness has independently demonstrated that progressive tool calls are faster and reliable. This avoids trading model latency for extra tool-call latency on harnesses with different streaming behavior.
|
||||
|
||||
<codex>
|
||||
- **Codex progressive override:** deliver progressively through `live-publish.mjs`, never by editing project source directly. Publish variant 1 as soon as it is complete, then publish each additional validated variant (or the largest ready prefix) without waiting for later siblings. Attach parameter manifests only with the final set. The browser makes every arrived variant immediately reviewable and acceptable; Accept/Discard durably cancel unfinished revisions.
|
||||
- **Codex progressive override:** deliver progressively through `live-publish.mjs`, never by editing project source directly. Publish variant 1 as soon as it is complete, then publish variants 2 through N and all parameter CSS/manifests together from the second design turn. The browser makes every arrived variant immediately reviewable and acceptable; Accept/Discard durably cancel unfinished revisions.
|
||||
</codex>
|
||||
|
||||
Chat is overhead. No recap, no tutorial output, no pasting PRODUCT / DESIGN bodies. Spend tokens on tools and edits; on failure, one or two short sentences.
|
||||
@@ -46,7 +46,7 @@ node {{scripts_path}}/live.mjs
|
||||
|
||||
Output JSON: `{ ok, serverPort, serverToken, pageFiles, hasProduct, product, productPath, hasDesign, design, designPath }`. `pageFiles` is the list of HTML entries the live script was injected into. Keep PRODUCT.md and DESIGN.md in mind for variant generation; **DESIGN.md wins on visual decisions; PRODUCT.md wins on strategic/voice decisions.** When DESIGN.md is missing, identity is **not** absent; extract it from CSS variables, computed styles, and sibling components on the page (see Step 4 Phase A). Identity preservation is the default; departure from existing identity requires an explicit trigger from PRODUCT.md anti-references or the user's freeform prompt.
|
||||
|
||||
If output includes `codexWorker.enabled: true`, run the returned `codexWorker.foregroundPoll` command. The dedicated lane owns `generate,accept,discard,prefetch`; the foreground owns `steer,manual_edit_apply,carbonize_cleanup,exit`. The fallback flag restores generation to the foreground only when the worker's owned process record is failed or unreachable. After each event, restart that same command. Do not also run the default unfiltered poll.
|
||||
If output includes `codexWorker.enabled: true`, run the returned `codexWorker.foregroundPoll` command once and keep its yielded exec session alive. The dedicated lane owns `generate,accept,discard,prefetch`; the foreground owns `steer,manual_edit_apply,carbonize_cleanup,exit`. The fallback flag restores generation to the foreground only when the worker's owned process record is failed or unreachable. When a control event is printed, handle it through separate tool calls and post its reply; the stream waits for that acknowledgement and then resumes automatically. The browser reports foreground availability while this stream is polling or waiting for its leased control event acknowledgement. Do not start another poll, and do not end the task with a final response until Live exits.
|
||||
|
||||
If output includes `codexWorker.error: "codex_cli_unavailable"`, tell the user once that Live is using foreground generation, then run the returned unfiltered `codexWorker.foregroundPoll`. Do not retry or install anything during the session. The browser mark carries a static status dot and explains that installing Codex CLI, running `codex login`, and restarting Live enables background variants.
|
||||
|
||||
@@ -123,14 +123,14 @@ Activation is process-local: the worker is enabled by default only when the proc
|
||||
The app-server worker is **default-on in Codex and Codex-only**. Claude, Gemini, Cursor, and every other harness keep the portable foreground/atomic behavior. Before detaching anything, Live resolves the configured Codex executable using the same explicit-path/PATH rules as Node spawn. A missing CLI becomes an immediate, durable foreground fallback with setup guidance instead of a misleading prewarm state. Otherwise Live records the worker as `starting` and returns immediately, so app-server initialization overlaps page/dev-server startup. Run only the returned foreground control poll. It checks the owned worker process every two seconds and safely restores generation/accept/discard leasing if startup, authentication, model selection, or the worker process fails. Dedicated-worker leases expire after 15 seconds, so a hard process loss cannot strand browser work behind the portable ten-minute lease.
|
||||
|
||||
```bash
|
||||
node {{scripts_path}}/live-poll.mjs --types=steer,manual_edit_apply,carbonize_cleanup,exit --codex-worker-fallback
|
||||
node {{scripts_path}}/live-poll.mjs --stream --types=steer,manual_edit_apply,carbonize_cleanup,exit --codex-worker-fallback
|
||||
```
|
||||
|
||||
The supervisor launches its own `codex app-server --stdio` process, dynamically prefers the strongest visible general model (currently GPT-5.6 Sol), and uses medium reasoning. The optional `fast` profile retains Spark/mini selection and low reasoning for controlled comparisons. It creates a dedicated Impeccable-owned thread and persists only that id in `.impeccable/live/codex-worker.json`; it never lists, resumes, steers, or writes to the desktop task. A crash reconnect may resume that id only when the ownership marker and project cwd both match. Clean Live exit interrupts the active turn, archives the dedicated thread, and stops app-server.
|
||||
|
||||
Each generation turn attaches the installed Impeccable skill as a native app-server skill input, includes the selected sub-command reference, resolves inherited/monorepo PRODUCT.md and DESIGN.md through the same context loader as the foreground skill, and supplies a bounded active-source/token/component neighborhood. Annotated requests attach `screenshotPath` as a real high-detail local image instead of a JSON path. Context is rebuilt before every progressive phase, so an accepted edit or external source change is visible to the next request without restarting the worker.
|
||||
The first generation turn in a worker task attaches the installed Impeccable skill as a native app-server skill input and resolves inherited/monorepo PRODUCT.md and DESIGN.md through the same context loader as the foreground skill. Each generation supplies the exact selected source artifact, event, scaffold, page URL, and action reference. The persistent read-only thread decides which imports, route layouts, styles, tokens, or shared components it needs to inspect; no lexical source-neighborhood heuristic stands in for repository understanding. Annotated requests attach `screenshotPath` as a real high-detail local image instead of a JSON path.
|
||||
|
||||
Model turns run read-only and return structured staged-artifact files. The supervisor validates their paths, writes only under `.impeccable/live/artifacts/`, and publishes exclusively through the generation publisher's epoch/source-hash/immutable-prefix fence. Progressive variant 1 is immediately reviewable; the final turn cannot rewrite its source, CSS, or component file. Accept/Discard interrupts the active app-server turn, while the durable generation fence rejects any late completion that still races cancellation.
|
||||
One persistent app-server thread performs both normal generation turns so identity, repository discoveries, the variant plan, and skill guidance remain coherent. Model turns run read-only and return structured staged-artifact files. Before publication, the supervisor runs the Impeccable detector against the staged candidate, compares it with the pre-existing baseline, and asks the same thread for one repair when new findings appear. The repair must fix real defects or explicitly classify contextually intentional/false-positive findings with narrow, reasoned per-candidate waivers; only findings left neither fixed nor waived block publication. Existing project detector ignores are honored by the scan, while the read-only worker never persists new config or inline suppressions. The supervisor then validates paths and publishes exclusively through the generation publisher's epoch/source-hash/immutable-prefix fence. Source-wrapper sessions use an isolated preview under `.impeccable/live/previews/`; the true source stays byte-identical until Accept. Progressive variant 1 is immediately reviewable; variants 2 through N and their parameters arrive together from turn two. Accept/Discard interrupts the active app-server turn, while the durable generation fence rejects any late completion that still races cancellation.
|
||||
|
||||
Controls:
|
||||
|
||||
@@ -368,7 +368,7 @@ Colocate preview CSS as a `<style>` tag inside the variant wrapper; `<style>` wo
|
||||
node .agents/skills/impeccable/scripts/live-publish.mjs --prepare --id EVENT_ID --file SOURCE_FILE
|
||||
```
|
||||
|
||||
The JSON result contains `artifactFile`, `epoch`, and `expectedSourceHash`. For the normal source-wrapper path, edit **only `artifactFile`** at `insertLine`: write variant 1 and only the CSS it needs. Do not attach `data-impeccable-params` yet.
|
||||
The JSON result contains `artifactFile`, `epoch`, and `expectedSourceHash`. For the normal source-wrapper path, the live scaffold is an isolated `source-artifact` preview under `.impeccable/live/previews/`; edit **only `artifactFile`** at `insertLine`: write variant 1 and only the CSS it needs. Do not attach `data-impeccable-params` yet. The true source is only the publisher's hash fence and must remain byte-identical until Accept.
|
||||
|
||||
For `previewMode: "svelte-component"` or `"vue-component"`, `artifactFile` is an isolated manifest and `componentDir` is its isolated component directory. Write `v1.svelte` or `v1.vue` under the returned `componentDir`, set the artifact manifest's `arrivedVariants` to `1`, and leave `params.json` absent. Keep `--file` pointed at the original live manifest on publish; the publisher fences against `targetSourceFile`, promotes the component, then commits the live manifest last. Never edit the live `componentDir` directly.
|
||||
3. Publish revision 1 with the exact fence values returned by `--prepare`:
|
||||
@@ -380,8 +380,9 @@ node .agents/skills/impeccable/scripts/live-publish.mjs --id EVENT_ID --epoch EP
|
||||
```
|
||||
|
||||
`{ok:false,error:"stale_generation_epoch"}` means the user already accepted or discarded. Stop immediately, do not touch source, and post the generation reply as canceled/error.
|
||||
4. Continue with variants 2 and 3. Whenever another variant validates, run `--prepare` again so the next revision starts from the published prefix, add only the newly ready variant(s), then publish with `--arrived READY_COUNT`. Never hold variant 2 merely because variant 3 is unfinished. Attach parameter manifests for every variant only when `READY_COUNT === EVENT_COUNT`. On component-preview paths, preserve every already-published `vN.svelte` / `vN.vue` byte-for-byte; publication rejects a revision that silently changes a variant the user may already be reviewing.
|
||||
5. Verify the published source parses, then `--reply done`. A late reply is diagnostic only after Accept/Discard and cannot move the durable session backward.
|
||||
4. In the same persistent thread, generate variants 2 through `EVENT_COUNT` together from the stored plan plus parameter wiring/manifests for every variant. Run `--prepare` again so the revision starts from the immutable published prefix, add the complete remaining set and parameters without changing variant 1 or any default appearance, then publish with `--arrived EVENT_COUNT --kind params`. On component-preview paths, preserve every already-published `vN.svelte` / `vN.vue` byte-for-byte; publication rejects a revision that silently changes a variant the user may already be reviewing.
|
||||
5. A params-only turn is recovery-only: use it when durable state says every variant arrived but `paramsPublished` is still false after an interrupted publication.
|
||||
6. Verify the published preview parses, then `--reply done`. A late reply is diagnostic only after Accept/Discard and cannot move the durable session backward.
|
||||
</codex>
|
||||
|
||||
Use the `cssAuthoring` object returned by `live-wrap.mjs` to author the temporary preview CSS. The style opening tag shown below is the common case; replace it with `cssAuthoring.styleTag` when the tool returns a different one. The variant markup shape is otherwise stable:
|
||||
@@ -557,7 +558,7 @@ Event: `{id, variantId, _acceptResult, _completionAck}`. The poll script already
|
||||
|
||||
When `_acceptResult.carbonize === true`, the accepted variant was stitched into source with helper markers and inline CSS so the browser can render it immediately with no visual gap. That stitch-in is **temporary**. The agent must rewrite it into permanent form before doing anything else. Skipping this leaves dead `@scope` rules for unaccepted variants, a pointless `data-impeccable-variant` wrapper, and `impeccable-carbonize-start/end` comment noise in the source file; all of which accumulate across sessions.
|
||||
|
||||
**Codex:** hand these five steps to the session's generation worker (or a dedicated cleanup worker) and restart the foreground poll immediately. The cleanup worker must not poll. It owns the source cleanup, validation, and final `live-complete.mjs --id SESSION_ID`. Track it by source file. A later Generate may be leased and planned while cleanup runs, but it must not publish against the old source revision: wait for cleanup or rerun publisher `--prepare` after a stale-source rejection. The source lock, generation epoch, and expected-source hash are the final safety gates.
|
||||
**Codex:** the persistent foreground control stream prints the cleanup event and then waits for its acknowledgement; this does not block the model. Leave that yielded exec session running, perform these five steps through separate tool calls in the main task, run `live-complete.mjs --id SESSION_ID`, and post the event reply. The same stream resumes polling automatically after the acknowledgement. A later Generate cannot be leased by this waiting control stream; the source lock, generation epoch, and expected-source hash remain the final safety gates.
|
||||
|
||||
**Other harnesses:** unless an equivalent independently supervised cleanup worker is proven, do these five steps synchronously before the next poll.
|
||||
|
||||
@@ -569,7 +570,7 @@ When `_acceptResult.carbonize === true`, the accepted variant was stitched into
|
||||
|
||||
After the file is clean, the cleanup owner runs `live-complete.mjs --id SESSION_ID` and verifies `phase: "completed"`. The Codex supervisor keeps polling throughout; synchronous harnesses poll again only after that verification.
|
||||
|
||||
With the dedicated worker, Accept emits a foreground `carbonize_cleanup` control event: `{id, sessionId, file, variantId, acceptResult}`. Perform the same five steps above for `sessionId`, run `live-complete.mjs --id SESSION_ID`, then acknowledge the control event with `live-poll.mjs --reply EVENT_ID complete --file FILE`. Restart the returned control poll immediately. The dedicated generation lane may prepare the next request concurrently, but its publisher must re-prepare after any stale source fence.
|
||||
With the dedicated worker, Accept emits a foreground `carbonize_cleanup` control event: `{id, sessionId, file, variantId, acceptResult}`. Perform the same five steps above for `sessionId`, run `live-complete.mjs --id SESSION_ID`, then acknowledge the control event with `live-poll.mjs --reply EVENT_ID complete --file FILE`. Do not restart the control poll; the existing stream resumes after this reply.
|
||||
|
||||
## Handle `discard`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user