From bbed6eef08e69de6fbdef8d39167d8a5114fce2d Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Wed, 15 Jul 2026 23:29:47 -0700 Subject: [PATCH] Refresh the Impeccable product experience Rework the landing page proof, steering demo, feature grid, slop catalog, detector coverage, theming, Live workflow, and responsive behavior.\n\nAI-assisted implementation by OpenAI Codex. --- .agents/skills/impeccable/SKILL.md | 2 +- .../agents/impeccable_live_generator.toml | 46 - .../impeccable/reference/live-generation.md | 32 - .agents/skills/impeccable/reference/live.md | 118 +- .../skills/impeccable/reference/new-work.md | 64 +- .agents/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- .agents/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .claude/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .claude/skills/impeccable/reference/live.md | 91 +- .../skills/impeccable/reference/new-work.md | 64 +- .claude/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- .claude/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .cursor/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .cursor/skills/impeccable/reference/live.md | 91 +- .../skills/impeccable/reference/new-work.md | 64 +- .cursor/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- .cursor/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .gemini/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .gemini/skills/impeccable/reference/live.md | 91 +- .../skills/impeccable/reference/new-work.md | 64 +- .gemini/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- .gemini/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .github/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .github/skills/impeccable/reference/live.md | 91 +- .../skills/impeccable/reference/new-work.md | 64 +- .github/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- .github/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .impeccable/design.json | 20 +- .kiro/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .kiro/skills/impeccable/reference/live.md | 91 +- .kiro/skills/impeccable/reference/new-work.md | 64 +- .kiro/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .kiro/skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .kiro/skills/impeccable/scripts/live-wrap.mjs | 110 +- .kiro/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .opencode/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .opencode/skills/impeccable/reference/live.md | 91 +- .../skills/impeccable/reference/new-work.md | 64 +- .../skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- .opencode/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .pi/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .pi/skills/impeccable/reference/live.md | 91 +- .pi/skills/impeccable/reference/new-work.md | 64 +- .pi/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .pi/skills/impeccable/scripts/live-accept.mjs | 262 +-- .pi/skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .pi/skills/impeccable/scripts/live-inject.mjs | 151 +- .pi/skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .pi/skills/impeccable/scripts/live-server.mjs | 335 +-- .pi/skills/impeccable/scripts/live-status.mjs | 18 +- .pi/skills/impeccable/scripts/live-wrap.mjs | 110 +- .pi/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .qoder/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .qoder/skills/impeccable/reference/live.md | 91 +- .../skills/impeccable/reference/new-work.md | 64 +- .qoder/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- .qoder/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .rovodev/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .rovodev/skills/impeccable/reference/live.md | 91 +- .../skills/impeccable/reference/new-work.md | 64 +- .../skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- .rovodev/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .trae-cn/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .trae-cn/skills/impeccable/reference/live.md | 91 +- .../skills/impeccable/reference/new-work.md | 64 +- .../skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- .trae-cn/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- .trae/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - .trae/skills/impeccable/reference/live.md | 91 +- .trae/skills/impeccable/reference/new-work.md | 64 +- .trae/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .trae/skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .trae/skills/impeccable/scripts/live-wrap.mjs | 110 +- .trae/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- DESIGN.md | 5 +- cli/engine/detect-antipatterns-browser.js | 34 +- cli/engine/engines/regex/detect-text.mjs | 30 +- cli/engine/rules/checks.mjs | 34 +- docs/live-performance.md | 108 - package.json | 4 - plugin/skills/impeccable/SKILL.md | 2 +- .../impeccable/reference/live-generation.md | 32 - plugin/skills/impeccable/reference/live.md | 91 +- .../skills/impeccable/reference/new-work.md | 64 +- plugin/skills/impeccable/scripts/context.mjs | 141 +- .../detector/detect-antipatterns-browser.js | 34 +- .../detector/engines/regex/detect-text.mjs | 30 +- .../scripts/detector/rules/checks.mjs | 34 +- .../scripts/lib/impeccable-paths.mjs | 4 - .../skills/impeccable/scripts/live-accept.mjs | 262 +-- .../skills/impeccable/scripts/live-browser.js | 517 +---- .../impeccable/scripts/live-codex-worker.mjs | 292 --- .../skills/impeccable/scripts/live-inject.mjs | 151 +- .../skills/impeccable/scripts/live-poll.mjs | 97 +- .../impeccable/scripts/live-publish.mjs | 37 - .../skills/impeccable/scripts/live-server.mjs | 335 +-- .../skills/impeccable/scripts/live-status.mjs | 18 +- .../skills/impeccable/scripts/live-wrap.mjs | 110 +- plugin/skills/impeccable/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- .../impeccable/scripts/live/codex-worker.mjs | 979 -------- .../scripts/live/event-validation.mjs | 15 - .../scripts/live/generation-preflight.mjs | 93 - .../scripts/live/generation-publisher.mjs | 617 ----- .../impeccable/scripts/live/poll-lanes.mjs | 14 - .../impeccable/scripts/live/session-store.mjs | 151 +- .../scripts/live/source-artifact.mjs | 76 - .../impeccable/scripts/live/source-lock.mjs | 56 - .../impeccable/scripts/live/vue-component.mjs | 343 --- scripts/benchmark-live-codex-architecture.mjs | 421 ---- scripts/benchmark-live-codex-worker.mjs | 224 -- scripts/benchmark-live-control.mjs | 62 - scripts/benchmark-live-init.mjs | 102 - scripts/benchmark-live-providers.mjs | 539 ----- scripts/benchmark-live.mjs | 727 ------ scripts/compare-live-benchmarks.mjs | 48 - scripts/judge-live-rendered.mjs | 110 - scripts/lib/codex-exec-benchmark.mjs | 114 - scripts/lib/live-benchmark.mjs | 496 ---- scripts/lib/live-codex-quality-benchmark.mjs | 303 --- scripts/lib/live-provider-benchmark.mjs | 547 ----- scripts/lib/live-rendered-quality.mjs | 172 -- scripts/test-suites.mjs | 20 +- site/components/Header.astro | 4 +- site/components/HeroProof.astro | 114 +- site/components/LiveUiGallery.astro | 136 -- site/components/SlopPatternPreview.astro | 224 +- site/components/SlopRuleCard.astro | 33 + site/data/live-annotated-result.ts | 20 - site/data/live-codex-worker-result.ts | 160 -- site/data/live-control-result.ts | 17 - site/data/live-framework-results.ts | 29 - site/data/live-harnesses.ts | 126 - site/data/live-init-result.ts | 14 - site/data/live-performance.json | 794 ------- site/data/live-progressive-result.ts | 25 - site/data/live-provider-result.ts | 85 - site/layouts/Base.astro | 2 +- site/pages/index.astro | 281 +-- site/pages/live-lab/index.astro | 540 ----- site/pages/live/index.astro | 3 - site/pages/slop/index.astro | 165 +- .../antipattern-examples/new-slop-2026.html | 47 +- site/scripts/app.js | 121 +- site/scripts/components/live-demo.js | 38 +- site/scripts/live-lab-workbench.js | 70 - site/scripts/live-performance.js | 31 - site/scripts/live-ui-gallery.js | 520 ----- site/scripts/utils/theme.js | 21 +- site/styles/designing-kinpaku.css | 6 +- site/styles/detector-lab.css | 4 +- site/styles/docs-kinpaku.css | 2 +- site/styles/docs-visuals.css | 2 +- site/styles/home-kinpaku.css | 24 +- site/styles/home-rebuild.css | 17 +- site/styles/home-refresh.css | 2030 +++++++++++++---- site/styles/kinpaku-kit.css | 10 +- site/styles/kinpaku-tokens.css | 1 + site/styles/light-mode.css | 4 +- site/styles/live-lab-workbench.css | 523 ----- site/styles/live-mode-kinpaku.css | 4 +- site/styles/live-performance.css | 741 ------ site/styles/live-ui-gallery.css | 1709 -------------- site/styles/slop-kinpaku.css | 118 +- skill/SKILL.src.md | 12 +- skill/agents/impeccable-live-generator.md | 55 - skill/reference/codex.md | 109 +- skill/reference/craft.md | 6 +- skill/reference/document.md | 58 +- skill/reference/init.md | 111 +- skill/reference/live-generation.md | 32 - skill/reference/live.md | 124 +- skill/reference/new-work.md | 96 +- skill/reference/routing.md | 2 +- skill/reference/shape.md | 174 +- skill/reference/typeset.md | 4 +- skill/scripts/concept-seed.mjs | 33 +- skill/scripts/context-signals.mjs | 3 +- skill/scripts/context.mjs | 181 +- skill/scripts/hook-lib.mjs | 51 +- skill/scripts/lib/impeccable-paths.mjs | 4 - skill/scripts/live-accept.mjs | 262 +-- skill/scripts/live-browser.js | 523 +---- skill/scripts/live-codex-worker.mjs | 292 --- skill/scripts/live-inject.mjs | 151 +- skill/scripts/live-poll.mjs | 97 +- skill/scripts/live-publish.mjs | 37 - skill/scripts/live-server.mjs | 335 +-- skill/scripts/live-status.mjs | 18 +- skill/scripts/live-wrap.mjs | 110 +- skill/scripts/live.mjs | 53 +- .../scripts/live/codex-app-server-client.mjs | 559 ----- .../scripts/live/codex-worker-supervisor.mjs | 962 -------- skill/scripts/live/codex-worker.mjs | 979 -------- skill/scripts/live/event-validation.mjs | 15 - skill/scripts/live/generation-preflight.mjs | 93 - skill/scripts/live/generation-publisher.mjs | 617 ----- skill/scripts/live/poll-lanes.mjs | 14 - skill/scripts/live/session-store.mjs | 151 +- skill/scripts/live/source-artifact.mjs | 76 - skill/scripts/live/source-lock.mjs | 56 - skill/scripts/live/vue-component.mjs | 343 --- tests/context-signals.test.mjs | 12 +- tests/context.test.mjs | 95 +- tests/detect-antipatterns-fixtures.test.mjs | 33 - tests/detect-antipatterns.test.js | 15 - tests/docs-integrity.test.js | 34 + .../astro-inset-shadow-stripe.astro | 41 - tests/framework-fixtures.test.mjs | 24 - tests/framework-fixtures/README.md | 42 - .../nuxt-vite7/files/app.vue | 10 + .../nuxt-vite7/files/app/app.vue | 3 - .../nuxt-vite7/files/nuxt.config.ts | 1 - .../files/{app => }/pages/index.vue | 0 .../nuxt-vite7/fixture.json | 32 +- .../files/DESIGN.md | 7 - .../files/PRODUCT.md | 7 - .../files/index.html | 12 - .../files/package.json | 18 - .../files/src/App.jsx | 26 - .../files/src/main.jsx | 10 - .../files/src/styles.css | 111 - .../files/vite.config.js | 10 - .../vite8-react-brand-fidelity/fixture.json | 67 - .../vite8-react-brand-fidelity/gitignore.txt | 3 - .../files/src/routes/+page.svelte | 7 - tests/hook.test.mjs | 46 +- tests/live-accept.test.mjs | 79 +- tests/live-benchmark.test.mjs | 252 -- tests/live-browser-regression.test.mjs | 109 +- tests/live-browser-source.test.mjs | 73 +- tests/live-codex-app-server-client.test.mjs | 435 ---- tests/live-codex-exec-benchmark.test.mjs | 44 - tests/live-codex-quality-benchmark.test.mjs | 89 - tests/live-codex-worker-supervisor.test.mjs | 830 ------- tests/live-codex-worker.test.mjs | 914 -------- tests/live-e2e-agent-output.test.mjs | 12 +- tests/live-e2e-llm-agent.test.mjs | 63 - tests/live-e2e.test.mjs | 388 +--- tests/live-e2e/agent.mjs | 334 +-- tests/live-e2e/agents/llm-agent.mjs | 101 +- tests/live-e2e/session.mjs | 112 +- tests/live-e2e/ui.mjs | 62 +- tests/live-event-validation.test.mjs | 13 - tests/live-generation-preflight.test.mjs | 98 - tests/live-generation-publisher.test.mjs | 442 ---- tests/live-inject.test.mjs | 69 +- tests/live-poll-stream.test.mjs | 83 - tests/live-poll.test.mjs | 42 - tests/live-provider-benchmark.test.mjs | 115 - tests/live-provider-benchmark/README.md | 54 - tests/live-reference.test.mjs | 31 +- tests/live-rendered-quality.test.mjs | 107 - tests/live-server.test.mjs | 428 +--- tests/live-session-store.test.mjs | 124 - tests/live-vue-component.test.mjs | 212 -- tests/live-wrap.test.mjs | 52 +- tests/skill-behavior/README.md | 12 +- tests/skill-behavior/fixtures.mjs | 87 +- tests/skill-behavior/harness.mjs | 62 +- tests/skill-behavior/scenarios.test.mjs | 50 +- .../skill-behavior/workflow-contract.test.mjs | 174 ++ tests/slop-catalog.test.mjs | 43 + tests/theme.test.mjs | 26 + 553 files changed, 8903 insertions(+), 97987 deletions(-) delete mode 100644 .agents/skills/impeccable/agents/impeccable_live_generator.toml delete mode 100644 .agents/skills/impeccable/reference/live-generation.md delete mode 100644 .agents/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .agents/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .agents/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .agents/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .agents/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .agents/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .agents/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .agents/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .agents/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .agents/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .agents/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .claude/skills/impeccable/reference/live-generation.md delete mode 100644 .claude/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .claude/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .claude/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .claude/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .claude/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .claude/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .claude/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .claude/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .claude/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .claude/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .claude/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .cursor/skills/impeccable/reference/live-generation.md delete mode 100644 .cursor/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .cursor/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .gemini/skills/impeccable/reference/live-generation.md delete mode 100644 .gemini/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .gemini/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .github/skills/impeccable/reference/live-generation.md delete mode 100644 .github/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .github/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .github/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .github/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .github/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .github/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .github/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .github/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .github/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .github/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .github/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .kiro/skills/impeccable/reference/live-generation.md delete mode 100644 .kiro/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .kiro/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .opencode/skills/impeccable/reference/live-generation.md delete mode 100644 .opencode/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .opencode/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .pi/skills/impeccable/reference/live-generation.md delete mode 100644 .pi/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .pi/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .pi/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .pi/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .pi/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .pi/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .pi/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .pi/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .pi/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .pi/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .pi/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .qoder/skills/impeccable/reference/live-generation.md delete mode 100644 .qoder/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .qoder/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .rovodev/skills/impeccable/reference/live-generation.md delete mode 100644 .rovodev/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .rovodev/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .trae-cn/skills/impeccable/reference/live-generation.md delete mode 100644 .trae-cn/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .trae-cn/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 .trae/skills/impeccable/reference/live-generation.md delete mode 100644 .trae/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 .trae/skills/impeccable/scripts/live-publish.mjs delete mode 100644 .trae/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 .trae/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 .trae/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 .trae/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 .trae/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 .trae/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 .trae/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 .trae/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 .trae/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 docs/live-performance.md delete mode 100644 plugin/skills/impeccable/reference/live-generation.md delete mode 100644 plugin/skills/impeccable/scripts/live-codex-worker.mjs delete mode 100644 plugin/skills/impeccable/scripts/live-publish.mjs delete mode 100644 plugin/skills/impeccable/scripts/live/codex-app-server-client.mjs delete mode 100644 plugin/skills/impeccable/scripts/live/codex-worker-supervisor.mjs delete mode 100644 plugin/skills/impeccable/scripts/live/codex-worker.mjs delete mode 100644 plugin/skills/impeccable/scripts/live/generation-preflight.mjs delete mode 100644 plugin/skills/impeccable/scripts/live/generation-publisher.mjs delete mode 100644 plugin/skills/impeccable/scripts/live/poll-lanes.mjs delete mode 100644 plugin/skills/impeccable/scripts/live/source-artifact.mjs delete mode 100644 plugin/skills/impeccable/scripts/live/source-lock.mjs delete mode 100644 plugin/skills/impeccable/scripts/live/vue-component.mjs delete mode 100644 scripts/benchmark-live-codex-architecture.mjs delete mode 100644 scripts/benchmark-live-codex-worker.mjs delete mode 100644 scripts/benchmark-live-control.mjs delete mode 100644 scripts/benchmark-live-init.mjs delete mode 100644 scripts/benchmark-live-providers.mjs delete mode 100644 scripts/benchmark-live.mjs delete mode 100644 scripts/compare-live-benchmarks.mjs delete mode 100644 scripts/judge-live-rendered.mjs delete mode 100644 scripts/lib/codex-exec-benchmark.mjs delete mode 100644 scripts/lib/live-benchmark.mjs delete mode 100644 scripts/lib/live-codex-quality-benchmark.mjs delete mode 100644 scripts/lib/live-provider-benchmark.mjs delete mode 100644 scripts/lib/live-rendered-quality.mjs delete mode 100644 site/components/LiveUiGallery.astro create mode 100644 site/components/SlopRuleCard.astro delete mode 100644 site/data/live-annotated-result.ts delete mode 100644 site/data/live-codex-worker-result.ts delete mode 100644 site/data/live-control-result.ts delete mode 100644 site/data/live-framework-results.ts delete mode 100644 site/data/live-harnesses.ts delete mode 100644 site/data/live-init-result.ts delete mode 100644 site/data/live-performance.json delete mode 100644 site/data/live-progressive-result.ts delete mode 100644 site/data/live-provider-result.ts delete mode 100644 site/pages/live-lab/index.astro delete mode 100644 site/pages/live/index.astro delete mode 100644 site/scripts/live-lab-workbench.js delete mode 100644 site/scripts/live-performance.js delete mode 100644 site/scripts/live-ui-gallery.js delete mode 100644 site/styles/live-lab-workbench.css delete mode 100644 site/styles/live-performance.css delete mode 100644 site/styles/live-ui-gallery.css delete mode 100644 skill/agents/impeccable-live-generator.md delete mode 100644 skill/reference/live-generation.md delete mode 100644 skill/scripts/live-codex-worker.mjs delete mode 100644 skill/scripts/live-publish.mjs delete mode 100644 skill/scripts/live/codex-app-server-client.mjs delete mode 100644 skill/scripts/live/codex-worker-supervisor.mjs delete mode 100644 skill/scripts/live/codex-worker.mjs delete mode 100644 skill/scripts/live/generation-preflight.mjs delete mode 100644 skill/scripts/live/generation-publisher.mjs delete mode 100644 skill/scripts/live/poll-lanes.mjs delete mode 100644 skill/scripts/live/source-artifact.mjs delete mode 100644 skill/scripts/live/source-lock.mjs delete mode 100644 skill/scripts/live/vue-component.mjs delete mode 100644 tests/fixtures/antipatterns/astro-inset-shadow-stripe.astro create mode 100644 tests/framework-fixtures/nuxt-vite7/files/app.vue delete mode 100644 tests/framework-fixtures/nuxt-vite7/files/app/app.vue rename tests/framework-fixtures/nuxt-vite7/files/{app => }/pages/index.vue (100%) delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/files/DESIGN.md delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/files/PRODUCT.md delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/files/index.html delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/files/package.json delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/files/src/App.jsx delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/files/src/main.jsx delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/files/src/styles.css delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/files/vite.config.js delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/fixture.json delete mode 100644 tests/framework-fixtures/vite8-react-brand-fidelity/gitignore.txt delete mode 100644 tests/live-benchmark.test.mjs delete mode 100644 tests/live-codex-app-server-client.test.mjs delete mode 100644 tests/live-codex-exec-benchmark.test.mjs delete mode 100644 tests/live-codex-quality-benchmark.test.mjs delete mode 100644 tests/live-codex-worker-supervisor.test.mjs delete mode 100644 tests/live-codex-worker.test.mjs delete mode 100644 tests/live-generation-preflight.test.mjs delete mode 100644 tests/live-generation-publisher.test.mjs delete mode 100644 tests/live-provider-benchmark.test.mjs delete mode 100644 tests/live-provider-benchmark/README.md delete mode 100644 tests/live-rendered-quality.test.mjs delete mode 100644 tests/live-vue-component.test.mjs create mode 100644 tests/skill-behavior/workflow-contract.test.mjs create mode 100644 tests/slop-catalog.test.mjs create mode 100644 tests/theme.test.mjs diff --git a/.agents/skills/impeccable/SKILL.md b/.agents/skills/impeccable/SKILL.md index 951c30028..0d50553c2 100644 --- a/.agents/skills/impeccable/SKILL.md +++ b/.agents/skills/impeccable/SKILL.md @@ -20,7 +20,7 @@ Approach every design task as the design lead at a small studio known for giving **Existing worlds are sacred.** Most of impeccable's work happens inside a site or app that already exists. When the surface has a committed design system (real tokens, deliberately chosen faces, a palette the brand owns), work inside that world: extend it, sharpen it, leave it unmistakably the same brand, and never degrade a working page's performance. Inventing parallel colors, fonts, or styles on an existing surface is a defect, not creativity. A scoped refinement keeps the named scope's content and media footprint unless the brief explicitly changes them; build the added emphasis by recomposing what is already there through hierarchy, proportion, rhythm, and the system's own motifs. -**New identity work reads the playbook first.** When nothing committed exists (greenfield, or a codebase with no real tokens or chosen faces), or the user asks for a redesign that discards the current look, you MUST read [reference/new-work.md](reference/new-work.md) before making any design decision. Not optional, not skippable under time pressure: producing new identity without it yields the generic default this skill exists to prevent. A redesign is new work; read the incumbent as evidence, not as a template or an obstacle: where it carries a deliberate, ownable visual idea, preserve that lineage and intensify it instead of replacing it with contemporary polish. `context.mjs` prints this directive when it detects the situation. Scoped fixes inside an existing world don't need the playbook; the craft floor below governs them. +**New identity work reads the playbook first.** When nothing committed exists (greenfield, or a codebase with no real tokens or chosen faces), or the user explicitly asks for a redesign that discards the current look, you MUST read [reference/new-work.md](reference/new-work.md) before making any design decision. Missing DESIGN.md alone does not make work greenfield: code, tokens, chosen type, components, and assets are incumbent design authority. Preserve and extend them unless the user asked to replace them. Producing a genuinely new identity without the playbook yields the generic default this skill exists to prevent. Scoped fixes inside an existing world don't need it; the craft floor below governs them. `context.mjs` prints the appropriate authority directive. ## Modes diff --git a/.agents/skills/impeccable/agents/impeccable_live_generator.toml b/.agents/skills/impeccable/agents/impeccable_live_generator.toml deleted file mode 100644 index 3a2ca539b..000000000 --- a/.agents/skills/impeccable/agents/impeccable_live_generator.toml +++ /dev/null @@ -1,46 +0,0 @@ -name = "impeccable_live_generator" -description = "Generates and transactionally publishes one Impeccable Live variant request while the parent keeps polling." -model_reasoning_effort = "low" -nickname_candidates = ["Variant Producer", "Live Composer", "Direction Maker"] -developer_instructions = ''' -# Impeccable Live Generator - -You own one leased Impeccable Live `generate` event. The parent thread owns browser control and the foreground poll loop. Never poll, Accept, Discard, commit, stage, or edit generated provider output. - -## Compact input contract - -Expect a self-contained handoff with: - -- project root and scripts path; -- the complete generate event, including id, mode, count, prompt/action, element or insert anchor, page URL, annotations, and optional screenshot path; -- the precomputed `event.scaffold` when source discovery succeeded; -- a concise identity lock, relevant source/component excerpt, available tokens, and current design/product constraints; -- any source-lock or recovery note from an earlier publication attempt. - -Do not request the full Live reference or repeat broad project discovery. Use the scaffold and compact handoff. Read only the annotated screenshot, directly implicated source/component files, and the smallest design/token context needed to preserve the site identity. - -## Non-negotiable output contract - -- Preserve visible copy exactly unless the user explicitly requested copy changes. -- Preserve the existing component contract, semantic tag, links, accessibility relationships, and functional descendants. -- Reuse existing components, CSS custom properties, typography, spacing, radii, and color roles. Never invent raw colors or foreign fonts when tokens exist. -- Do not add gradients, blur, glow, glass, neon, decorative shadows, emoji, or unrelated content unless the explicit user direction requires it. -- Never decorate a card, label, row, tab, or container with a colored stripe on only one edge. This includes borders, inset box-shadows, gradients, and pseudo-elements; selection and focus indicators are the only exception. -- Produce the requested number of materially different directions through hierarchy, layout, density, or existing color-role allocation. CSS-only no-ops and source-identical variants are invalid. -- Keep temporary Live markers and preview CSS out of accepted project truth; the publisher/Accept pipeline owns cleanup. - -## Workflow - -1. Trust `event.scaffold` when present. Do not rerun source discovery or wrapping. If it is absent, run the correct wrap/insert helper once. -2. If annotations exist, read the screenshot before designing. Treat pins and strokes as semantic constraints. -3. Name all directions and their parameter axes before writing so the set stays coherent. Parameters are lazy: revision 1 carries no parameter manifest. -4. Prepare revision 1 with `live-publish.mjs --prepare --id EVENT_ID --file SOURCE_FILE`. Edit only the returned artifact (or isolated component directory), never live project source. -5. Write one complete, valid first variant plus only its CSS. Run `detect.mjs --json` on the staged artifact before publishing. Fix genuine findings; when inspection shows a contextual false positive, use judgment and continue without changing persistent detector configuration. The detector is a review signal, not an automatic publication veto. Publish immediately with the returned epoch, artifact path, expected source hash, `--arrived 1`, and the requested `--expected` count. -6. Prepare again from the published prefix, add the remaining validated directions, attach parameter manifests only with the complete set, and publish the largest ready prefix. Preserve every already-published variant byte-for-byte. -7. On `stale_generation_epoch`, `source_changed`, or another fence rejection, stop. Do not retry against stale source or leave direct edits behind. -8. Verify the final artifact/source parses and run the detector again before the final publication. Apply the same genuine-finding versus contextual-false-positive judgment. Reply exactly once with `live-poll.mjs --reply EVENT_ID done --file RELATIVE_PATH`. On a real failure, reply once with `error` and a short reason. - -For Svelte or Vue component preview, write only `vN.svelte` / `vN.vue` in the isolated `componentDir` returned by prepare and update the isolated manifest. Never edit the live component directory. For JSX/TSX source previews, preserve JSX attribute syntax and wrap preview CSS as required by `scaffold.cssAuthoring`. - -Speed matters because the user is waiting. Publish the first reviewable result before exploring tunables, writing explanations, or polishing later variants. Return no recap: tool work and the protocol reply are the result. -''' diff --git a/.agents/skills/impeccable/reference/live-generation.md b/.agents/skills/impeccable/reference/live-generation.md deleted file mode 100644 index 86dc7c46b..000000000 --- a/.agents/skills/impeccable/reference/live-generation.md +++ /dev/null @@ -1,32 +0,0 @@ -# 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. diff --git a/.agents/skills/impeccable/reference/live.md b/.agents/skills/impeccable/reference/live.md index df7ecfc1f..bc1e3e9ea 100644 --- a/.agents/skills/impeccable/reference/live.md +++ b/.agents/skills/impeccable/reference/live.md @@ -12,26 +12,21 @@ 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 .agents/skills/impeccable/scripts/live.mjs --target ` 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). Run `live-poll.mjs` again immediately after every event or `--reply`; Codex runs this one-shot poll in the foreground. Never pass a short `--timeout=`. +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=`. 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; deliver variants using the harness policy below; `--reply done`; poll again. In Codex, delegate the complete event to `impeccable_live_generator` and resume the foreground poll immediately; the generator owns publication and the reply. +4. On `generate`: read screenshot if present; load the action's reference; plan three distinct directions; write all variants in one edit; `--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 the foreground task runs `live-complete.mjs --id EVENT_ID`; finish that 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 you finish cleanup, run `live-complete.mjs --id EVENT_ID`, and only then poll 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**: run the default one-shot poll in a **yielded foreground exec session**. Do not suffix it with `&`, use `--stream`, or leave Live without an active foreground poll. When `generate` arrives, delegate to the low-effort `impeccable_live_generator` agent with a compact handoff, then immediately start the next foreground poll while that agent publishes and replies. Do not paste this full reference into the handoff. Handle Steer, Accept/Discard, manual Apply, carbonize, and Exit in the main task; after each handler/reply, restart the foreground poll. +- **Codex**: run the poll in the **foreground** (blocking shell; not a background task, not a subagent). Codex background exec sessions do not reliably surface poll stdout back into the conversation at the moment events arrive, so a "fire-and-forget" background poll will stall live mode. - **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 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 CSS/manifests only with the complete set. The browser makes every arrived variant immediately reviewable and acceptable; Accept/Discard durably cancel unfinished revisions. - 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. ## Start @@ -42,10 +37,6 @@ node .agents/skills/impeccable/scripts/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. -Normally `codexWorker.enabled` is `false`; run the default unfiltered `live-poll.mjs` command in the foreground. If an explicitly opted-in run returns `codexWorker.enabled: true`, follow the experimental worker section below instead. - -If an explicitly opted-in run includes `codexWorker.error: "codex_cli_unavailable"`, tell the user once that Live fell back to foreground generation, then run the returned unfiltered `codexWorker.foregroundPoll`. Do not retry or install anything during the session. - `serverPort` and `serverToken` belong to the small **Impeccable live helper** HTTP server (serves `/live.js`, SSE, and `/poll`). That port is **not** your dev server and is usually not the URL you open to view the app. The browser page is whatever origin serves one of the `pageFiles` entries (Vite / Next / Bun / tunnel / LAN hostname). If output is `{ ok: false, error: "config_missing" | "config_invalid", path }`, this project hasn't been configured for live mode (or its config is stale). See **First-time setup** at the bottom. @@ -97,61 +88,20 @@ node .agents/skills/impeccable/scripts/live-complete.mjs --id SESSION_ID Server restart rule: start `live-server.mjs` again, then poll. Startup requeues unacknowledged pending events from the journal, so do not ask the user to click Go again unless `live-resume.mjs` says no active session exists. -### Experimental dedicated Codex worker - -The app-server supervisor is retained for controlled experiments, but it is not the primary Codex path. Enable it explicitly for a run: - -```bash -IMPECCABLE_LIVE_CODEX_WORKER=1 node .agents/skills/impeccable/scripts/live.mjs -``` - -Activation remains process-local. The worker is off by default, including in Codex. `IMPECCABLE_LIVE_CODEX_WORKER=1` is the direct opt-in; alternatively, Codex may opt in through `experimentalCodexWorker.enabled`. A committed setting can never switch a non-Codex harness onto this path: - -```json -{ - "experimentalCodexWorker": { - "enabled": true, - "profile": "quality", - "delivery": "progressive" - } -} -``` - -When explicitly enabled, the worker remains Codex-only. Claude, Gemini, Cursor, and every other harness keep their portable polling 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. Otherwise Live records the worker as `starting` and returns immediately. 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 .agents/skills/impeccable/scripts/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. - -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. - -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: - -```bash -node .agents/skills/impeccable/scripts/live-codex-worker.mjs --status -node .agents/skills/impeccable/scripts/live-codex-worker.mjs --stop -``` - -Model and binary overrides are `IMPECCABLE_LIVE_CODEX_PROFILE`, `IMPECCABLE_LIVE_CODEX_MODEL`, `IMPECCABLE_LIVE_CODEX_EFFORT`, and `IMPECCABLE_CODEX_PATH`. `delivery: "atomic"` retains the one-turn publication control. Steer, manual Apply, carbonize cleanup, and Exit remain on the high-judgment foreground control lane; the server's type filter prevents either lane from leasing the other's events. - ## Handle `generate` **Replace mode** (default): `{id, action, freeformPrompt?, count, pageUrl, element, screenshotPath?, comments?, strokes?}`. **Insert mode** (`event.mode === "insert"`): `{id, mode: "insert", count, pageUrl, insert: { position, anchor }, placeholder: { width, height }, freeformPrompt?, screenshotPath?, comments?, strokes?}`. No `action`. Requires a non-empty `freeformPrompt` **or** annotations. Screenshot is sent only when annotations exist (same rule as replace). Use `placeholder` dimensions as a soft size hint for net-new content. -Speed matters; the user is watching the selected element. Reuse server preflight metadata when available, minimize discovery calls, and follow the harness-specific delivery policy above. +Speed matters; the user is watching a spinner. Minimize tool calls by using the wrap/insert helper and writing all variants in a single edit. ### Insert mode branch When `event.mode === "insert"`: 1. Read the screenshot if `event.screenshotPath` is present (annotations only). -2. If `event.scaffold` is present, use it as the insert-helper result and do **not** run the helper again. Otherwise run the insert helper instead of wrap: +2. Run the insert helper instead of wrap: ```bash node .agents/skills/impeccable/scripts/live-insert.mjs --id EVENT_ID --count EVENT_COUNT --position after \ @@ -161,7 +111,7 @@ node .agents/skills/impeccable/scripts/live-insert.mjs --id EVENT_ID --count EVE - `--position` ← `event.insert.position` (`before` | `after`) - Anchor flags ← `event.insert.anchor` (same mapping as wrap: id, classes, tag, text) -The scaffold has **no** `data-impeccable-variant="original"`. Variants are net-new HTML+CSS inserted at `insertLine`. For Operate/Read surfaces load `operate.md`; Persuade/Experience surfaces use SKILL.md's mode guidance plus `new-work.md` when the variant invents identity (freeform only, no action sub-command). Deliver using the harness policy, then `--reply done`. +The scaffold has **no** `data-impeccable-variant="original"`. Variants are net-new HTML+CSS inserted at `insertLine`. Load `brand.md` or `product.md` (freeform only, no action sub-command). Write all variants in one edit, then `--reply done`. For Svelte/SvelteKit targets, `live-insert.mjs` returns `previewMode: "svelte-component"` with `mode: "insert"`, `file` pointing at a temporary `node_modules/.impeccable-live//manifest.json`, `componentDir` pointing at the variant component files, and `sourceFile` pointing at the real `.svelte` route. Write each inserted variant as a real Svelte component (`v1.svelte`, `v2.svelte`, …) under `componentDir`. Insert variants must be non-empty net-new content with a single top-level root, no `data-impeccable-*` attributes, and CSS in each component's `', styleContentStart); - if (styleClose < 0 || styleClose > wrapper.closeEnd) { - throw workerError('worker_output_source_delta_style_invalid'); - } - const styleContent = source.slice(styleContentStart, styleClose); - let nextStyleContent; - const firstTick = styleContent.indexOf('`'); - const lastTick = styleContent.lastIndexOf('`'); - if (firstTick >= 0 || lastTick >= 0) { - if (firstTick < 0 || lastTick <= firstTick) { - throw workerError('worker_output_source_delta_style_invalid'); - } - nextStyleContent = styleContent.slice(0, lastTick).trimEnd() - + '\n' + [css, normalizedParameterCss].filter(Boolean).join('\n') + '\n' - + styleContent.slice(lastTick); - } else { - nextStyleContent = styleContent.trimEnd() - + '\n' + [css, normalizedParameterCss].filter(Boolean).join('\n') + '\n'; - } - merged = source.slice(0, styleContentStart) + nextStyleContent + source.slice(styleClose); - } else { - if (variantId !== 1) throw workerError('worker_output_source_delta_style_missing'); - const openingTag = String(styleTag || `'].join('\n') - : [openingTag, css, ''].join('\n'); - } - - const nextWrapper = findSessionWrapper(merged, id); - if (!nextWrapper) throw workerError('worker_output_source_delta_wrapper_missing'); - const endMarker = findSessionEndMarker(merged, id, nextWrapper); - const closeLineStart = merged.lastIndexOf('\n', nextWrapper.closeStart) + 1; - const closeLinePrefix = merged.slice(closeLineStart, nextWrapper.closeStart); - const childIndent = endMarker?.indent || nextWrapper.indent + ' '; - const contentIndent = childIndent + ' '; - const indentedMarkup = markup.split('\n') - .map((line) => line.trim() ? contentIndent + line : '') - .join('\n'); - const variantBlock = [ - ...(newStyleBlock - ? newStyleBlock.split('\n').map((line) => childIndent + line) - : []), - `${childIndent}
`, - indentedMarkup, - `${childIndent}
`, - ].join('\n'); - if (endMarker) { - merged = merged.slice(0, endMarker.lineStart) + variantBlock + '\n' + merged.slice(endMarker.lineStart); - } else if (/^\s*$/.test(closeLinePrefix)) { - merged = merged.slice(0, closeLineStart) + variantBlock + '\n' + merged.slice(closeLineStart); - } else { - merged = merged.slice(0, nextWrapper.closeStart) - + '\n' + variantBlock + '\n' + nextWrapper.indent - + merged.slice(nextWrapper.closeStart); - } - if (params) merged = applySourceParams(merged, id, params, variantCount); - return merged; -} - -export function applyCodexSourceParameters({ - source, - sessionId, - expectedVariants = 3, - styleMode = 'scoped', - parameterCss = '', - paramsJson, -}) { - const variantCount = Number(expectedVariants); - const params = normalizeSourceParams(paramsJson, variantCount); - const css = String(parameterCss || '').trim(); - if (/<\/?style\b|`|\$\{/i.test(css)) { - throw workerError('worker_output_source_delta_css_unsafe'); - } - if (css) { - validateSourceDeltaCss(css, { - variantIds: Array.from({ length: variantCount }, (_, index) => index + 1), - styleMode, - }); - } - - const id = String(sessionId || ''); - if (!id) throw workerError('worker_output_source_delta_session_missing'); - let merged = String(source || ''); - if (css) { - const escapedId = escapeRegExp(id); - const styleOpen = new RegExp(`]*\\bdata-impeccable-css=(?:"${escapedId}"|'${escapedId}')[^>]*>`, 'i'); - const styleMatch = styleOpen.exec(merged); - if (!styleMatch) throw workerError('worker_output_source_delta_style_missing'); - const contentStart = styleMatch.index + styleMatch[0].length; - const styleClose = merged.indexOf('', contentStart); - if (styleClose < 0) throw workerError('worker_output_source_delta_style_invalid'); - const styleContent = merged.slice(contentStart, styleClose); - const lastTick = styleContent.lastIndexOf('`'); - const nextStyleContent = lastTick >= 0 - ? styleContent.slice(0, lastTick).trimEnd() + '\n' + css + '\n' + styleContent.slice(lastTick) - : styleContent.trimEnd() + '\n' + css + '\n'; - merged = merged.slice(0, contentStart) + nextStyleContent + merged.slice(styleClose); - } - return applySourceParams(merged, id, params, variantCount); -} - -function validateSourceDeltaCss(css, { variantIds, styleMode, requireVariantId = null }) { - const allowed = new Set(variantIds.map(String)); - const refs = [...String(css).matchAll(/\[data-impeccable-variant=(?:"([^"]+)"|'([^']+)')\]/g)] - .map((match) => match[1] || match[2]); - if ((requireVariantId != null && !refs.includes(String(requireVariantId))) - || refs.some((variant) => !allowed.has(variant))) { - throw workerError('worker_output_source_delta_css_unfenced'); - } - if (!String(css).trim()) return; - const astroGlobal = styleMode === 'astro-global-prefixed'; - if (astroGlobal ? /@scope\b/.test(css) : !/@scope\s*\(/.test(css)) { - throw workerError('worker_output_source_delta_css_strategy_invalid'); - } -} - -function normalizeSourceParams(paramsJson, expectedVariants) { - if (!Number.isInteger(expectedVariants) || expectedVariants < 1 - || Buffer.byteLength(String(paramsJson)) > 20_000) { - throw workerError('worker_output_source_delta_params_invalid'); - } - let parsed; - try { - parsed = JSON.parse(String(paramsJson)); - } catch { - throw workerError('worker_output_source_delta_params_invalid'); - } - if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw workerError('worker_output_source_delta_params_invalid'); - } - const expectedKeys = Array.from({ length: expectedVariants }, (_, index) => String(index + 1)); - if (Object.keys(parsed).sort().join(',') !== expectedKeys.join(',')) { - throw workerError('worker_output_source_delta_params_invalid'); - } - for (const key of expectedKeys) { - if (!Array.isArray(parsed[key]) || parsed[key].length > 4) { - throw workerError('worker_output_source_delta_params_invalid'); - } - const ids = new Set(); - for (const spec of parsed[key]) { - const id = String(spec?.id || ''); - const kind = String(spec?.kind || ''); - if (!/^[a-z][a-z0-9-]{0,31}$/.test(id) || ids.has(id) - || !['range', 'steps', 'toggle'].includes(kind) - || typeof spec?.label !== 'string' || !spec.label.trim()) { - throw workerError('worker_output_source_delta_params_invalid'); - } - ids.add(id); - if (kind === 'range' - && !['min', 'max', 'step', 'default'].every((field) => Number.isFinite(spec[field]))) { - throw workerError('worker_output_source_delta_params_invalid'); - } - if (kind === 'steps' && (!Array.isArray(spec.options) || spec.options.length < 2 - || spec.options.some((option) => ( - typeof option?.value !== 'string' || typeof option?.label !== 'string' - )))) { - throw workerError('worker_output_source_delta_params_invalid'); - } - if (kind === 'toggle' && typeof spec.default !== 'boolean') { - throw workerError('worker_output_source_delta_params_invalid'); - } - } - } - return parsed; -} - -function applySourceParams(source, sessionId, params, expectedVariants) { - const wrapper = findSessionWrapper(source, sessionId); - if (!wrapper) throw workerError('worker_output_source_delta_wrapper_missing'); - let body = source.slice(wrapper.openStart, wrapper.closeEnd); - for (let variant = 1; variant <= expectedVariants; variant += 1) { - const attr = escapeRegExp(String(variant)); - const open = new RegExp(`]*\\bdata-impeccable-variant=(?:"${attr}"|'${attr}')[^>]*>`, 'i'); - const match = open.exec(body); - if (!match) throw workerError('worker_output_source_delta_variant_missing', { variant }); - const json = JSON.stringify(params[String(variant)]) - .replaceAll('&', '&') - .replaceAll("'", '''); - const nextOpen = match[0] - .replace(/\sdata-impeccable-params=(?:"[^"]*"|'[^']*')/i, '') - .replace(/>$/, ` data-impeccable-params='${json}'>`); - body = body.slice(0, match.index) + nextOpen + body.slice(match.index + match[0].length); - } - return source.slice(0, wrapper.openStart) + body + source.slice(wrapper.closeEnd); -} - -function findSessionEndMarker(source, sessionId, wrapper) { - const marker = `impeccable-variants-end ${sessionId}`; - const markerAt = source.indexOf(marker, wrapper.openStart); - if (markerAt < 0 || markerAt >= wrapper.closeStart) return null; - const lineStart = source.lastIndexOf('\n', markerAt) + 1; - const indent = source.slice(lineStart, markerAt).match(/^\s*/)?.[0] || ''; - return { lineStart, indent }; -} - -function normalizeVariantPlan(plan, expectedVariants) { - if (!plan || typeof plan !== 'object' || Array.isArray(plan)) { - throw workerError('worker_output_plan_invalid'); - } - const identityLock = Array.isArray(plan.identityLock) - ? plan.identityLock.map((item) => String(item || '').trim()).filter(Boolean) - : []; - const directions = Array.isArray(plan.directions) ? plan.directions : []; - if (identityLock.length < 1 || identityLock.length > 8 || directions.length !== Number(expectedVariants)) { - throw workerError('worker_output_plan_invalid'); - } - const normalizedDirections = directions.map((direction) => ({ - variantId: Number(direction?.variantId), - name: String(direction?.name || '').trim(), - axis: String(direction?.axis || '').trim(), - intent: String(direction?.intent || '').trim(), - })); - const expectedIds = Array.from({ length: Number(expectedVariants) }, (_, index) => index + 1); - const sortedIds = normalizedDirections.map((direction) => direction.variantId).sort((a, b) => a - b); - if (normalizedDirections.some((direction) => ( - !Number.isInteger(direction.variantId) - || !direction.name - || !direction.axis - || !direction.intent - )) || sortedIds.some((id, index) => id !== expectedIds[index])) { - throw workerError('worker_output_plan_invalid'); - } - return { identityLock, directions: normalizedDirections }; -} - -export function prepareCodexWorkerPhase({ id, sourceFile, cwd = process.cwd() }) { - const prepared = prepareGenerationArtifact({ id, sourceFile, cwd }); - if (!prepared.ok) throw workerError(`prepare_${prepared.error}`, prepared); - return prepared; -} - -export function publishCodexWorkerPhase({ - event, - prepared, - arrivedVariants, - phase, - cwd = process.cwd(), -}) { - const published = publishGenerationArtifact({ - id: event.id, - epoch: prepared.epoch, - sourceFile: event.scaffold.file, - artifactFile: prepared.artifactFile, - expectedSourceHash: prepared.expectedSourceHash, - arrivedVariants, - expectedVariants: Number(event.count || arrivedVariants), - publicationKind: ['remainder', 'params', 'atomic'].includes(phase) ? 'params' : 'variants', - cwd, - }); - if (!published.ok) throw workerError(`publish_${published.error}`, published); - return published; -} - -export function generationIsCanceled(eventId, { cwd = process.cwd() } = {}) { - const snapshot = createLiveSessionStore({ cwd, sessionId: eventId }).getSnapshot(eventId, { includeCompleted: true }); - return snapshot?.generationCanceled === true; -} - -export function codexWorkerStateIsOwned(state, cwd) { - return codexWorkerOwnerMatches(state, cwd) - && typeof state?.threadId === 'string' - && state.threadId.length > 0; -} - -export function isCodexComponentPreviewMode(value) { - return value === 'svelte-component' || value === 'vue-component'; -} - -export function codexWorkerProcessStateIsOwned(state, cwd) { - return codexWorkerOwnerMatches(state, cwd) - && Number.isInteger(state?.pid) - && state.pid > 0; -} - -function codexWorkerOwnerMatches(state, cwd) { - return state?.owner === CODEX_WORKER_OWNER - && canonicalPath(state?.cwd) === canonicalPath(cwd); -} - -function canonicalPath(value) { - if (!value || typeof value !== 'string') return null; - const resolved = path.resolve(value); - try { return fs.realpathSync.native(resolved); } catch { return resolved; } -} - -function sanitizeEvent(event) { - const copy = { ...event }; - delete copy.agentAction; - delete copy._acceptResult; - delete copy._completionAck; - return copy; -} - -function findSessionWrapper(source, sessionId) { - const escapedId = escapeRegExp(sessionId); - const open = new RegExp(`]*\\bdata-impeccable-variants=(?:"${escapedId}"|'${escapedId}')[^>]*>`, 'i'); - const wrapperOpen = open.exec(source); - if (!wrapperOpen) return null; - const token = /]*\/\s*>|]*>|<\/div\s*>/gi; - token.lastIndex = wrapperOpen.index; - let depth = 0; - let match; - while ((match = token.exec(source))) { - if (/^<\/div/i.test(match[0])) { - depth -= 1; - if (depth === 0) { - const lineStart = source.lastIndexOf('\n', wrapperOpen.index) + 1; - const indent = source.slice(lineStart, wrapperOpen.index).match(/^\s*/)?.[0] || ''; - return { - openStart: wrapperOpen.index, - closeStart: match.index, - closeEnd: token.lastIndex, - indent, - }; - } - } else if (!/\/\s*>$/.test(match[0])) { - depth += 1; - } - } - return null; -} - -function extractSourceVariantBlock(source, variantId) { - const attr = escapeRegExp(String(variantId)); - return new RegExp(`]*\\bdata-impeccable-variant=(?:"${attr}"|'${attr}')[^>]*>`, 'i').test(source); -} - -function parseWorkerJson(value) { - const text = String(value || '').trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, ''); - try { - return JSON.parse(text); - } catch (error) { - throw workerError('worker_output_json_invalid', { message: error.message }); - } -} - -function parseBoolean(value) { - if (value == null || value === '') return null; - if (/^(?:1|true|yes|on)$/i.test(String(value))) return true; - if (/^(?:0|false|no|off)$/i.test(String(value))) return false; - return null; -} - -function nonEmpty(value) { - return typeof value === 'string' && value.trim() ? value.trim() : null; -} - -function positiveInteger(value, fallback) { - const parsed = Number(value); - return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; -} - -function resolveInside(root, value) { - if (!value || typeof value !== 'string') return null; - const resolvedRoot = path.resolve(root); - const resolved = path.resolve(resolvedRoot, value); - const relative = path.relative(resolvedRoot, resolved); - if (!relative || (!relative.startsWith('..') && !path.isAbsolute(relative))) return resolved; - return null; -} - -function readBounded(file, maxBytes) { - const stat = fs.statSync(file); - if (stat.size > maxBytes) throw workerError('artifact_too_large', { bytes: stat.size }); - return fs.readFileSync(file, 'utf-8'); -} - -function workerError(code, detail = {}) { - const error = new Error(code); - error.code = code; - Object.assign(error, detail); - return error; -} - -function escapeRegExp(value) { - return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -} diff --git a/.agents/skills/impeccable/scripts/live/event-validation.mjs b/.agents/skills/impeccable/scripts/live/event-validation.mjs index 122c29836..0897b1317 100644 --- a/.agents/skills/impeccable/scripts/live/event-validation.mjs +++ b/.agents/skills/impeccable/scripts/live/event-validation.mjs @@ -118,15 +118,6 @@ export function validateEvent(msg) { return 'checkpoint: paramValues must be an object'; } return null; - case 'agent_phase': - if (!isValidId(msg.id)) return 'agent_phase: missing or malformed id'; - if (typeof msg.phase !== 'string' || !/^[a-z][a-z0-9_]{1,63}$/.test(msg.phase)) { - return 'agent_phase: missing or malformed phase'; - } - if (msg.durationMs !== undefined && (!Number.isFinite(msg.durationMs) || msg.durationMs < 0)) { - return 'agent_phase: durationMs must be a non-negative number'; - } - return null; case 'exit': return null; case 'prefetch': @@ -140,12 +131,6 @@ export function validateEvent(msg) { if (msg.message.length > 4000) return 'steer: message too long'; if (msg.pageUrl !== undefined && typeof msg.pageUrl !== 'string') return 'steer: pageUrl must be string'; return null; - case 'carbonize_cleanup': - if (!isValidId(msg.id)) return 'carbonize_cleanup: missing or malformed id'; - if (!isValidId(msg.sessionId)) return 'carbonize_cleanup: missing or malformed sessionId'; - if (!msg.file || typeof msg.file !== 'string') return 'carbonize_cleanup: missing file'; - if (!isValidVariantId(String(msg.variantId))) return 'carbonize_cleanup: missing or malformed variantId'; - return null; default: return 'Unknown event type: ' + msg.type; } diff --git a/.agents/skills/impeccable/scripts/live/generation-preflight.mjs b/.agents/skills/impeccable/scripts/live/generation-preflight.mjs deleted file mode 100644 index d2dcc7f7f..000000000 --- a/.agents/skills/impeccable/scripts/live/generation-preflight.mjs +++ /dev/null @@ -1,93 +0,0 @@ -import { execFileSync } from 'node:child_process'; -import path from 'node:path'; - -const PREFLIGHT_TIMEOUT_MS = 15_000; - -export function buildGenerationPreflight(event, scriptsDir, { isolated = false } = {}) { - if (!event || event.type !== 'generate' || !event.id) return null; - - const isInsert = event.mode === 'insert'; - const target = isInsert ? insertTarget(event) : replaceTarget(event); - if (!target.elementId && !target.classes) return null; - - const script = path.join(scriptsDir, isInsert ? 'live-insert.mjs' : 'live-wrap.mjs'); - const args = [script, '--id', event.id, '--count', String(event.count || 3)]; - if (!isInsert && isolated) args.push('--isolated'); - if (isInsert) args.push('--position', target.position); - if (target.elementId) args.push('--element-id', target.elementId); - if (target.classes) args.push('--classes', target.classes); - if (target.tag) args.push('--tag', target.tag); - if (target.text) args.push('--text', target.text); - if (!isInsert && event.pageUrl) args.push('--page-url', event.pageUrl); - return { script, args, mode: isInsert ? 'insert' : 'replace' }; -} - -export function runGenerationPreflight(event, { - cwd = process.cwd(), - scriptsDir, - execFileSyncImpl = execFileSync, - timeoutMs = PREFLIGHT_TIMEOUT_MS, - isolated = false, -} = {}) { - const command = buildGenerationPreflight(event, scriptsDir, { isolated }); - if (!command) { - return { ok: false, skipped: true, reason: 'insufficient_locator' }; - } - - const startedAt = performance.now(); - try { - const stdout = execFileSyncImpl(process.execPath, command.args, { - cwd, - encoding: 'utf-8', - timeout: timeoutMs, - stdio: ['ignore', 'pipe', 'pipe'], - }); - const line = String(stdout).trim().split('\n').filter(Boolean).pop(); - if (!line) throw new Error('preflight returned no scaffold metadata'); - return { - ok: true, - mode: command.mode, - durationMs: performance.now() - startedAt, - scaffold: JSON.parse(line), - }; - } catch (error) { - return { - ok: false, - mode: command.mode, - durationMs: performance.now() - startedAt, - error: compactError(error), - }; - } -} - -function replaceTarget(event) { - return normalizeTarget(event.element || {}); -} - -function insertTarget(event) { - return { - ...normalizeTarget(event.insert?.anchor || {}), - position: event.insert?.position === 'before' ? 'before' : 'after', - }; -} - -function normalizeTarget(target) { - const classes = Array.isArray(target.classes) - ? target.classes.join(' ') - : String(target.classes || '').trim(); - const text = typeof target.textContent === 'string' - ? target.textContent.trim().slice(0, 80) - : ''; - return { - elementId: target.id || target.elementId || undefined, - classes: classes || undefined, - tag: target.tagName || target.tag || undefined, - text: text || undefined, - }; -} - -function compactError(error) { - const stderr = error?.stderr ? String(error.stderr).trim() : ''; - const message = stderr.split('\n').filter(Boolean).pop() || error?.message || 'preflight failed'; - return String(message).slice(0, 500); -} diff --git a/.agents/skills/impeccable/scripts/live/generation-publisher.mjs b/.agents/skills/impeccable/scripts/live/generation-publisher.mjs deleted file mode 100644 index d13f65bbf..000000000 --- a/.agents/skills/impeccable/scripts/live/generation-publisher.mjs +++ /dev/null @@ -1,617 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { createHash } from 'node:crypto'; -import { createLiveSessionStore } from './session-store.mjs'; -import { withSourceLockSync } from './source-lock.mjs'; -import { getLiveDir } from '../lib/impeccable-paths.mjs'; -import { - SOURCE_ARTIFACT_PREVIEW_MODE, - findSourceArtifactManifest, -} from './source-artifact.mjs'; - -export function sha256(value) { - return createHash('sha256').update(value).digest('hex'); -} - -export function reconcilePublishedSourceVariants({ current, candidate, priorArrived = 0 } = {}) { - let reconciled = String(candidate || ''); - const stable = String(current || ''); - for (let variant = 1; variant <= Number(priorArrived || 0); variant += 1) { - const stableBlock = extractVariantBlock(stable, variant); - const candidateBlock = extractVariantBlock(reconciled, variant); - if (!stableBlock || !candidateBlock) { - return failure('published_variant_missing', { variant }); - } - const offset = reconciled.indexOf(candidateBlock); - reconciled = reconciled.slice(0, offset) + stableBlock + reconciled.slice(offset + candidateBlock.length); - } - return { ok: true, content: reconciled }; -} - -export function prepareGenerationArtifact({ id, sourceFile, cwd = process.cwd() } = {}) { - if (!id) return failure('missing_session_id'); - if (!sourceFile) return failure('missing_file'); - const requestedPath = resolveInside(cwd, sourceFile); - if (!requestedPath || !fs.existsSync(requestedPath)) return failure(requestedPath ? 'source_missing' : 'path_outside_project'); - - const componentTarget = readComponentPublicationTarget(requestedPath, cwd, id); - if (componentTarget?.error) return componentTarget; - const sourceArtifactTarget = componentTarget ? null : readSourceArtifactPublicationTarget(requestedPath, cwd, id); - if (sourceArtifactTarget?.error) return sourceArtifactTarget; - const sourcePath = componentTarget?.sourcePath || sourceArtifactTarget?.sourcePath || requestedPath; - - try { - return withSourceLockSync(sourcePath, 'generation-prepare:' + id, () => { - const store = createLiveSessionStore({ cwd, sessionId: id }); - const snapshot = store.getSnapshot(id, { includeCompleted: true }); - if (!snapshot?.updatedAt) return failure('session_missing'); - if (snapshot.generationCanceled === true) { - return failure('stale_generation_epoch', { canceled: true, phase: snapshot.phase }); - } - const source = fs.readFileSync(sourcePath, 'utf-8'); - const artifactBase = sourceArtifactTarget - ? fs.readFileSync(sourceArtifactTarget.previewPath, 'utf-8') - : source; - const revision = Number(snapshot.publishedRevision || 0) + 1; - const artifactDir = path.join(getLiveDir(cwd), 'artifacts'); - if (componentTarget) { - return prepareComponentArtifact({ - id, - revision, - snapshot, - source, - sourcePath, - requestedPath, - target: componentTarget, - artifactDir, - cwd, - }); - } - const extension = path.extname(sourcePath) || '.html'; - const artifactPath = path.join(artifactDir, id + '-r' + revision + extension); - fs.mkdirSync(artifactDir, { recursive: true }); - fs.writeFileSync(artifactPath, artifactBase, 'utf-8'); - return { - ok: true, - id, - epoch: Number(snapshot.generationEpoch || 1), - revision, - sourceFile: relative(cwd, sourcePath), - ...(sourceArtifactTarget ? { - previewFile: relative(cwd, sourceArtifactTarget.previewPath), - previewMode: SOURCE_ARTIFACT_PREVIEW_MODE, - } : {}), - artifactFile: relative(cwd, artifactPath), - expectedSourceHash: sha256(source), - }; - }, { cwd }); - } catch (error) { - if (error?.code === 'SOURCE_LOCKED') return failure('source_locked'); - return failure('prepare_failed', { message: error?.message || String(error) }); - } -} - -export function publishGenerationArtifact({ - id, - epoch, - sourceFile, - artifactFile, - expectedSourceHash, - arrivedVariants, - expectedVariants, - publicationKind, - cwd = process.cwd(), -} = {}) { - if (!id) return failure('missing_session_id'); - if (!Number.isInteger(epoch) || epoch < 1) return failure('invalid_generation_epoch'); - if (!sourceFile || !artifactFile) return failure('missing_file'); - if (publicationKind && !['variants', 'params'].includes(publicationKind)) { - return failure('invalid_publication_kind'); - } - - const requestedPath = resolveInside(cwd, sourceFile); - const artifactPath = resolveInside(cwd, artifactFile); - if (!requestedPath || !artifactPath) return failure('path_outside_project'); - if (!fs.existsSync(requestedPath)) return failure('source_missing'); - if (!fs.existsSync(artifactPath)) return failure('artifact_missing'); - - const componentTarget = readComponentPublicationTarget(requestedPath, cwd, id); - if (componentTarget?.error) return componentTarget; - const sourceArtifactTarget = componentTarget ? null : readSourceArtifactPublicationTarget(requestedPath, cwd, id); - if (sourceArtifactTarget?.error) return sourceArtifactTarget; - const artifactManifest = readJson(artifactPath); - const isComponentArtifact = isComponentPreviewMode(artifactManifest?.previewMode); - if (Boolean(componentTarget) !== isComponentArtifact) { - return failure('artifact_preview_mode_mismatch'); - } - if (componentTarget && componentTarget.manifest.previewMode !== artifactManifest?.previewMode) { - return failure('artifact_preview_mode_mismatch'); - } - const sourcePath = componentTarget?.sourcePath || sourceArtifactTarget?.sourcePath || requestedPath; - - try { - return withSourceLockSync(sourcePath, 'generation:' + id + ':' + epoch, () => { - const store = createLiveSessionStore({ cwd, sessionId: id }); - const snapshot = store.getSnapshot(id, { includeCompleted: true }); - if (!snapshot?.updatedAt) return failure('session_missing'); - if (snapshot.generationCanceled === true) { - return failure('stale_generation_epoch', { canceled: true, phase: snapshot.phase }); - } - if (Number(snapshot.generationEpoch || 1) !== epoch) { - return failure('stale_generation_epoch', { expectedEpoch: snapshot.generationEpoch || 1 }); - } - - const current = fs.readFileSync(sourcePath, 'utf-8'); - const currentHash = sha256(current); - if (!expectedSourceHash || currentHash !== expectedSourceHash) { - return failure('source_hash_mismatch', { actualSourceHash: currentHash }); - } - - if (componentTarget) { - return publishComponentArtifact({ - id, - epoch, - snapshot, - target: componentTarget, - artifactManifest, - artifactPath, - sourcePath, - arrivedVariants, - expectedVariants, - publicationKind, - store, - cwd, - }); - } - - const stablePreview = sourceArtifactTarget - ? fs.readFileSync(sourceArtifactTarget.previewPath, 'utf-8') - : current; - const artifact = fs.readFileSync(artifactPath, 'utf-8'); - if (!artifact.includes('data-impeccable-variants="' + id + '"')) { - return failure('artifact_missing_session_wrapper'); - } - const delivered = countDeliveredVariants(artifact); - if (delivered < 1) return failure('artifact_has_no_variants'); - if (Number.isInteger(arrivedVariants) && delivered < arrivedVariants) { - return failure('artifact_variant_count_mismatch', { delivered }); - } - const priorArrived = Math.max(0, Number(snapshot.arrivedVariants || 0)); - for (let variant = 1; variant <= priorArrived; variant++) { - const currentVariant = extractVariantBlock(stablePreview, variant); - const artifactVariant = extractVariantBlock(artifact, variant); - if (!currentVariant || !artifactVariant) { - return failure('published_variant_missing', { variant }); - } - if (sha256(withoutVariantParams(currentVariant)) !== sha256(withoutVariantParams(artifactVariant))) { - return failure('published_variant_changed', { variant }); - } - } - const currentPreviewCss = extractPreviewCss(stablePreview, id); - const artifactPreviewCss = extractPreviewCss(artifact, id); - if (priorArrived > 0 && currentPreviewCss && !artifactPreviewCss.startsWith(currentPreviewCss)) { - return failure('published_variant_css_changed'); - } - - const commitSnapshot = store.getSnapshot(id, { includeCompleted: true }); - if (commitSnapshot?.generationCanceled === true) { - return failure('stale_generation_epoch', { canceled: true, phase: commitSnapshot.phase }); - } - if (Number(commitSnapshot?.generationEpoch || 1) !== epoch) { - return failure('stale_generation_epoch', { expectedEpoch: commitSnapshot?.generationEpoch || 1 }); - } - const artifactHash = sha256(artifact); - const publishPath = sourceArtifactTarget?.previewPath || sourcePath; - atomicReplace(publishPath, artifact); - const revision = Number(commitSnapshot.publishedRevision || 0) + 1; - store.appendEvent({ - type: 'variant_published', - id, - generationEpoch: epoch, - revision, - digest: artifactHash, - sourceFile: relative(cwd, sourcePath), - ...(sourceArtifactTarget ? { - previewFile: relative(cwd, publishPath), - previewMode: SOURCE_ARTIFACT_PREVIEW_MODE, - } : {}), - arrivedVariants: delivered, - expectedVariants: Number(expectedVariants || snapshot.expectedVariants || delivered), - publicationKind: publicationKind || 'variants', - at: Date.now(), - }); - return { - ok: true, - id, - epoch, - revision, - digest: artifactHash, - sourceFile: relative(cwd, sourcePath), - ...(sourceArtifactTarget ? { - previewFile: relative(cwd, publishPath), - previewMode: SOURCE_ARTIFACT_PREVIEW_MODE, - } : {}), - arrivedVariants: delivered, - expectedVariants: Number(expectedVariants || snapshot.expectedVariants || delivered), - publicationKind: publicationKind || 'variants', - }; - }, { cwd }); - } catch (error) { - if (error?.code === 'SOURCE_LOCKED') return failure('source_locked'); - return failure('publish_failed', { message: error?.message || String(error) }); - } -} - -function prepareComponentArtifact({ - id, - revision, - snapshot, - source, - sourcePath, - requestedPath, - target, - artifactDir, - cwd, -}) { - const artifactComponentDir = path.join( - artifactDir, - id + '-r' + revision + '-' + target.manifest.previewMode + '-' + process.pid + '-' + Date.now(), - ); - fs.mkdirSync(artifactComponentDir, { recursive: true }); - copyDirectoryFiles(target.componentPath, artifactComponentDir); - const artifactPath = path.join(artifactComponentDir, 'manifest.json'); - const artifactManifest = { - ...target.manifest, - componentDir: relative(cwd, artifactComponentDir), - }; - fs.writeFileSync(artifactPath, JSON.stringify(artifactManifest, null, 2) + '\n', 'utf-8'); - return { - ok: true, - id, - epoch: Number(snapshot.generationEpoch || 1), - revision, - sourceFile: relative(cwd, requestedPath), - targetSourceFile: relative(cwd, sourcePath), - artifactFile: relative(cwd, artifactPath), - componentDir: relative(cwd, artifactComponentDir), - previewMode: target.manifest.previewMode, - expectedSourceHash: sha256(source), - }; -} - -function publishComponentArtifact({ - id, - epoch, - snapshot, - target, - artifactManifest, - artifactPath, - sourcePath, - arrivedVariants, - expectedVariants, - publicationKind, - store, - cwd, -}) { - if (!artifactManifest || typeof artifactManifest !== 'object') { - return failure('artifact_manifest_invalid'); - } - if (artifactManifest.id !== id || target.manifest.id !== id) { - return failure('artifact_session_mismatch'); - } - const artifactComponentPath = resolveInside(cwd, artifactManifest.componentDir); - if (!artifactComponentPath || path.resolve(artifactComponentPath) !== path.dirname(artifactPath)) { - return failure('artifact_component_dir_mismatch'); - } - if (!isDescendant(path.join(getLiveDir(cwd), 'artifacts'), artifactComponentPath)) { - return failure('artifact_not_staged'); - } - const immutableMismatch = componentManifestMismatch(target.manifest, artifactManifest); - if (immutableMismatch) { - return failure('artifact_manifest_changed', { field: immutableMismatch }); - } - - const expected = Number(expectedVariants || target.manifest.count || snapshot.expectedVariants || 0); - const declared = optionalPositiveInteger(artifactManifest.arrivedVariants); - const delivered = Number.isInteger(arrivedVariants) ? arrivedVariants : declared; - if (!Number.isInteger(delivered) || delivered < 1) return failure('artifact_has_no_variants'); - if (expected > 0 && delivered > expected) { - return failure('artifact_variant_count_mismatch', { delivered, expected }); - } - if (declared !== null && declared !== delivered) { - return failure('artifact_variant_count_mismatch', { delivered: declared, expected: delivered }); - } - - const priorArrived = Math.max( - optionalPositiveInteger(target.manifest.arrivedVariants) || 0, - Number(snapshot.arrivedVariants || 0), - ); - if (delivered < priorArrived) { - return failure('artifact_variant_count_regressed', { delivered, priorArrived }); - } - - const componentExtension = target.manifest.componentExtension - || (target.manifest.previewMode === 'vue-component' ? 'vue' : 'svelte'); - const variantContents = []; - for (let variant = 1; variant <= delivered; variant++) { - const artifactVariantPath = path.join(artifactComponentPath, 'v' + variant + '.' + componentExtension); - if (!regularFileInside(artifactComponentPath, artifactVariantPath)) { - return failure('artifact_variant_missing', { variant }); - } - const content = fs.readFileSync(artifactVariantPath, 'utf-8'); - if (!content.trim()) return failure('artifact_variant_empty', { variant }); - const targetVariantPath = path.join(target.componentPath, 'v' + variant + '.' + componentExtension); - if (variant <= priorArrived && !regularFileInside(target.componentPath, targetVariantPath)) { - return failure('published_variant_missing', { variant }); - } - if (variant <= priorArrived) { - const prior = fs.readFileSync(targetVariantPath, 'utf-8'); - if (sha256(prior) !== sha256(content)) { - return failure('published_variant_changed', { variant }); - } - } - variantContents.push({ variant, content, targetPath: targetVariantPath }); - } - - const artifactParamsPath = path.join(artifactComponentPath, 'params.json'); - let paramsContent = null; - if (fs.existsSync(artifactParamsPath)) { - if (!regularFileInside(artifactComponentPath, artifactParamsPath)) { - return failure('artifact_params_invalid'); - } - paramsContent = fs.readFileSync(artifactParamsPath, 'utf-8'); - const params = parseJson(paramsContent); - if (!params || typeof params !== 'object' || Array.isArray(params)) { - return failure('artifact_params_invalid'); - } - } - - // Components and optional params become reachable before the manifest - // advertises them. Committing the manifest last makes publication atomic - // from the browser's point of view while the source lock excludes Accept. - fs.mkdirSync(target.componentPath, { recursive: true }); - for (const variant of variantContents) { - if (variant.variant > priorArrived) atomicReplace(variant.targetPath, variant.content); - } - if (paramsContent !== null) { - atomicReplace(path.join(target.componentPath, 'params.json'), paramsContent); - } - const commitSnapshot = store.getSnapshot(id, { includeCompleted: true }); - if (commitSnapshot?.generationCanceled === true) { - return failure('stale_generation_epoch', { canceled: true, phase: commitSnapshot.phase }); - } - if (Number(commitSnapshot?.generationEpoch || 1) !== epoch) { - return failure('stale_generation_epoch', { expectedEpoch: commitSnapshot?.generationEpoch || 1 }); - } - const publishedManifest = { - ...target.manifest, - componentDir: relative(cwd, target.componentPath), - arrivedVariants: delivered, - }; - delete publishedManifest.manifestPath; - const manifestContent = JSON.stringify(publishedManifest, null, 2) + '\n'; - atomicReplace(target.manifestPath, manifestContent); - - const digest = digestComponentPublication(manifestContent, variantContents, paramsContent); - const revision = Number(snapshot.publishedRevision || 0) + 1; - const sourceFile = relative(cwd, sourcePath); - const previewFile = relative(cwd, target.manifestPath); - store.appendEvent({ - type: 'variant_published', - id, - generationEpoch: epoch, - revision, - digest, - sourceFile, - previewFile, - previewMode: target.manifest.previewMode, - arrivedVariants: delivered, - expectedVariants: expected || delivered, - publicationKind: publicationKind || 'variants', - at: Date.now(), - }); - return { - ok: true, - id, - epoch, - revision, - digest, - sourceFile, - previewFile, - previewMode: target.manifest.previewMode, - componentDir: relative(cwd, target.componentPath), - arrivedVariants: delivered, - expectedVariants: expected || delivered, - publicationKind: publicationKind || 'variants', - }; -} - -const COMPONENT_MANIFEST_FIELDS = [ - 'id', - 'mode', - 'previewMode', - 'sourceFile', - 'sourceStartLine', - 'sourceEndLine', - 'insertLine', - 'position', - 'anchorStartLine', - 'anchorEndLine', - 'count', - 'propContract', - 'originalMarkup', - 'anchorMarkup', - 'runtimeModule', - 'componentModuleBase', - 'framework', - 'componentExtension', -]; - -function readComponentPublicationTarget(manifestPath, cwd, id) { - if (path.basename(manifestPath) !== 'manifest.json') return null; - const manifest = readJson(manifestPath); - if (!manifest || !isComponentPreviewMode(manifest.previewMode)) return null; - if (manifest.id !== id) return failure('artifact_session_mismatch'); - const sourcePath = resolveInside(cwd, manifest.sourceFile); - const componentPath = resolveInside(cwd, manifest.componentDir); - if (!sourcePath || !componentPath) return failure('path_outside_project'); - if (!fs.existsSync(sourcePath)) return failure('source_missing'); - if (path.resolve(componentPath) !== path.dirname(manifestPath)) { - return failure('manifest_component_dir_mismatch'); - } - return { manifest, manifestPath, sourcePath, componentPath }; -} - -function readSourceArtifactPublicationTarget(requestedPath, cwd, id) { - const manifest = findSourceArtifactManifest(id, cwd); - if (!manifest) return null; - if (path.resolve(requestedPath) !== path.resolve(manifest.previewPath)) { - return failure('source_artifact_preview_mismatch'); - } - return manifest; -} - -function componentManifestMismatch(target, artifact) { - for (const field of COMPONENT_MANIFEST_FIELDS) { - if (JSON.stringify(target[field] ?? null) !== JSON.stringify(artifact[field] ?? null)) return field; - } - return null; -} - -function isComponentPreviewMode(value) { - return value === 'svelte-component' || value === 'vue-component'; -} - -function copyDirectoryFiles(sourceDir, targetDir) { - for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) { - if (!entry.isFile() || entry.isSymbolicLink()) continue; - fs.copyFileSync(path.join(sourceDir, entry.name), path.join(targetDir, entry.name)); - } -} - -function regularFileInside(root, file) { - const rel = path.relative(root, file); - if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return false; - try { - return fs.lstatSync(file).isFile(); - } catch { - return false; - } -} - -function isDescendant(root, candidate) { - const rel = path.relative(root, candidate); - return Boolean(rel) && !rel.startsWith('..') && !path.isAbsolute(rel); -} - -function digestComponentPublication(manifestContent, variants, paramsContent) { - const hash = createHash('sha256'); - hash.update(manifestContent); - for (const variant of variants) { - hash.update('\0v' + variant.variant + '\0'); - hash.update(variant.content); - } - if (paramsContent !== null) hash.update('\0params\0' + paramsContent); - return hash.digest('hex'); -} - -function readJson(file) { - try { - return JSON.parse(fs.readFileSync(file, 'utf-8')); - } catch { - return null; - } -} - -function parseJson(value) { - try { - return JSON.parse(value); - } catch { - return null; - } -} - -function optionalPositiveInteger(value) { - const number = Number(value); - return Number.isInteger(number) && number > 0 ? number : null; -} - -function countDeliveredVariants(source) { - const matches = source.match(/]*\bdata-impeccable-variant=(?:"|')(?!original(?:"|'))[^"']+(?:"|')[^>]*>/g); - return matches?.length || 0; -} - -function extractVariantBlock(source, variant) { - const open = /]*>/gi; - let match; - let start = -1; - const attr = new RegExp("\\bdata-impeccable-variant=(?:\"" + variant + "\"|'" + variant + "')"); - while ((match = open.exec(source))) { - if (attr.test(match[0])) { - start = match.index; - break; - } - } - if (start < 0) return null; - - const token = /]*\/\s*>|]*>|<\/div\s*>/gi; - token.lastIndex = start; - let depth = 0; - while ((match = token.exec(source))) { - if (/^<\/div/i.test(match[0])) { - depth -= 1; - if (depth === 0) return source.slice(start, token.lastIndex); - } else if (!/\/\s*>$/.test(match[0])) { - depth += 1; - } - } - return null; -} - -function withoutVariantParams(block) { - return String(block || '').replace( - /\sdata-impeccable-params=(?:"[^"]*"|'[^']*')/i, - '', - ); -} - -function extractPreviewCss(source, id) { - const escapedId = String(id).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const open = new RegExp("]*\\bdata-impeccable-css=(?:\"" + escapedId + "\"|'" + escapedId + "')[^>]*>", 'i'); - const match = open.exec(source); - if (!match) return ''; - const start = match.index + match[0].length; - const end = source.indexOf('', start); - if (end < 0) return ''; - return source.slice(start, end) - .replace(/^\s*\{\s*`\s*/, '') - .replace(/\s*`\s*\}\s*$/, '') - .trim(); -} - -function atomicReplace(target, content) { - let mode = 0o666; - try { mode = fs.statSync(target).mode; } catch {} - const temp = target + '.impeccable-publish-' + process.pid + '-' + Date.now(); - try { - fs.writeFileSync(temp, content, { encoding: 'utf-8', mode }); - fs.renameSync(temp, target); - } finally { - try { fs.unlinkSync(temp); } catch {} - } -} - -function resolveInside(cwd, value) { - const resolved = path.resolve(cwd, value); - const rel = path.relative(cwd, resolved); - if (rel.startsWith('..') || path.isAbsolute(rel)) return null; - return resolved; -} - -function relative(cwd, value) { - return path.relative(cwd, value).split(path.sep).join('/'); -} - -function failure(error, details = {}) { - return { ok: false, error, ...details }; -} diff --git a/.agents/skills/impeccable/scripts/live/poll-lanes.mjs b/.agents/skills/impeccable/scripts/live/poll-lanes.mjs deleted file mode 100644 index 65f20a87a..000000000 --- a/.agents/skills/impeccable/scripts/live/poll-lanes.mjs +++ /dev/null @@ -1,14 +0,0 @@ -export function eventPriority(event = {}) { - if (event.type === 'accept' || event.type === 'discard' || event.type === 'exit') return 0; - if (event.type === 'manual_edit_apply' || event.type === 'steer' || event.type === 'carbonize_cleanup') return 1; - if (event.type === 'generate') return 2; - return 3; -} - -export function selectAvailablePendingEvent(entries, { now = Date.now(), types = null } = {}) { - const allowed = types instanceof Set ? types : (Array.isArray(types) ? new Set(types) : null); - return entries - .filter((entry) => !(entry.leaseUntil && entry.leaseUntil > now)) - .filter((entry) => !allowed || allowed.has(entry.event?.type)) - .sort((a, b) => eventPriority(a.event) - eventPriority(b.event) || a.seq - b.seq)[0] || null; -} diff --git a/.agents/skills/impeccable/scripts/live/session-store.mjs b/.agents/skills/impeccable/scripts/live/session-store.mjs index 52e41d722..affba67c9 100644 --- a/.agents/skills/impeccable/scripts/live/session-store.mjs +++ b/.agents/skills/impeccable/scripts/live/session-store.mjs @@ -3,13 +3,6 @@ import path from 'node:path'; import { getLegacyLiveSessionsDir, getLiveSessionsDir } from '../lib/impeccable-paths.mjs'; const COMPLETED_PHASES = new Set(['completed', 'discarded']); -const GENERATION_FENCED_PHASES = new Set([ - 'accept_requested', - 'discard_requested', - 'carbonize_required', - 'completed', - 'discarded', -]); export function createLiveSessionStore({ cwd = process.cwd(), sessionId } = {}) { const rootDir = getLiveSessionsDir(cwd); @@ -45,10 +38,7 @@ export function createLiveSessionStore({ cwd = process.cwd(), sessionId } = {}) if (!fs.existsSync(journalPath) && fs.existsSync(legacyJournalPath)) { fs.copyFileSync(legacyJournalPath, journalPath); } - // Publisher/complete helpers can append from a separate process while - // the server is alive. Rebuild here so sequence numbers and phase - // fences never come from a stale in-memory cache. - const prior = rebuildSnapshotFromJournal(getReadableJournalPath(normalized.id), normalized.id); + const prior = loadCachedOrRebuild(normalized.id); const seq = prior.nextSeq; const entry = { seq, @@ -126,21 +116,9 @@ function baseSnapshot(id) { pendingEvent: null, deliveryLease: null, checkpointRevision: 0, - browserCheckpointRevision: 0, - publicationCheckpointRevision: 0, activeOwner: null, sourceMarkers: {}, fallbackMode: null, - generationPhase: null, - generationTimings: {}, - generationEpoch: 1, - publishedRevision: 0, - deliveredVariants: {}, - variantPlan: null, - paramsPublished: false, - generationCanceled: false, - generationCanceledAt: null, - cancelReason: null, annotationArtifacts: [], diagnostics: [], updatedAt: null, @@ -180,9 +158,6 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) { ...snapshot, paramValues: { ...(snapshot.paramValues || {}) }, sourceMarkers: { ...(snapshot.sourceMarkers || {}) }, - generationTimings: { ...(snapshot.generationTimings || {}) }, - deliveredVariants: { ...(snapshot.deliveredVariants || {}) }, - variantPlan: snapshot.variantPlan || null, annotationArtifacts: [...(snapshot.annotationArtifacts || [])], diagnostics: [...(snapshot.diagnostics || [])], updatedAt: entry.ts || new Date().toISOString(), @@ -195,81 +170,14 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) { switch (event.type) { case 'generate': next.phase = 'generate_requested'; - next.generationEpoch = Number(event.generationEpoch || next.generationEpoch || 1); next.pageUrl = event.pageUrl ?? next.pageUrl; next.expectedVariants = event.count ?? next.expectedVariants; next.pendingEventSeq = entry.seq ?? next.pendingEventSeq; next.pendingEvent = toPendingEvent(event); - next.variantPlan = null; if (event.screenshotPath) upsertArtifact(next.annotationArtifacts, { type: 'screenshot', path: event.screenshotPath }); break; - case 'variant_plan': - if (!next.generationCanceled && !GENERATION_FENCED_PHASES.has(next.phase)) { - next.variantPlan = event.plan ?? next.variantPlan; - } - break; - case 'detector_waivers': - if (!next.generationCanceled && !GENERATION_FENCED_PHASES.has(next.phase)) { - next.detectorWaivers = [ - ...(next.detectorWaivers || []), - ...(Array.isArray(event.waivers) ? event.waivers : []), - ]; - } - break; - case 'variant_published': - if (next.generationCanceled || GENERATION_FENCED_PHASES.has(next.phase)) { - next.diagnostics.push({ - error: 'late_generation_event_ignored', - type: event.type, - phase: next.phase, - revision: event.revision ?? null, - }); - break; - } - if (Number(event.generationEpoch || 0) !== Number(next.generationEpoch || 1)) { - next.diagnostics.push({ - error: 'stale_generation_epoch_ignored', - epoch: event.generationEpoch ?? null, - expectedEpoch: next.generationEpoch || 1, - }); - break; - } - next.phase = 'variants_progress'; - next.publishedRevision = Math.max(next.publishedRevision || 0, Number(event.revision || 0)); - next.arrivedVariants = Math.max(next.arrivedVariants || 0, Number(event.arrivedVariants || 0)); - next.expectedVariants = Number(event.expectedVariants || next.expectedVariants || 0); - if (event.publicationKind === 'params') next.paramsPublished = true; - next.sourceFile = event.sourceFile ?? next.sourceFile; - next.previewFile = event.previewFile ?? next.previewFile; - next.previewMode = event.previewMode ?? next.previewMode; - if (event.revision) { - next.deliveredVariants[String(event.revision)] = { - digest: event.digest || null, - arrivedVariants: Number(event.arrivedVariants || 0), - publishedAt: event.at || null, - }; - } - break; - case 'agent_phase': - next.generationPhase = event.phase ?? next.generationPhase; - if (event.phase) { - next.generationTimings[event.phase] = { - at: event.at ?? (Date.parse(entry.ts || '') || null), - durationMs: event.durationMs ?? null, - }; - } - break; case 'variants_ready': case 'agent_done': - if ((next.generationCanceled || GENERATION_FENCED_PHASES.has(next.phase)) - && !(event.type === 'agent_done' && event.carbonize === true && next.phase === 'accept_requested')) { - next.diagnostics.push({ - error: 'late_generation_event_ignored', - type: event.type, - phase: next.phase, - }); - break; - } next.phase = event.carbonize === true ? 'carbonize_required' : 'variants_ready'; next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile; next.previewFile = event.previewFile ?? next.previewFile; @@ -286,45 +194,27 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) { } break; case 'checkpoint': - if (next.generationCanceled || GENERATION_FENCED_PHASES.has(next.phase)) { + if (COMPLETED_PHASES.has(next.phase)) { next.diagnostics.push({ error: 'checkpoint_after_terminal_ignored', phase: event.phase ?? null, revision: event.revision ?? null }); break; } - { - const revisionDomain = event.revisionDomain === 'publication' - || (event.reason === 'variants_progress' && !event.owner) - ? 'publication' - : 'browser'; - const revisionField = revisionDomain === 'publication' - ? 'publicationCheckpointRevision' - : 'browserCheckpointRevision'; - const currentRevision = next[revisionField] - ?? (revisionDomain === 'browser' ? next.checkpointRevision : 0) - ?? 0; - if ((event.revision ?? 0) >= currentRevision) { - next.phase = event.phase ?? next.phase; - next[revisionField] = event.revision ?? currentRevision; - if (revisionDomain === 'browser') { - next.checkpointRevision = event.revision ?? next.checkpointRevision; - next.activeOwner = event.owner ?? next.activeOwner; - } - next.arrivedVariants = event.arrivedVariants ?? next.arrivedVariants; - if (revisionDomain === 'browser') next.visibleVariant = event.visibleVariant ?? next.visibleVariant; - next.sourceFile = event.sourceFile ?? next.sourceFile; - next.previewFile = event.previewFile ?? next.previewFile; - next.previewMode = event.previewMode ?? next.previewMode; - if (revisionDomain === 'browser' && event.paramValues) next.paramValues = { ...event.paramValues }; - } else { - next.diagnostics.push({ error: 'stale_checkpoint_ignored', revision: event.revision, revisionDomain }); - } + if ((event.revision ?? 0) >= (next.checkpointRevision ?? 0)) { + next.phase = event.phase ?? next.phase; + next.checkpointRevision = event.revision ?? next.checkpointRevision; + next.activeOwner = event.owner ?? next.activeOwner; + next.arrivedVariants = event.arrivedVariants ?? next.arrivedVariants; + next.visibleVariant = event.visibleVariant ?? next.visibleVariant; + next.sourceFile = event.sourceFile ?? next.sourceFile; + next.previewFile = event.previewFile ?? next.previewFile; + next.previewMode = event.previewMode ?? next.previewMode; + if (event.paramValues) next.paramValues = { ...event.paramValues }; + } else { + next.diagnostics.push({ error: 'stale_checkpoint_ignored', revision: event.revision }); } break; case 'accept': case 'accept_intent': next.phase = 'accept_requested'; - next.generationCanceled = true; - next.generationCanceledAt = event.at ?? (Date.parse(entry.ts || '') || Date.now()); - next.cancelReason = 'accept'; next.visibleVariant = Number(event.variantId ?? next.visibleVariant); if (event.paramValues) next.paramValues = { ...event.paramValues }; next.pendingEventSeq = entry.seq ?? next.pendingEventSeq; @@ -342,12 +232,6 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) { next.pendingEventSeq = entry.seq ?? next.pendingEventSeq; next.pendingEvent = toPendingEvent(event); break; - case 'carbonize_cleanup': - next.phase = 'carbonize_cleanup_requested'; - next.sourceFile = event.file ?? next.sourceFile; - next.pendingEventSeq = entry.seq ?? next.pendingEventSeq; - next.pendingEvent = toPendingEvent(event); - break; case 'steer_done': next.phase = 'steer_done'; next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile; @@ -359,9 +243,6 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) { break; case 'discard': next.phase = 'discard_requested'; - next.generationCanceled = true; - next.generationCanceledAt = event.at ?? (Date.parse(entry.ts || '') || Date.now()); - next.cancelReason = 'discard'; next.pendingEventSeq = entry.seq ?? next.pendingEventSeq; next.pendingEvent = toPendingEvent(event); break; @@ -379,10 +260,6 @@ function applyEvent(snapshot, entry, inheritedDiagnostics = []) { next.pendingEvent = null; break; case 'agent_error': - if (next.generationCanceled && event.sourceEventType === 'generate') { - next.diagnostics.push({ error: 'late_generation_event_ignored', type: event.type, phase: next.phase }); - break; - } next.phase = 'agent_error'; next.pendingEventSeq = null; next.pendingEvent = null; diff --git a/.agents/skills/impeccable/scripts/live/source-artifact.mjs b/.agents/skills/impeccable/scripts/live/source-artifact.mjs deleted file mode 100644 index 53f9bff3b..000000000 --- a/.agents/skills/impeccable/scripts/live/source-artifact.mjs +++ /dev/null @@ -1,76 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; - -import { getLiveDir } from '../lib/impeccable-paths.mjs'; - -export const SOURCE_ARTIFACT_PREVIEW_MODE = 'source-artifact'; - -export function scaffoldSourceArtifactSession({ - id, - count, - sourceFile, - sourceStartLine, - sourceEndLine, - originalSource, - previewContent, - cwd = process.cwd(), -} = {}) { - if (!/^[A-Za-z0-9_-]{1,128}$/.test(String(id || ''))) { - throw new Error('invalid source artifact session id'); - } - const sourcePath = resolveInside(cwd, sourceFile); - if (!sourcePath || !fs.existsSync(sourcePath)) throw new Error('source artifact target missing'); - - const sessionDir = path.join(getLiveDir(cwd), 'previews', id); - const extension = path.extname(sourcePath) || '.html'; - const previewPath = path.join(sessionDir, 'preview' + extension); - const manifestPath = path.join(sessionDir, 'manifest.json'); - fs.mkdirSync(sessionDir, { recursive: true }); - - const manifest = { - id, - count: Number(count || 1), - previewMode: SOURCE_ARTIFACT_PREVIEW_MODE, - sourceFile: relative(cwd, sourcePath), - previewFile: relative(cwd, previewPath), - sourceStartLine: Number(sourceStartLine), - sourceEndLine: Number(sourceEndLine), - originalSource: String(originalSource || ''), - }; - fs.writeFileSync(previewPath, String(previewContent || ''), 'utf-8'); - fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8'); - return { ...manifest, manifestFile: relative(cwd, manifestPath), sessionDir: relative(cwd, sessionDir) }; -} - -export function findSourceArtifactManifest(id, cwd = process.cwd()) { - if (!/^[A-Za-z0-9_-]{1,128}$/.test(String(id || ''))) return null; - const manifestPath = path.join(getLiveDir(cwd), 'previews', id, 'manifest.json'); - let manifest; - try { manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); } catch { return null; } - if (manifest?.id !== id || manifest?.previewMode !== SOURCE_ARTIFACT_PREVIEW_MODE) return null; - const sourcePath = resolveInside(cwd, manifest.sourceFile); - const previewPath = resolveInside(cwd, manifest.previewFile); - if (!sourcePath || !previewPath || !fs.existsSync(sourcePath) || !fs.existsSync(previewPath)) return null; - return { ...manifest, manifestPath, sourcePath, previewPath }; -} - -export function removeSourceArtifactSession(id, cwd = process.cwd()) { - if (!/^[A-Za-z0-9_-]{1,128}$/.test(String(id || ''))) return false; - const sessionDir = path.join(getLiveDir(cwd), 'previews', id); - if (!fs.existsSync(sessionDir)) return false; - fs.rmSync(sessionDir, { recursive: true, force: true }); - return true; -} - -function resolveInside(cwd, value) { - if (!value || typeof value !== 'string') return null; - const root = path.resolve(cwd); - const resolved = path.resolve(root, value); - const rel = path.relative(root, resolved); - if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return null; - return resolved; -} - -function relative(cwd, value) { - return path.relative(cwd, value).split(path.sep).join('/'); -} diff --git a/.agents/skills/impeccable/scripts/live/source-lock.mjs b/.agents/skills/impeccable/scripts/live/source-lock.mjs deleted file mode 100644 index dd82989bd..000000000 --- a/.agents/skills/impeccable/scripts/live/source-lock.mjs +++ /dev/null @@ -1,56 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { createHash } from 'node:crypto'; -import { getLiveDir } from '../lib/impeccable-paths.mjs'; - -const STALE_LOCK_MS = 60_000; - -export function sourceLockPath(file, cwd = process.cwd()) { - const digest = createHash('sha256').update(path.resolve(cwd, file)).digest('hex').slice(0, 24); - return path.join(getLiveDir(cwd), 'locks', digest + '.lock'); -} - -export function withSourceLockSync(file, owner, fn, { - cwd = process.cwd(), - waitMs = 0, - retryMs = 5, -} = {}) { - const lockPath = sourceLockPath(file, cwd); - fs.mkdirSync(path.dirname(lockPath), { recursive: true }); - const deadline = Date.now() + Math.max(0, Number(waitMs) || 0); - let fd; - while (fd === undefined) { - clearStaleLock(lockPath); - try { - fd = fs.openSync(lockPath, 'wx'); - fs.writeFileSync(fd, JSON.stringify({ owner, pid: process.pid, at: Date.now(), file: path.resolve(cwd, file) }) + '\n'); - } catch (error) { - if (error?.code !== 'EEXIST') throw error; - if (Date.now() >= deadline) { - const locked = new Error('source_locked'); - locked.code = 'SOURCE_LOCKED'; - locked.lockPath = lockPath; - throw locked; - } - sleepSync(Math.max(1, Math.min(Number(retryMs) || 5, deadline - Date.now()))); - } - } - - try { - return fn(); - } finally { - try { if (fd !== undefined) fs.closeSync(fd); } catch {} - try { fs.unlinkSync(lockPath); } catch {} - } -} - -function sleepSync(ms) { - Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); -} - -function clearStaleLock(lockPath) { - try { - const stat = fs.statSync(lockPath); - if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) fs.unlinkSync(lockPath); - } catch {} -} diff --git a/.agents/skills/impeccable/scripts/live/vue-component.mjs b/.agents/skills/impeccable/scripts/live/vue-component.mjs deleted file mode 100644 index c8f4d0825..000000000 --- a/.agents/skills/impeccable/scripts/live/vue-component.mjs +++ /dev/null @@ -1,343 +0,0 @@ -/** - * Nuxt/Vue live-mode component previews. - * - * Generation writes real Vue SFCs into a generated app-local module tree. - * Nuxt/Vite compiles those modules without touching the active route; Accept - * is the only operation that writes the user's .vue source. - */ - -import fs from 'node:fs'; -import path from 'node:path'; - -const NUXT_CONFIG_RE = /^nuxt\.config\.(?:js|mjs|cjs|ts|mts|cts)$/; - -export function detectNuxtVueProject(cwd = process.cwd()) { - const configFile = fs.readdirSync(cwd, { withFileTypes: true }) - .find((entry) => entry.isFile() && NUXT_CONFIG_RE.test(entry.name))?.name; - if (!configFile) return null; - const config = fs.readFileSync(path.join(cwd, configFile), 'utf-8'); - const srcDirMatch = config.match(/\bsrcDir\s*:\s*(['"])([^'"]+)\1/); - let appDir = fs.existsSync(path.join(cwd, 'app')) ? 'app' : ''; - if (srcDirMatch) { - const candidate = path.posix.normalize(srcDirMatch[2].replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '')); - if (candidate !== '..' && !candidate.startsWith('../') && !path.isAbsolute(candidate)) { - appDir = candidate === '.' ? '' : candidate; - } - } - const componentRoot = [appDir, '.impeccable-live'].filter(Boolean).join('/'); - return { configFile, appDir, componentRoot }; -} - -export function shouldUseVueComponentInjection(filePath, cwd = process.cwd()) { - if (/^(0|false|no)$/i.test(process.env.IMPECCABLE_LIVE_VUE_COMPONENT || '')) return false; - return path.extname(filePath).toLowerCase() === '.vue' && !!detectNuxtVueProject(cwd); -} - -export function vueComponentSessionDir(id, cwd = process.cwd()) { - const project = detectNuxtVueProject(cwd); - if (!project) throw new Error('Nuxt project not found'); - return path.join(cwd, project.componentRoot, id); -} - -export function vueManifestPathForSession(id, cwd = process.cwd()) { - return path.join(vueComponentSessionDir(id, cwd), 'manifest.json'); -} - -function ensureVueRuntime(cwd = process.cwd()) { - const project = detectNuxtVueProject(cwd); - if (!project) throw new Error('Nuxt project not found'); - const rel = `${project.componentRoot}/__runtime.js`; - const file = path.join(cwd, rel); - fs.mkdirSync(path.dirname(file), { recursive: true }); - const source = `import { createApp } from 'vue';\n\nexport function mount(Component, options = {}) {\n const app = createApp(Component, options.props || {});\n app.mount(options.target);\n return app;\n}\n\nexport async function unmount(app) {\n app?.unmount?.();\n}\n`; - if (!fs.existsSync(file) || fs.readFileSync(file, 'utf-8') !== source) fs.writeFileSync(file, source, 'utf-8'); - return nuxtViteFsModulePath(file, cwd); -} - -/** - * Nuxt mounts Vite beneath its build-assets base (normally `/_nuxt/`). - * Keep the manifest path base-agnostic and let the browser prepend the - * runtime's actual buildAssetsDir. A page-route URL such as - * `/app/.impeccable-live/x.vue` is handled by Nitro and returns HTML. - */ -export function nuxtViteFsModulePath(file, cwd = process.cwd()) { - const absolute = path.resolve(cwd, file).split(path.sep).join('/'); - const relative = path.relative(cwd, absolute); - if (relative.startsWith('..') || path.isAbsolute(relative)) { - throw new Error('Nuxt live module must stay inside the project root'); - } - return '/@fs/' + absolute.replace(/^\/+/, ''); -} - -export function extractVueExpressions(markup) { - const out = []; - const seen = new Set(); - const re = /\{\{\s*([^{}]+?)\s*\}\}/g; - let match; - while ((match = re.exec(String(markup || '')))) { - const expr = match[1].trim(); - if (!expr || seen.has(expr)) continue; - seen.add(expr); - out.push({ expr, token: match[0] }); - } - return out; -} - -function buildVuePropContract(expressions) { - return expressions.map(({ expr, token }, index) => ({ - prop: derivePropName(expr, index), - expr, - placeholder: token, - // DOMParser sees Vue interpolation `{{ user.name }}` as text containing - // the inner `{ user.name }` token; preserve its whitespace for the - // browser's source-text → rendered-text map. - previewToken: token.slice(1, -1), - })); -} - -function derivePropName(expr, index) { - const tail = expr.match(/(?:^|\.|\[)([A-Za-z_$][\w$]*)\s*\]?$/); - return tail?.[1] || `prop${index}`; -} - -function substituteVueExpressions(markup, contract) { - let out = String(markup || ''); - for (const entry of contract) out = out.split(entry.placeholder).join(`{{ ${entry.prop} }}`); - return out; -} - -function buildVueVariantStub(variant, markup, contract) { - const props = contract.length > 0 - ? `\n\n` - : ''; - return `${props}\n\n\n`; -} - -export function scaffoldVueComponentSession({ - id, - count, - sourceFile, - sourceStartLine, - sourceEndLine, - originalLines, - cwd = process.cwd(), -}) { - const runtimeModule = ensureVueRuntime(cwd); - const dir = vueComponentSessionDir(id, cwd); - fs.mkdirSync(dir, { recursive: true }); - const originalMarkup = originalLines.join('\n'); - const propContract = buildVuePropContract(extractVueExpressions(originalMarkup)); - const previewMarkup = substituteVueExpressions(originalMarkup, propContract); - const manifest = { - id, - previewMode: 'vue-component', - framework: 'vue', - componentExtension: 'vue', - sourceFile: sourceFile.split(path.sep).join('/'), - sourceStartLine, - sourceEndLine, - count, - propContract, - originalMarkup, - componentDir: path.relative(cwd, dir).split(path.sep).join('/'), - componentModuleBase: nuxtViteFsModulePath(dir, cwd), - runtimeModule, - }; - fs.writeFileSync(path.join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8'); - for (let variant = 1; variant <= count; variant++) { - const file = path.join(dir, `v${variant}.vue`); - if (!fs.existsSync(file)) fs.writeFileSync(file, buildVueVariantStub(variant, previewMarkup, propContract), 'utf-8'); - } - return { - manifest, - manifestFile: path.relative(cwd, path.join(dir, 'manifest.json')).split(path.sep).join('/'), - componentDir: manifest.componentDir, - propContract, - }; -} - -export function findVueComponentManifest(id, cwd = process.cwd()) { - let direct; - try { direct = vueManifestPathForSession(id, cwd); } catch { return null; } - if (!fs.existsSync(direct)) return null; - try { - const manifest = JSON.parse(fs.readFileSync(direct, 'utf-8')); - return manifest?.id === id && manifest?.previewMode === 'vue-component' - ? { ...manifest, manifestPath: direct } - : null; - } catch { - return null; - } -} - -function parseVueSfc(source) { - const text = String(source || ''); - const template = text.match(/]*>([\s\S]*?)<\/template\s*>/i)?.[1]?.trim() || ''; - const style = text.match(/]*>([\s\S]*?)<\/style\s*>/i)?.[1]?.trim() || ''; - return { template, cssLines: style ? style.split('\n').map((line) => line.trimEnd()) : [] }; -} - -function restoreVueExpressions(markup, contract) { - let out = String(markup || ''); - for (const entry of contract || []) { - out = out.replace(new RegExp(`\\{\\{\\s*${escapeRegExp(entry.prop)}\\s*\\}\\}`, 'g'), entry.placeholder); - } - return out; -} - -export function inlineVueComponentAccept(manifest, variantNum, cwd = process.cwd()) { - const sourcePath = resolveInside(cwd, manifest.sourceFile); - const componentDir = resolveInside(cwd, manifest.componentDir); - const variantPath = componentDir && path.join(componentDir, `v${variantNum}.vue`); - const resultBase = { - file: manifest.sourceFile, - sourceFile: manifest.sourceFile, - previewMode: 'vue-component', - componentDir: manifest.componentDir, - carbonize: false, - }; - if (!sourcePath || !componentDir || !variantPath || !fs.existsSync(sourcePath) || !fs.existsSync(variantPath)) { - return { handled: false, error: `Variant ${variantNum} not found`, ...resultBase }; - } - const { template, cssLines } = parseVueSfc(fs.readFileSync(variantPath, 'utf-8')); - if (!template) return { handled: false, error: 'Accepted Vue variant has no template', ...resultBase }; - if (/\bdata-impeccable-[\w-]*\s*=/.test(template)) { - return { handled: false, error: 'Accepted Vue variant contains preview-only attributes', ...resultBase }; - } - - const sourceLines = fs.readFileSync(sourcePath, 'utf-8').split('\n'); - const start = Number(manifest.sourceStartLine) - 1; - const end = Number(manifest.sourceEndLine) - 1; - if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end >= sourceLines.length) { - return { handled: false, error: 'Invalid source line range for ' + manifest.sourceFile, ...resultBase }; - } - const indent = sourceLines[start].match(/^(\s*)/)?.[1] || ''; - const mergedTemplate = mergeOriginalVueAttrs(template, manifest.originalMarkup || ''); - const markupLines = restoreVueExpressions(mergedTemplate, manifest.propContract) - .split('\n') - .map((line) => line.trim() ? indent + line.trimStart() : ''); - let next = [...sourceLines.slice(0, start), ...markupLines, ...sourceLines.slice(end + 1)]; - const meaningfulCss = cssLines.filter((line) => line.trim() && !/^\/\*\s*Variant \d+:/.test(line.trim())); - if (meaningfulCss.length > 0) next = appendVueStyle(next, meaningfulCss); - fs.writeFileSync(sourcePath, next.join('\n'), 'utf-8'); - retireVueComponentSession(manifest.id, cwd); - return { handled: true, ...resultBase }; -} - -function appendVueStyle(lines, cssLines) { - let close = -1; - for (let index = lines.length - 1; index >= 0; index--) { - if (/<\/style\s*>/.test(lines[index])) { close = index; break; } - } - const block = ['', ...cssLines.map((line) => line.trim() ? ' ' + line.trimStart() : '')]; - if (close < 0) return [...lines, '', '']; - return [...lines.slice(0, close), ...block, ...lines.slice(close)]; -} - -function mergeOriginalVueAttrs(markup, originalMarkup) { - const variant = matchOpeningTag(markup); - const original = matchOpeningTag(originalMarkup); - if (!variant || !original || variant.tag.toLowerCase() !== original.tag.toLowerCase()) return markup; - const variantAttrs = parseStaticAttrs(variant.attrs); - const originalAttrs = parseStaticAttrs(original.attrs); - const additions = []; - let attrs = variant.attrs; - - const originalClass = originalAttrs.get('class'); - const variantClass = variantAttrs.get('class'); - if (originalClass && variantClass) { - const classes = [ - ...variantClass.value.split(/\s+/), - ...originalClass.value.split(/\s+/), - ].filter(Boolean); - const replacement = `class=${variantClass.quote}${[...new Set(classes)].join(' ')}${variantClass.quote}`; - attrs = attrs.slice(0, variantClass.start) + replacement + attrs.slice(variantClass.end); - } else if (originalClass) { - additions.push(originalClass.raw); - } - for (const [name, attr] of originalAttrs) { - if (name === 'class' || variantAttrs.has(name)) continue; - additions.push(attr.raw); - } - const open = `<${variant.tag}${attrs}${additions.map((attr) => ' ' + attr.trim()).join('')}${variant.close}`; - return markup.slice(0, variant.index) + open + markup.slice(variant.index + variant.raw.length); -} - -function matchOpeningTag(markup) { - const match = String(markup || '').match(/<([A-Za-z][\w:-]*)([^>]*?)(\/?>)/); - return match ? { - raw: match[0], - tag: match[1], - attrs: match[2] || '', - close: match[3], - index: match.index || 0, - } : null; -} - -function parseStaticAttrs(attrs) { - const out = new Map(); - const re = /([A-Za-z_:][\w:.-]*)\s*=\s*(["'])(.*?)\2/g; - let match; - while ((match = re.exec(attrs))) { - out.set(match[1], { - raw: match[0], - value: match[3], - quote: match[2], - start: match.index, - end: match.index + match[0].length, - }); - } - return out; -} - -export function removeVueComponentSession(id, cwd = process.cwd()) { - try { fs.rmSync(vueComponentSessionDir(id, cwd), { recursive: true, force: true }); } catch { /* best effort */ } -} - -/** - * Make an accepted/discarded session undiscoverable immediately while keeping - * Vue modules that Vite has in its graph alive until Live shuts down. Deleting - * an imported SFC mid-session makes Nuxt's HMR client attempt to reload a - * missing module and emit a console error. The generated directory remains - * ignored and removeAllVueComponentSessions removes it on server shutdown. - */ -export function retireVueComponentSession(id, cwd = process.cwd()) { - let dir; - try { dir = vueComponentSessionDir(id, cwd); } catch { return; } - for (const name of ['manifest.json', 'params.json']) { - try { fs.rmSync(path.join(dir, name), { force: true }); } catch { /* best effort */ } - } -} - -export function removeAllVueComponentSessions(cwd = process.cwd()) { - const project = detectNuxtVueProject(cwd); - if (!project) return; - const root = path.join(cwd, project.componentRoot); - if (!fs.existsSync(root)) return; - fs.rmSync(root, { recursive: true, force: true }); -} - -export function buildVueComponentCssAuthoring(count) { - return { - mode: 'vue-component', - count, - requirements: [ - 'Write each variant as a real Vue SFC in componentDir/vN.vue.', - 'Keep one root element inside