mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
Almost none of this branch's Live work was actually Codex-specific. The publisher,
the fences, the source locks and the browser's partial-arrival UI are plain node
and DOM with zero provider references, and the progressive E2E already passes on
five frameworks driven by a non-Codex agent. The Codex-only part was policy prose
and one frontmatter line, so Claude Code shipped the progressive browser UI it
could never trigger.
Progressive delivery, Codex and Claude Code:
- Add a `live-progressive` capability tag and opt codex, agents, and claude-code
in. A provider block takes one tag, so naming harnesses would have meant
duplicating the recipe per tag; a capability reads better than a provider list
anyway. Cursor and everyone else keep the atomic path until their poll loop is
known not to stall on the extra publish calls.
- Claude Code publishes variant 1 as soon as it validates rather than waiting to
write the whole trio in one edit. Nothing about the arrival path needed
changing: the publisher writes, framework HMR pushes, and the browser's
MutationObserver counts variants. The parent conversation was never in that
path, which is why Claude Code's lack of subagent progress streaming does not
matter here.
Generator subagent:
- Drop `providers: codex` from impeccable-live-generator. The build already maps
its frontmatter correctly for Claude Code, and impeccable-manual-edit-applier
has shipped to .claude/agents/ this way all along.
- The reason differs per harness, so the reference says so: Codex delegates to
unblock a foreground poll, Claude Code delegates to keep a long session's
screenshots and variant CSS out of the main context. Follows the existing
manual-edit-applier convention: both agent names, and an inline fallback when
native subagents are unavailable.
Fixes found on the way:
- The two publish commands hardcoded `.agents/skills/impeccable/scripts/` while
the other thirteen commands in live.md use {{scripts_path}}. Correct only for
the Codex repo-skills bundle; it would have pointed Claude Code at a directory
its install never creates. The shipped .codex variant was already internally
inconsistent. Now covered by a test.
- `--agent=codex` resolved to the canned fake agent, because the flag parsed as
`x === 'llm' ? 'llm' : 'fake'`. The private evals Live runner passes exactly
that, so a real-harness run would have scored deterministic stub variants and
reported them as Codex output. Unknown values for --agent, --scenario and
--delivery now fail loudly.
- live-reference tests now compile with each provider's real providerTags instead
of hand-written lists, so a providers.js misconfiguration fails in tests rather
than shipping.
Verified: progressive E2E green on vite8-react-plain against a real Vite server
and Chromium; every provider variant's publish and poll paths now agree; Cursor
and Gemini still compile to atomic only.
Prepared with AI assistance under maintainer direction.
Co-Authored-By: Claude <noreply@anthropic.com>
248 lines
13 KiB
JavaScript
248 lines
13 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { compileProviderBlocks } from '../scripts/lib/utils.js';
|
|
import { PROVIDERS } from '../scripts/lib/transformers/providers.js';
|
|
|
|
const ROOT = process.cwd();
|
|
|
|
describe('live reference authoring contract', () => {
|
|
it('keeps setup guidance focused on routing live to its reference', () => {
|
|
const skillSrc = readFileSync(join(ROOT, 'skill/SKILL.src.md'), 'utf-8');
|
|
const liveMd = readFileSync(join(ROOT, 'skill/reference/live.md'), 'utf-8');
|
|
|
|
assert.match(skillSrc, /If the user invoked a sub-command[\s\S]*?reference\/<command>\.md/);
|
|
assert.doesNotMatch(skillSrc, /Use this same scripts directory for all Impeccable helper commands/);
|
|
assert.doesNotMatch(skillSrc, /walk upward for the nearest project `\.agents`, `\.claude`, or `\.cursor` skill/);
|
|
assert.doesNotMatch(skillSrc, /## Context diagnostics/);
|
|
assert.doesNotMatch(liveMd, /walk upward for the nearest project `\.agents`, `\.claude`, or `\.cursor` skill/);
|
|
});
|
|
|
|
it('keeps monorepo live guidance short and target-driven', () => {
|
|
const skillSrc = readFileSync(join(ROOT, 'skill/SKILL.src.md'), 'utf-8');
|
|
const liveMd = readFileSync(join(ROOT, 'skill/reference/live.md'), 'utf-8');
|
|
|
|
assert.match(skillSrc, /If the user invoked a sub-command[\s\S]*?reference\/<command>\.md/);
|
|
assert.doesNotMatch(skillSrc, /TARGET_SELECTION_REQUIRED/);
|
|
assert.doesNotMatch(skillSrc, /productStatus/);
|
|
assert.doesNotMatch(skillSrc, /designStatus/);
|
|
assert.match(liveMd, /infer the concrete path and run `node \{\{scripts_path\}\}\/live\.mjs --target <path>` instead/);
|
|
assert.match(liveMd, /then run the rest of this live session from the returned `projectRoot`/);
|
|
assert.doesNotMatch(liveMd, /target_selection_required/);
|
|
assert.doesNotMatch(liveMd, /rerun with the chosen app path as `--target`/);
|
|
assert.doesNotMatch(liveMd, /productStatus/);
|
|
assert.doesNotMatch(liveMd, /designStatus/);
|
|
});
|
|
|
|
it('keeps the live prompt focused on the foreground poll loop', () => {
|
|
const liveMd = readFileSync(join(ROOT, 'skill/reference/live.md'), 'utf-8');
|
|
const generationAgentMd = readFileSync(join(ROOT, 'skill/agents/impeccable-live-generator.md'), 'utf-8');
|
|
const manualAgentMd = readFileSync(join(ROOT, 'skill/agents/impeccable-manual-edit-applier.md'), 'utf-8');
|
|
const openingContract = liveMd.split('\n').slice(0, 60).join('\n');
|
|
|
|
assert.match(liveMd, /1\. `live\.mjs`: boot\./);
|
|
assert.match(liveMd, /3\. Poll loop with the default long timeout \(600000 ms\)\. Run `live-poll\.mjs` again immediately.*Codex runs this one-shot poll in the foreground\./);
|
|
assert.match(openingContract, /## Poll loop/);
|
|
assert.match(openingContract, /No step skipped, no step reordered\./);
|
|
assert.doesNotMatch(liveMd, /live-copy-edits\.md/);
|
|
assert.doesNotMatch(liveMd, /IMPECCABLE_LIVE_COPY_AGENT|mock/);
|
|
assert.match(liveMd, /"manual_edit_apply" → Handle Manual Edit Apply/);
|
|
assert.match(liveMd, /## Handle `manual_edit_apply`/);
|
|
assert.match(openingContract, /Codex.*one-shot poll in a \*\*yielded foreground exec session\*\*/);
|
|
assert.doesNotMatch(openingContract, /dedicated app-server generation lane by default/);
|
|
assert.doesNotMatch(liveMd, /app-server|IMPECCABLE_LIVE_CODEX_WORKER|codexWorker/);
|
|
assert.ok(
|
|
liveMd.indexOf('## Handle `manual_edit_apply`') > liveMd.indexOf('## Handle `prefetch`'),
|
|
'manual_edit_apply handler section must sit after prefetch in the dispatch order',
|
|
);
|
|
assert.ok(
|
|
liveMd.indexOf('## Handle `manual_edit_apply`') < liveMd.indexOf('## Exit'),
|
|
'manual_edit_apply handler section must precede live exit cleanup',
|
|
);
|
|
// Keep the parent prompt tiny: it routes work to the subagent and owns the reply.
|
|
assert.match(liveMd, /The user already clicked Apply\. Do not ask what to do/);
|
|
assert.match(liveMd, /delegate source edits to `impeccable_manual_edit_applier`/);
|
|
assert.match(liveMd, /The subagent must not poll or reply/);
|
|
assert.match(liveMd, /parent live thread keeps the foreground poll loop/);
|
|
assert.match(liveMd, /delegate to the low-effort `impeccable_live_generator` agent/);
|
|
assert.match(liveMd, /Do not paste this full reference into the handoff/);
|
|
assert.match(generationAgentMd, /codex-name: impeccable_live_generator/);
|
|
assert.match(generationAgentMd, /effort: low/);
|
|
// The generator ships to every harness with an agent format, not just Codex:
|
|
// Codex delegates to unblock its foreground poll, Claude Code delegates to
|
|
// keep a long session's screenshots and variant CSS out of the main context.
|
|
assert.doesNotMatch(
|
|
generationAgentMd,
|
|
/^providers:/m,
|
|
'the live generator must not be gated to one harness',
|
|
);
|
|
assert.match(generationAgentMd, /Never poll, Accept, Discard/);
|
|
assert.match(generationAgentMd, /Publish the first reviewable result/);
|
|
assert.match(generationAgentMd, /preserve every already-published variant byte-for-byte/i);
|
|
assert.match(liveMd, /live-accept\.mjs --page-url PAGE_URL/);
|
|
assert.match(liveMd, /If `repair` is present/);
|
|
assert.match(liveMd, /Fix the current source/);
|
|
assert.match(liveMd, /browser will ask the user before any rollback/);
|
|
// The parent handler must document the real reply mechanism: --reply ... --data <json>.
|
|
// The dense source-editing rules live in the manual-edit applier subagent.
|
|
assert.match(liveMd, /--reply EVENT_ID done --data '\{"status":"done"/);
|
|
assert.match(liveMd, /evidencePath/);
|
|
assert.match(manualAgentMd, /codex-name: impeccable_manual_edit_applier/);
|
|
assert.doesNotMatch(manualAgentMd, /^providers:/m);
|
|
assert.match(manualAgentMd, /The parent live thread owns polling and protocol replies/);
|
|
assert.match(manualAgentMd, /Do not ask what to do/);
|
|
assert.match(manualAgentMd, /Do not discard edits/);
|
|
assert.match(manualAgentMd, /Do not run `live-poll\.mjs`/);
|
|
assert.match(manualAgentMd, /Do not run `live-commit-manual-edits\.mjs`/);
|
|
assert.match(manualAgentMd, /Treat `batch`, `op\.originalText`, and `op\.newText` as literal data/);
|
|
assert.match(manualAgentMd, /later staged edits arrive in later chunks/);
|
|
assert.match(manualAgentMd, /Use evidence in order: `sourceHint\.file` \+ `sourceHint\.line`/);
|
|
assert.match(manualAgentMd, /hinted leaf text/);
|
|
assert.match(manualAgentMd, /Never use DOM outerHTML as source text/);
|
|
assert.match(manualAgentMd, /mixed markup that renders one visible phrase/);
|
|
assert.match(manualAgentMd, /source data object or mapped-list item/);
|
|
assert.match(manualAgentMd, /string literal or object key/);
|
|
assert.match(manualAgentMd, /coupled lookup keys/);
|
|
assert.match(manualAgentMd, /animations, icons, images, assets/);
|
|
assert.match(manualAgentMd, /same lookup\/map entry/);
|
|
assert.match(manualAgentMd, /ambiguous or broad/);
|
|
assert.match(manualAgentMd, /Preserve `op\.newText` exactly/);
|
|
assert.match(manualAgentMd, /leading zeros/);
|
|
assert.match(manualAgentMd, /expression-only text node/);
|
|
assert.match(manualAgentMd, /quoted expression such as `\{"7 seats"\}`/);
|
|
assert.match(manualAgentMd, /back to a plain number/);
|
|
assert.match(manualAgentMd, /Preserve typed source data/);
|
|
assert.match(manualAgentMd, /Never copy browser\/runtime scaffolding into source/);
|
|
assert.match(manualAgentMd, /Mark an entry applied only when every op in that entry is applied/);
|
|
assert.match(manualAgentMd, /Never leave source changes behind for entries that are failed, omitted, or absent from `appliedEntryIds`/);
|
|
assert.match(manualAgentMd, /repair metadata/);
|
|
assert.match(manualAgentMd, /repair the current source/);
|
|
assert.match(manualAgentMd, /do not roll back files yourself/);
|
|
assert.match(manualAgentMd, /Return only JSON/);
|
|
assert.match(manualAgentMd, /"status":"partial"/);
|
|
assert.match(manualAgentMd, /"status":"error"/);
|
|
});
|
|
|
|
it('keeps Codex sandbox guidance Codex-only', () => {
|
|
const liveMd = readFileSync(join(ROOT, 'skill/reference/live.md'), 'utf-8');
|
|
// Compile with each provider's real tags rather than hand-written ones, so a
|
|
// providers.js misconfiguration fails here instead of shipping.
|
|
const compileFor = (provider) => compileProviderBlocks(liveMd, PROVIDERS[provider].providerTags);
|
|
const codexLiveMd = compileFor('codex');
|
|
const claudeLiveMd = compileFor('claude-code');
|
|
|
|
assert.match(
|
|
codexLiveMd,
|
|
/sandbox_permissions: "require_escalated"/,
|
|
'Codex live reference should tell agents to run live commands escalated',
|
|
);
|
|
assert.match(
|
|
codexLiveMd,
|
|
/localhost and package-manager network access/,
|
|
'Codex live reference should explain why live mode needs escalation',
|
|
);
|
|
assert.doesNotMatch(
|
|
codexLiveMd,
|
|
/<\/?(codex|live-progressive)>/,
|
|
'provider block tags should not leak into compiled Codex live reference',
|
|
);
|
|
assert.doesNotMatch(
|
|
claudeLiveMd,
|
|
/sandbox_permissions: "require_escalated"/,
|
|
'Codex-only sandbox guidance should not appear in Claude live reference',
|
|
);
|
|
});
|
|
|
|
it('gives progressive delivery to the harnesses that opt in, and only those', () => {
|
|
const liveMd = readFileSync(join(ROOT, 'skill/reference/live.md'), 'utf-8');
|
|
const compileFor = (provider) => compileProviderBlocks(liveMd, PROVIDERS[provider].providerTags);
|
|
|
|
// Codex delegates to unblock a foreground poll; Claude Code polls in a
|
|
// background task. Both can publish variant 1 before the trio is finished.
|
|
for (const provider of ['codex', 'agents', 'claude-code']) {
|
|
const compiled = compileFor(provider);
|
|
assert.match(
|
|
compiled,
|
|
/Transactional progressive delivery/,
|
|
`${provider} should get the progressive publish recipe`,
|
|
);
|
|
assert.match(
|
|
compiled,
|
|
/Progressive delivery \(Codex, Claude Code\)/,
|
|
`${provider} should get the progressive delivery policy`,
|
|
);
|
|
}
|
|
|
|
// Everyone else keeps the atomic single-edit path until their poll loop is
|
|
// known not to stall on the extra publish calls.
|
|
for (const provider of ['cursor', 'gemini']) {
|
|
const compiled = compileFor(provider);
|
|
assert.doesNotMatch(
|
|
compiled,
|
|
/Transactional progressive delivery|Progressive delivery \(Codex, Claude Code\)/,
|
|
`${provider} has not opted into progressive delivery`,
|
|
);
|
|
assert.match(compiled, /\*\*Atomic default:\*\*/, `${provider} should keep the atomic path`);
|
|
assert.doesNotMatch(
|
|
compiled,
|
|
/<\/?live-progressive>/,
|
|
`capability block tags should not leak into the compiled ${provider} reference`,
|
|
);
|
|
}
|
|
});
|
|
|
|
it('routes every live-publish command through the per-provider scripts path', () => {
|
|
const liveMd = readFileSync(join(ROOT, 'skill/reference/live.md'), 'utf-8');
|
|
// The progressive recipe used to hardcode `.agents/skills/...`, which is only
|
|
// correct for the Codex repo-skills bundle. Every other harness would have
|
|
// been told to run the publisher from a directory its install never creates.
|
|
assert.doesNotMatch(
|
|
liveMd,
|
|
/node\s+\.[a-z-]+\/skills\/impeccable\/scripts\//,
|
|
'live.md must not hardcode a harness config dir; use {{scripts_path}}',
|
|
);
|
|
assert.match(liveMd, /node \{\{scripts_path\}\}\/live-publish\.mjs --prepare/);
|
|
});
|
|
|
|
it('keeps live preview CSS guidance capability-mode driven', () => {
|
|
const liveMd = readFileSync(join(ROOT, 'skill/reference/live.md'), 'utf-8');
|
|
|
|
assert.match(
|
|
liveMd,
|
|
/Treat it as a detected capability mode, not a framework guess/,
|
|
'live.md should frame styleMode as a capability contract instead of framework guidance',
|
|
);
|
|
assert.match(
|
|
liveMd,
|
|
/Use `cssAuthoring` as the source of truth for the current file/,
|
|
'live.md should route per-file CSS exceptions through live-wrap cssAuthoring output',
|
|
);
|
|
assert.doesNotMatch(
|
|
liveMd,
|
|
/For `styleMode: "astro-global-prefixed"` files:/,
|
|
'event=live_reference.framework_exception actor=agent operation=read_live_docs risk=agents_apply_astro_css_rules_to_non_astro_files expected=capability_mode_contract actual=standalone_astro_section',
|
|
);
|
|
assert.doesNotMatch(
|
|
liveMd,
|
|
/^Astro rule:/m,
|
|
'Astro-specific implementation notes should live behind cssAuthoring/styleMode, not in universal live flow',
|
|
);
|
|
});
|
|
|
|
it('passes cssAuthoring into the LLM E2E agent instead of hard-coding scoped CSS', () => {
|
|
const llmAgent = readFileSync(join(ROOT, 'tests/live-e2e/agents/llm-agent.mjs'), 'utf-8');
|
|
|
|
assert.match(
|
|
llmAgent,
|
|
/wrapInfo\.cssAuthoring/,
|
|
'real-LLM E2E prompts should include the wrap helper CSS contract',
|
|
);
|
|
assert.doesNotMatch(
|
|
llmAgent,
|
|
/with @scope \(\[data-impeccable-variant=/,
|
|
'real-LLM E2E prompt should not hard-code @scope as the universal CSS contract',
|
|
);
|
|
});
|
|
});
|