mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
14d2641685a3fae643eceb74d9c843c54fb33bf8
263
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
14d2641685 |
Fix: keep the node runtime probe clear of cmd.exe metacharacters (#458)
Volta's Windows shims exec through `cmd /C`, which re-parses the argument list, so the `>=` inside the probe's `node -e` payload was read as output redirection. The command died with "The filename, directory name, or volume label syntax is incorrect" before node started, the guard read that as a missing runtime, and the hook it exists to protect was disabled on every PostToolUse and Stop. A user on a supported Node 24 got a one-time notice telling them to install Node 22, then silence. Clamping with Math.min is the same floor test in the same ES5-only syntax, with no character cmd.exe can claim. Verified through the Volta shim on Node 24.16.0 and 22.18.0 (exit 0) and against a real Node 20.6.1 binary (exit 1), so the floor is unchanged. Adds a regression test asserting no `<`, `>`, or newline reaches any generated `node -e` payload. Upstream cause: volta-cli/volta#1791. Prepared with AI assistance (Claude Code). |
||
|
|
d6a9891066 |
Share browser detector bundling (#498)
Centralize the browser-safe module set and source transformation so the browser and extension builders cannot drift. AI assistance: This refactor was prepared by Codex under pbakaus's scheduled architecture-simplification authorization. |
||
|
|
6d2af3f800 |
Guard the plugin loader contract that PR #494 exposed (#499)
* test: guard the plugin loader contract that PR #494 exposed The agents manifest key shipped for months and silently loaded zero of the four subagents; no validator looked at the generated plugin manifest's shape and claude plugin validate never checks it. Three layers now do: - scripts/lib/validate-plugin-manifest.js pins the verified loader contract (KNOWN_LOADER_KEYS allowlist, no agents key, trailing-slash skills path from issue #86, every skill/agents/*.md shipped in plugin/agents/), unit-tested in tests/validate-plugin-manifest.test.js including a check of the real committed subtree. - The same check gates bun run build next to the version-drift guard. - tests/plugin-e2e.test.mjs installs the committed ./plugin subtree into a real Claude Code (sandboxed via CLAUDE_CONFIG_DIR in a temp dir) and asserts the component inventory: skill parses, all agents visible, hooks discovered. In the default suite; runs in about a second and skips cleanly when the claude CLI is absent, so CI is unaffected. All three failed against the pre-#494 tree for the shipped reason (Agents 0 of 4) and pass against current main. AI-assisted via Claude Code under maintainer direction. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix: address PR review bot findings - Copilot: guard collectPluginManifestFindings against valid JSON that is not an object (null, string, number, array) so a broken manifest is a finding instead of a build crash; unit test added - Copilot: update the plugin-e2e header comment, the suite is in the default lineup rather than opt-in AI-assisted via Claude Code under maintainer direction. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix: harden plugin E2E sandbox isolation Bugbot: create the sandbox CLAUDE_CONFIG_DIR up front and redirect HOME and USERPROFILE into the temp workDir too, so a CLI code path that derives config or cache locations from the home directory instead of CLAUDE_CONFIG_DIR still cannot touch the developer's real Claude config when the default suite runs. AI-assisted via Claude Code under maintainer direction. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix: agent parity check mirrors the build's emit rules Bugbot: the shipped filename is `${claude-name || name}.md` and a providers: list may exclude claude-code, so comparing raw source basenames could fail the build on a renamed or provider-scoped agent with a build:release hint that cannot fix it. The validator now derives expected filenames the same way the transformer factory does (shared parseFrontmatter, same providers gate) with unit coverage for renames, name overrides, and provider-scoped agents. AI-assisted via Claude Code under maintainer direction. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix: run the plugin E2E through a shell on Windows Bugbot: the claude CLI is a .cmd shim on Windows and Node refuses to spawn those via execFile without a shell, so the availability probe always failed and the suite silently skipped there. Windows now invokes through a shell with every argument double-quoted (temp paths routinely contain spaces); the POSIX path is unchanged. AI-assisted via Claude Code under maintainer direction. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com> |
||
|
|
baa76cfd05 |
fix: stop emitting the "agents" key so the four subagents actually load (#494)
build.js derives plugin/.claude-plugin/plugin.json from the root manifest and injects an `agents` array built from the generated agent files. In Claude Code that array is exactly what stops them loading. Verified on a throwaway local marketplace, across the plausible shapes: array of file paths (what build.js emits) -> 0 agents load, skills fine a string, e.g. "./agents" -> whole plugin fails to load array containing a directory -> whole plugin fails to load key omitted -> all agents load, skills fine Claude Code discovers agents/*.md on its own, and the identifier it uses is the file name rather than the frontmatter `name`. So the key is not needed, and any present form of it is worse than its absence. Confirmed end to end on this repo: with the key emitted, `claude plugin details impeccable` reports "Agents (0)"; with it omitted it reports "Agents (4) impeccable-asset-producer, impeccable-documenter, impeccable-finish-reviewer, impeccable-manual-edit-applier". The agent files themselves are still copied by the existing copyDirSync a few lines below — only the manifest key goes away. Two things make this hard to notice: with a breaking shape `claude plugin details` prints "Plugin not found" instead of a validation error, and `claude plugin validate` does not catch it because it validates the marketplace manifest, not the plugin manifest. The only reliable signal is the "Agents (N)" line. The same defect is reported against another project at addyosmani/agent-skills#449, with the full reproduction. Scope note: verified on Claude Code only. plugin/ is the Claude-Code / Grok subtree, and the Grok manifest is written separately just below, so this does not touch the other harnesses. |
||
|
|
7d6109b723 |
Drop deprecated craft alias from the generated argument-hint
craft is a deprecated compatibility alias, but its SKILL_CATEGORIES entry kept it advertised in every generated SKILL.md argument-hint. Unmapping it removes it from the hint while the alias keeps routing through the router table and command-metadata.json. AI-assisted change (reviewed by maintainer). Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c91f3717d4 |
Merge pull request #466 from pbakaus/codex/remove-legacy-pattern-parser
Simplify curated pattern loading |
||
|
|
33f824b5b5 |
Clarify curated pattern source
Describe the catalog as independent of SKILL.md extraction rather than repository content, addressing Copilot's review feedback. Prepared with Codex assistance under pbakaus's scheduled architecture cleanup authorization. |
||
|
|
358fc2e716 |
Simplify curated pattern loading
Remove the unreachable legacy SKILL.md pattern parser now that readPatterns uses the curated catalog exclusively. Prepared with Codex assistance under pbakaus's scheduled architecture cleanup authorization. |
||
|
|
bf957452c4 | Add Google Antigravity provider support | ||
|
|
9d1b4bdfac |
Fix five stale rule counts and the validator blind spots that hid them
single-font's retirement made the detector 59 rules; both READMEs still said 60 in five places, and the count validator reported clean because 'deterministic detector rules' puts a word the regex never expected between the qualifier and the noun, and README.npm.md was never in the checked file list. The regex now tolerates the detector infix, counts qualified 'issues' claims, and README.npm.md joins the list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5f4b58d06d |
Merge pull request #433 from pbakaus/live-v2-rewrite
Live v2: root manifest, mount-ack protocol, AST scaffolder, mechanical accept |
||
|
|
fa1177ed9c |
Ship native subagent definitions for GitHub Copilot and Cursor
The github and cursor providers previously received only the generated
degraded/ inline fallbacks. Both harnesses support real custom subagents,
so the build now emits them from the same skill/agents/ source:
- GitHub Copilot: .github/agents/impeccable-<role>.agent.md with portable
frontmatter only (name + description; omitting tools grants all tools,
and Copilot has no documented model/effort/max-turns equivalents).
- Cursor: .cursor/agents/impeccable-<role>.md with name, description,
model: inherit, is_background: false, and readonly derived from the
agent's tool list (true only for the finish reviewer, which declares
neither Write nor Edit). effort/max-turns are skipped because Cursor's
effort option requires an explicit model id.
Agent bodies now also resolve {{scripts_path}} and strip rule markers in
the shared agentFormat pipeline, which fixes the previously unresolved
placeholder in the emitted Claude asset-producer agent.
The CLI installer places agents per scope: project installs write
<repo>/.github/agents/ and <repo>/.cursor/agents/; user-level installs
write ~/.copilot/agents/ (Copilot's user dir, not ~/.github/) and
~/.cursor/agents/, overwriting stale impeccable-* copies. Because
Copilot lets user-level agents shadow same-named project ones, a project
install warns when shadowing copies exist; Cursor gives project agents
precedence, so no warning there.
new-work.md and visualize.md extend their harness-naming clauses with
the Cursor and Copilot invocations. The degraded/ fallbacks keep
shipping for surfaces where the model still fails to delegate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
4ac54bebee |
fix: address PR review bot findings
cursor[bot] findings on #433: - Nightly schedule no longer enables the paid opt-in suites: a schedule event has no diff base, so the change-detection fallback flagged every file-triggered suite, which would have billed the skill-behavior, accept-cleanup, and deepseek LLM suites nightly. The plan now pins the schedule event to deterministic suites plus the full live-e2e matrix, with a regression test. - Dismissing the mount-error card no longer strands the session: while the bar is hidden in GENERATING the card is the only recovery surface, so dismiss now returns the state machine to PICKING (session and server truth survive for a later republish). This work was produced with AI assistance (Claude Code). Co-Authored-By: Claude Code <noreply@anthropic.com> |
||
|
|
17dabf4b7e |
Live v2: root manifest, mount-ack protocol, AST scaffolder, mechanical accept
A ground-up hardening of live mode, driven by a production session in a nested-app monorepo that hit six distinct failure classes. Full design rationale in docs/LIVE-REWRITE-PLAN.md; every Codex-reported failure now has a mechanical fix and a regression test. Roots: live/roots.mjs resolves appRoot/repoRoot/contextRoot once at boot (keyed on dev-server configs, not monorepo brand markers), persists a manifest, and every live CLI re-anchors onto it at startup, so a helper run from the wrong directory can no longer fork session state. Context files are discovered upward to the git root. Render truth: variant_mounted / variant_mount_failed events give the journal per-variant mount state; failures reach the agent's poll queue, raise a persistent error card with Retry (no more localStorage wipe), and an attach probe names root/dev-server mismatches explicitly. The browser rehydrates from the server when localStorage is gone. Svelte: the scaffolder now parses with the app's own svelte 5 compiler. Control flow survives (an each collection crosses the contract as one structured prop), keyed each blocks hydrate synthetic keys, and anything a detached preview cannot support falls back to source-preview instead of shipping a wrong scaffold. Preview modules live in per-publish revision directories, defeating stale transform caches. Accept: CSS is reconciled, not appended. Matching selectors are replaced, params bake from params.json kinds, the compiler's unused-selector pass prunes superseded rules (pre-existing dead rules protected), a selector- loss postcondition refuses any write that would drop hand-written rules, and live-complete refuses to finish while live plumbing remains in source. Also: framework registry (live/frameworks/) with a crash-safe injection journal, session-store snapshot caching with read-only reads, protocol enum consolidation, steer Send button, honest DESIGN-panel empty states. Testing: new unit suites (roots, AST scaffolder, accept CSS, accept pipeline, framework conformance); e2e now fails on preview-tree 404s, proves computed-style mount for every variant, drives the Tune panel through baked params, and injects failures (broken mounts, republish, storage loss). New runtime fixtures: monorepo-nested-vite (repo root != app root) and vite8-sveltekit-stateful (each blocks + state). Nightly full-matrix cron. An independent adversarial review pass preceded this commit; its blocker and major findings are fixed and regression-tested. This work was produced with AI assistance (Claude Code). Co-Authored-By: Claude Code <noreply@anthropic.com> |
||
|
|
c9c0fc887b |
Merge remote-tracking branch 'upstream/main' into fix/detect-system-chrome-gpu-window
# Conflicts: # scripts/test-suites.mjs |
||
|
|
5e572c8b8a |
Merge pull request #423 from pbakaus/hook-guard-unsupported-node
Stop the design hook erroring on a node too old for ESM |
||
|
|
fd9076f4f0 |
Enforce the engines floor in the probe instead of a capability check
The probe asked whether node could load ESM, while the notice promised a Node 22 floor and package.json engines declares >=22.12.0. Reviewers kept flagging the gap, and they were right to: a 14.18-to-21 runtime passed the probe on the strength of one import while the hook and its detector bundle are only ever exercised on the engines floor, so "can load our code" was a weaker claim than the one being made for it. Check the floor directly: parseInt(process.versions.node) >= 22, in ES5-only syntax that parses on any node old enough to fail it. Probe and notice now derive from one NODE_MAJOR_FLOOR constant, so they cannot disagree, and the archaeology about node: scheme support and pre-15 unhandled-rejection semantics goes with the import it explained. Add the missing contract test: every generated hook command carries the probe, the notice appears exactly where a harness can render it (Claude and Codex, project and plugin), and the expected floor is read from package.json engines rather than repeated by hand. Verified against a fake pre-22 node, no node, and a real node: one notice then the marker holds it silent, exit 0 in every failure shape, and the hook's own exit code still passes through on a supported runtime. Co-Authored-By: Claude Fable 5 (via Cursor) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
86cdf528c5 |
Probe the import the hook actually uses, and fail closed on rejection
Greptile flagged that the probe does not enforce the Node 22 engines floor.
Two parts to that, and they land differently.
The real defect is narrower and worse than stated: the hook closure imports
`node:fs`, `node:os`, `node:path` and `node:url`, and the `node:` scheme needs
14.18, so a bare `import('fs')` probe passed on 12 and 13 and those runtimes
then died on the real import, which is the banner this branch exists to remove.
Probing `node:fs` closes that. The added `.catch(()=>process.exit(1))` is load
bearing rather than tidiness: before Node 15 an unhandled rejection is only a
warning and the process still exits 0, so a rejected probe would have read as a
pass on exactly the versions in question.
Not enforcing 22 is deliberate and stays. The probe asks whether this runtime
can load our code, not whether it is a supported one, so a 14.18-to-21 runtime
that works today keeps working rather than being silently switched off. The
notice names 22 because that is the version worth installing, and it only ever
reaches someone whose runtime already failed the probe, so no user is shown a
threshold that contradicts what ran.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
4f999ceff8 |
Give Codex the notice too; its hook reference documents systemMessage
Commit
|
||
|
|
0c19098754 |
Guard the remaining harness manifests against a dead node runtime
Bugbot caught the Codex plugin builder still invoking node directly, and the same reasoning covers GitHub Copilot and Grok Build: all three shipped the exact failure this branch exists to stop, and sat visibly inconsistent with their guarded siblings. Route them through guardedNode with no notice, matching Codex and Cursor. GitHub gains a second property from it: outside a git repository `$(git rev-parse --show-toplevel)` expands to nothing, so the old command handed node a path that could not exist and failed the turn. The file test now short-circuits that to exit 0. Every builder carries the probe; only the two Claude manifests carry the notice, which is the only harness whose response shape is confirmed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8397d532b9 |
Keep the unsupported-node notice to the harness that can render it
`systemMessage` on stdout is a Claude Code contract. The shared guard was emitting it for Codex and Cursor too, where what a harness does with stdout it did not ask for is unconfirmed, and a Cursor preToolUse hook printing an unexpected JSON object is the wrong thing to guess about. Pass the notice in per harness instead of baking it into the guard. Claude manifests opt in; Codex and Cursor take the runtime probe alone, so an unsupported runtime stays as quiet there as it was before the probe existed. Giving them their own shape later is one more argument at the call site. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0db59088ff |
Stop the design hook erroring on a node too old for ESM
The hook command invokes bare `node`. When that node predates ESM, `hook.mjs` dies while it is still being parsed, before the script's own always-exit-0 contract can run, so node exits 1 and the harness reports a hook error on every Stop and every edit. Probe the runtime in the command string before invoking the hook, and route the Claude plugin manifest through the guard that already covered the project-local manifests. On probe failure the command exits 0 and emits a one-time `systemMessage` naming the two fixes available to the user, since nothing written in ESM can report this condition. Fixes #410. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
916b0a1fdf |
Generate degraded-mode fallback references from the subagent definitions
Harnesses with no subagent capability now run each role inline from the
same single source. The build emits reference/degraded/<role>.md for every
agent in skill/agents/ (role name is the agent name minus the impeccable-
prefix), stripping frontmatter and prepending the inline-substitution
preamble. These pass through the same provider-block compilation and
placeholder replacement as ordinary reference files, so <codex> blocks and
{{placeholders}} resolve per target, and they land in the committed harness
dirs on build:release like every reference file.
Repoint the three capability-first fallback sites in the prose at the
generated files: new-work.md reviewer and documenter fallbacks, and
visualize.md asset-producer fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
bcf354cd0c |
Fix Codex hook path so .codex-directory installs run the detector
The committed .codex/hooks.json hardcoded .agents/skills/impeccable/scripts/
hook.mjs. On a .codex-directory install the skill payload lives at .codex/
skills/..., so the guarded command ([ ! -f X ] || node X) found no file and
silently no-opped, leaving the design detector dead for those users.
Derive the hook payload path from the emitting provider's own configDir rather
than hardcoding .agents:
- buildCodexHooksManifest(skillDir) now builds `${skillDir}/skills/impeccable/
scripts/hook.mjs`; hooksJsonFor threads each provider's configDir through. The
Codex provider (configDir .codex) emits .codex/skills; the root sync and the
self-consistent dist/codex bundle both point at their own payload.
- CLI installer: project-scope hook rewriting now derives the provider's own
project-relative path instead of preserving the bundle token. The Codex bundle
ships a .codex/skills command, but the CLI lays the skill at .agents/skills, so
the installed .codex/hooks.json is rewritten to .agents/skills (Claude keeps
its ${CLAUDE_PROJECT_DIR} token; global installs keep the absolute rewrite).
Per-provider hook payload path after the fix:
Emission hook path
dist/codex/.codex/hooks.json .codex/skills/impeccable/scripts/hook.mjs
root .codex/hooks.json (build sync) .codex/skills/impeccable/scripts/hook.mjs
CLI .agents (codex) project install .agents/skills/impeccable/scripts/hook.mjs
CLI .agents (codex) global install <home>/.agents/skills/.../hook.mjs (abs)
.claude / .cursor unchanged
Tests: extended hook-build (codex-dir -> .codex/skills, agents-dir -> .agents/
skills) and skills-cli (bundle ships .codex/skills, install rewrites to .agents/
skills). Regenerated tracked .codex/hooks.json via build:release.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
501528c07f |
Register orphaned live-tanstack-adapter test in the live suite
tests/live-tanstack-adapter.test.mjs (added in
|
||
|
|
087983070b |
Release script verifies impeccable.style serves the released version
The 4.0.0 release stranded npx-update users on a stale bundle for a day because the site deploy is a separate step nobody was reminded of. Skill releases now check /api/version and print the redeploy command when the served version lags. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
eda81f0937 |
Release prep: skill v4.0.1
Bump plugin + marketplace to 4.0.1 and sync the regenerated provider output: the guarded hook commands from issue #399 (a missing hook file exits 0 instead of crashing every turn of a user-level install), the canon standing exit, the visualize flow, the two shipped subagents, and the interactive-spine fixes from today's live testing. Detector count validates at 59 with undersized-ui-text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6ece0e588f |
Add deterministic new-work interactive smoke suite
A cheap, LLM-free E2E tier for the interactive parts of new-work, mirroring the two-layer live-e2e pattern (deterministic now, opt-in LLM tier later). - generate-image.mjs: IMPECCABLE_IMAGE_GEN_FAKE=1 writes a deterministic offline image (SVG with wrapped prompt + SYNTHETIC COMP label, or a valid palette-stripe PNG carrying the prompt/marker in a tEXt chunk). Same CLI contract, no key, no network, $0.00 cost line. - tests/new-work-e2e/user-bot.mjs: scripted user bot (module + CLI) that resolves the serve-question daemon from the workspace and drives the real page via Playwright (pick, re-roll + steer, canon, tab close). - tests/new-work-e2e.test.mjs: node --test coverage of the serve-question cycles (pick + CHOSEN CARD, re-roll + --update re-deal, canon + CANON CHOSEN, tab-close exit-4, text-only card) plus fake image determinism. - Registered as the opt-in new-work-e2e suite; added test:new-work-e2e. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
70fdc172b8 |
Resolve detect DESIGN.md from each target's project, not cwd
The detect CLI loaded DESIGN.md once from process.cwd() and applied it to every scan target. Scanning another project's files from inside a different repo therefore judged them against the wrong project's design system (cross-project contamination observed during eval work: running detect from impeccable-evals against a generated artifact elsewhere applied the evals repo's DESIGN.md). DESIGN.md now resolves by walking up from each scan target's own location to its design root: a directory carrying a DESIGN.md is the root; a directory carrying a project marker (.git / package.json / .impeccable) without a DESIGN.md is a boundary that stops the walk with no design system, so a sibling project never inherits a parent's or cwd's rules. A target with no design root above it falls back to no design system rather than cwd's. Resolution is memoized per root, so a multi-file scan reads each DESIGN.md once, and targets spanning projects each get their own. file:// URLs resolve from their path; remote http(s) URLs get no design system. Adds tests/detect-cli-design-contamination.test.mjs, which spawns the real CLI to prove B's file is not judged by A's DESIGN.md, that a project still governs its own file, that a mixed-project scan resolves per target, and that a marker-less bare file gets no design system. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
33d7684c06 |
fix(detect): use system Chrome on Windows to stop GPU crash-loop window (#372)
On Windows, `impeccable detect <url>` flashed a persistent black window during scans. The scan uses puppeteer's bundled Chrome, which runs from an untrusted user-cache path; Windows blocks its GPU process, so it crash-loops and flashes a compositor surface on every retry. It is not a real application window (not in Alt+Tab, not clickable, invisible to window enumeration) and not malware. Prefer the system-installed Chrome via channel:'chrome' on Windows, which runs from a trusted location with a healthy GPU: no crash loop, no window. Fall back to the bundled browser when Chrome is not installed. Scoped to Windows only, so mac and linux keep the pinned bundled build for consistent measurement. Both render on hardware GPU, so contrast measurement is unaffected. Also routes both launch sites through one helper and fixes a pre-existing bug where detectUrl hardcoded headless:true instead of honoring options.headless. Tests: new tests/detect-url-launch.test.mjs covers the launch choice per platform (Windows prefers channel:'chrome' and falls back to bundled; non-Windows never attempts it), wired into the detector suite. Verified on Windows 11 / Chrome 150: zero GPU crashes, window gone, findings unchanged. This change was prepared with AI assistance. |
||
|
|
0376145a46 |
Fix release.mjs crash: existsSync is a named import, not fs.existsSync
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5575a027dc |
Flag and repair drift in Impeccable's own project artifacts
v4 changed PRODUCT.md's shape and retired the register axis, so an upgraded project can carry answers nothing reads. Nothing measured that. Two tiers, and the split is a performance contract: - Boot (context.mjs, emitting CONTEXT_STALE) spends only what a boot already spends: markdown already in memory, a bounded set of stats, the small JSON files the boot reads anyway. No new directory walks. One directive for the whole set, throttled to once a week per project so a finding the user declined does not reappear tomorrow. - doctor.mjs runs the deep pass on demand: git drift, ignore lists validated against the live rule registry, hook script paths that stop resolving, and the monorepo workspace sweep. --fix applies only the migrations that carry no decision. Findings are data, not prose, so the boot directive, the text report and --json all render one set. Severity says what should happen: auto (fix on the next write anyway), mention (state once), route (name the command that owns the repair). PRODUCT.md now carries a schema stamp so the checks stop reconstructing a file's vintage from which sections it happens to have. Schema version, not release version: a record written by 4.0.0 is not stale under 4.0.1. DESIGN.md gets no stamp, because it follows the external design.md spec that Stitch lints and every DESIGN.md signal is measurable without one. The highest-value catch is a project that resolves to web while carrying native build files, including a monorepo app inheriting a root record that says web. That one costs output quality silently; nothing failed before. doctor follows the hooks/pin pattern rather than the Commands table, so it stays out of the design menu and the count stays at 23. Also corrects CLAUDE.md, which still documented the register axis, reference/brand.md, reference/product.md, eleven deleted domain reference files, and an extractRegister() whose only occurrence in the repo was that sentence. Prepared with AI assistance (Claude Code). Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
06d21dea7d |
Add first-class Grok Build harness support
Emit .grok skills, agents, and PostToolUse/Stop hooks; wire the CLI installer and downloads; fix the plugin install path to #plugin; and document Grok in HARNESSES.md and README. AI assistance: written with Grok Build. |
||
|
|
739747d358 |
Register serve-question tests in the suite registry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d7d10277d1 |
Merge main into oneshot-v4, keeping the service layer split out
main still carries the site, so every `site/` path resolves to deleted. `tests/docs-integrity.test.js` goes with it (it imports the site's demo renderer), and `package.json` keeps main's `@anthropic-ai/sdk` bump while dropping `@google/genai` and `@paper-design/shaders`, which nothing in the product layer imports. Real code merges: - hook-lib: main's #391 cache fix (sync the remembered set to the live scan so fixed findings stop being named and a reintroduced one fires again) now runs on the immediate tier rather than the whole filtered set. Remembering a deferred finding the per-edit pass never reported would let the Stop deep pass dedupe it away. main's `maxFileBytes` ceiling, `cleanAcked` once-per-file ack, and template-extensions re-export all land alongside the tiering work. - live-browser: main's `hasParams` gate on the Tune badge, keeping this branch's `C.ink` badge text so it stays legible on kinpaku gold. - detect-text: both the block-level codex-grid-background scan and main's inset-stripe CSS check. - test-suites: union of both trigger sets and file lists, minus the site-only entries (`shiki-theme`, `docs-integrity`). - Two hook tests moved off deferred-tier rules (`overused-font`, `side-tab`) onto immediate-tier ones. They assert cache bookkeeping, which the per-edit pass only reaches for the immediate tier. Also drops the site waivers from `.impeccable/config.json` and stops `build:browser` recreating a stray `site/` tree just to write a bundle the other repo builds itself. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
90f9eeb99b |
Split service layer into private impeccable-site repo
The public repo keeps the OSS promise surface: skill, CLI, extension, tests, and the provider build. The site, labs, concept/composition catalogs, image pipeline, Cloudflare functions, and authoring guide move to pbakaus/impeccable-site. concept-seed tests run against a synthetic fixture catalog; the plugin icon and skill categories moved in-repo; build validation narrows to README prose and non-site counts; release notes read from a sibling impeccable-site checkout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b5ec969c07 |
Add world roll API and seed telemetry client
/api/roll deals deterministic challenger rolls server-side (same salts and sha256 ranking as the local seed, verified bit-for-bit); the request log is the impression record. /api/chosen takes the anonymous choice ping. Events land in Workers Analytics Engine. concept-seed.mjs resolves data in order: local catalog dir, roll API, degraded promotion-only seed. --chosen sends the choice ping; DO_NOT_TRACK and IMPECCABLE_NO_TELEMETRY disable it. API-dealt seeds carry the telemetry instruction inline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7557935fdb |
Expand concept system: modes, ratings, re-roll, breadth strategy
Catalog: mode-aligned staging surfaces (persuade/operate/read/experience), star ratings on approvals feeding challenger draw weights, family retirements, authoring strategy and territory guide, rework and breadth authoring rounds, composition mining from rejected worlds. Seed: six challengers (two per tier), --reroll chains, --mode staging filter, rating-weighted draws. New-work: Present/visualize/re-roll flow, image-gen requirement, register-neutral vocabulary. Pipeline: per-mode staging prompts with split frames, hero-from-board reference generation, render-safety guards. Labs: ratings UI, unrated filter, mode chips, composition approve-guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
83c37e453a |
feat: add Mistral Vibe harness support (#373)
* feat: add Mistral Vibe harness support
Mistral Vibe is Mistral AI's open-source CLI coding assistant that ships an
Agent Skills system at .vibe/skills/{name}/SKILL.md with slash-command
invocation, mapping cleanly onto the existing transformer pipeline. Adds
Vibe as a 14th first-class harness:
- PROVIDER_PLACEHOLDERS entry in scripts/lib/utils.js (model, config_file
= AGENTS.md, ask_instruction, command_prefix) mirroring the Qoder shape.
- PROVIDERS entry in scripts/lib/transformers/providers.js with configDir
.vibe and frontmatterFields user-invocable, license, compatibility,
metadata, allowed-tools (Vibe docs do not document argument-hint).
- transformVibe named export in scripts/lib/transformers/index.js for
test-spy parity.
- vibe added to FILE_DOWNLOAD_PROVIDER_CONFIG_DIRS so the download
endpoint accepts /api/download/skill/vibe/* and resolves to
dist/vibe/.vibe/.
- .vibe added to PROVIDER_DIRS, PROVIDER_ALIASES, PROVIDER_DISPLAY,
PROVIDER_INPUT_ORDER, GLOBAL_HARNESS_HINTS, and the normalizeForHash
provider regex in cli/bin/commands/skills.mjs so the CLI detects
existing Vibe installs.
- docs/HARNESSES.md updated: official docs row, frontmatter support
column, directory structure row, and Last verified date bumped.
- docs/DEVELOP.md, README.md (install instructions, providers list,
Supported Tools), .github issue/PR templates, sync-generated-output
workflow, and AGENTS.md extended with Vibe.
The dynamic providers.test.js loop picks up Vibe automatically; all 160
provider tests pass including the 14 new Mistral Vibe cases. The build
regenerates dist/vibe/.vibe/skills/impeccable/ with correct frontmatter
and .vibe-substituted script paths. Generated harness output is left
unstaged per the source-first policy; the sync-generated-output workflow
(now listing .vibe) will commit it back to main after merge.
Co-Authored-By: Mistral Vibe <noreply@mistral.ai>
* Address review on Vibe harness support
Verified the Vibe claims against the docs and the mistralai/mistral-vibe
source, then tightened what the tables say.
- model placeholder: 'Mistral', not 'the model'. Vibe is Mistral's own
first-party CLI, so it belongs with gemini -> Gemini and codex -> GPT
rather than with the provider-agnostic harnesses. SKILL.src.md's one
use of {{model}} now renders 'Mistral is capable of extraordinary
work.' instead of a lowercase 'the model'.
- Docs links point at the skills page, not the product overview, matching
every other row in both tables.
- disable-model-invocation is No, not TBD. The field appears nowhere in
Vibe's source; unknown frontmatter keys are silently ignored.
- Split the directory row into project and global scopes the way the Pi
row already does. Vibe reads .vibe/skills/ and .agents/skills/ at the
project level and ~/.vibe/skills/ and ~/.agents/skills/ globally; the
global .agents dir was missing, and project .agents/skills/ was sitting
in the global column. Sources: vibe/core/paths/_local_config_files.py
and vibe/core/config/harness_files/_paths.py.
- Restored 'Last verified' to 2026-04-28 and dated the Vibe row on its
own. Only that row was checked, and this file warns against trusting
stale claims, so a blanket re-date made ten other rows look fresher
than they are.
AI assistance: written with Claude Code.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Mistral Vibe <noreply@mistral.ai>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
5d719a279a |
Fix Live accept for Elixir templates in lib/ (#374)
* Fix Live accept for Elixir templates in lib/ Wrap and accept search the repo for impeccable variant markers. That search skipped .ex files and the lib/ tree, so Phoenix LiveView markup inside ~H""" blocks never matched and browser Accept returned "Session markers not found". Extend the same EXTENSIONS and searchDirs in live-accept.mjs and live-wrap.mjs. Add a regression test that accepts from lib/my_app_web/components/layouts.ex. * Live: give the source search one owner for template extensions The #374 fix had to patch the same hardcoded EXTENSIONS array in two files because live-wrap.mjs and live-accept.mjs each carried their own copy of the project source walk. The copies had already drifted: same extension list twice, same searchDirs twice, and one realpathSync guarded by try/catch while the other was not. Meanwhile hook-lib.mjs had solved this properly for the design hook in #316/#347 with a configurable `detector.extensions` and suffix matching that handles .blade.php and .html.erb. Live never read it, so a project that taught the hook about .heex still got 'Session markers not found' on Accept. - lib/template-extensions.mjs is the single owner. It holds Live's built-in markup list, the suffix matcher, and the detector.extensions config reader. hook-lib.mjs now imports its normalize/merge/match helpers from here instead of duplicating them, and re-exports matchConfiguredExtension for its existing callers. - Live resolves built-ins PLUS detector.extensions, so teaching the hook about a server template teaches wrap and accept at the same time. - live/source-search.mjs holds the walk both scripts share. Callers pass the one thing that actually differs (skipDirs, fileFilter). Unifying gives live-wrap the guarded realpathSync, so a dangling symlink in the tree no longer throws out of the whole wrap, and makes it skip .impeccable artifacts the way accept already did. - Extensions are matched on filename suffix rather than path.extname, so root.html.heex and show.html.erb resolve. - Drop .exs. Those are Elixir scripts (mix.exs, config/*.exs), never markup, and including them only lets a wrap query match build config. - Fill the Elixir gap in the manual-edit paths, which kept their own allowlists and would have left Live half-working for Phoenix: live-commit-manual-edits.mjs and live-manual-edit-evidence.mjs. Verified the round trip by hand against a Phoenix layout: wrap injects markers into a ~H""" block in lib/**/*.ex, accept carbonizes the chosen variant back out. AI assistance: written with Claude Code. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Nils Kanevad <heliumbrain@users.noreply.github.com> Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d0ac67c6e9 |
Live: polling rework, source locks, preflight scaffolding (#381)
* Improve Live polling responsiveness and reliability Restore foreground/background polling as the primary harness architecture, add progressive publication and framework-safe previews, and harden quality and regression coverage. The experimental app-server runtime is intentionally excluded.\n\nPrepared with AI assistance under maintainer direction. * Fix source-safety, detector, and lock defects in Live polling work Addresses the review findings on #371, plus several the bots did not catch. All fixes have regression coverage that fails on the prior code. Source corruption: - Vue accept dropped valueless root attrs (disabled, v-cloak) and, worse, rewrote @click="x" as a literal click="x" DOM attribute, because the attr parser was name-anchored and skipped the sigil. Tokenize the whole Vue attr grammar and normalize shorthands so accept round-trips directives. - --variant was interpolated unescaped into a RegExp, so --variant '.*' matched the original block first and reported a successful accept while silently restoring the original. Validate against the digits pattern the browser and the /events schema already enforce. - --id reached path.join unvalidated, so --id ../../../../etc/evil wrote and read receipts outside the project. Hoist the existing safeSessionId check into impeccable-paths and apply it at every id-to-path sink. Accept/lock correctness: - Plain HTML/JSX accept and discard did not catch SOURCE_LOCKED, so contention exited non-zero with empty stdout and the agent got no JSON to retry on. - Lock staleness was mtime-only and never read the pid it records: a holder whose critical section outran 60s had its live lock swept, admitting a second writer to the same file, while a crashed holder blocked accepts for a full 60s. Decide staleness by owner liveness, and release only our own lock. Detector: - isNeutralColor only parses computed color forms, so routing authored CSS through it reported inset 4px 0 0 #000 / black / #e5e7eb as chromatic side-tab stripes. Add an authored-color neutrality test covering hex and named neutrals; the fixture had no literal-color cases at all. - Rule line numbers were off by one for every rule after the first, and commented-out CSS was scanned as live rules. Server: - An error reply carries no sourceEventType, and inferSourceEventType returned undefined, which acknowledgePendingEvent treats as a wildcard: a stale generate worker's failure consumed the user's queued Accept, which then reached no agent and left the browser in SAVING forever. - The generate preflight spawned live-wrap.mjs synchronously inside the request handler, freezing the single-threaded server for the whole scaffold (~7.6s measured on this repo, 15s ceiling) and stalling Accept/Discard/SSE. Make it async, claiming the lease before the first await so no event double-delivers. - Every browser checkpoint was echoed back as variant_progress, so a Tune slider drag remounted the preview under the user's cursor and latched the *_reviewable phases from the wrong trigger. Gate on the reason. Cleanup: - Collapse four divergent benchmark argv parsers into scripts/lib/cli-args.mjs. Three silently misread flags: --iterations 20 benchmarked 5, --agent llm ran the fake agent, --median-target=0.4 used the default threshold. - Drop a snapshot cache this branch made write-only (it grew per session for the server's lifetime and was never read), a dead exported reconcile helper, and the unused deferReply branch. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Route the last two benchmark scripts through the shared argv parser Follow-up on review feedback. The previous commit consolidated four of the six Live benchmark parsers and left these two on their own hand-rolled `arg()`, which was the inconsistency the first pass was meant to remove. - benchmark-live-control.mjs and benchmark-live-init.mjs parsed --iterations with Number(), so a non-numeric value became NaN and `index < NaN` ran the benchmark zero times before failing on the metrics file. They also accepted only the space-separated form, so --iterations=20 silently measured the default. Both now use parseArgs + positiveIntFlag, which throws on a value that was clearly meant as a number. - benchmark-live-control.mjs read the metrics file with no handling for the case where the run produced nothing: a missing file surfaced as a raw ENOENT stack and a malformed line as a bare SyntaxError. Report both with a diagnostic naming the file and the env var that populates it. - summarize() now reports a `samples` count and nulls instead of letting percentile() read past an empty array, where the NaN serialized to null and a report of nothing measured looked like a real measurement. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Stop telling users a busy agent is disconnected The agent-poll indicator tracks whether a poll is parked, which is the right signal for "can steering reach the agent right now" and is why the flag itself is left alone. But it goes quiet for two different reasons, and both got the same copy: "Agent disconnected - run live-poll.mjs to connect". Under the one-shot foreground polling that live.md calls the primary contract, no poll is parked while the agent works, so the second reason is every normal generation. For its whole duration the bar told the user a healthy session was broken and advised them to start a poll loop that was already running. Pick the copy from the live state, which the browser already tracks: GENERATING and SAVING mean the agent holds work it was handed, so say it is working. Every other state with no parked poll keeps the original, actionable wording. The aria-label carries the same distinction, since the tooltip is mouse-only. The text is derived at read time rather than cached, because the live state moves between the 5s status polls and a finished generation would otherwise keep reading "Agent is working" until the next one landed. Deriving it also keeps the read out of setLiveState, which runs long before agentPollingConnected's declaration and would hit its temporal dead zone. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Scope design-system-font-size off the injected live overlay live-browser.js builds a self-contained UI that renders over arbitrary host pages, so its inline type scale is deliberately independent of DESIGN.md, which documents the impeccable website's ramp. The rule fired 32 times there and is the only rule that fires on that file. Suppress it as a file-scoped value wildcard rather than via ignoreFiles: an ignoreFiles glob would silence every rule for the file, and the overlay is real user-facing chrome where a future contrast or side-tab finding should still be heard. Scoped to this one file, so the rule keeps working everywhere else. Written by hand because hook-admin's ignore-value cannot emit the `files` array that detector.ignoreValues supports and existing entries already use. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Let hooks ignore-value scope a rule to files, and stop churning the config Fallout from suppressing the overlay's font-size findings: the narrowest exception detector.ignoreValues supports was unreachable from the path the hook tells the model to use, so the guidance steered to the blunt instrument instead. - hook-admin's ignore-value now takes --file / --files / --file= / --files=, matching `impeccable ignores add-value`, which already had them. Without it the only file-scoped option was ignore-file, which silences every rule for a path permanently, including rules not yet written. - A bare wildcard value is now refused with a message pointing at either --file or ignore-rule. Previously `ignore-value <rule> "*"` quietly wrote a project-wide suppression from a single file's finding. - ignore-value keyed entries on rule+value only, so a second scope for the same rule overwrote the first instead of coexisting. Key on the file scope too. - An unknown flag folded into the value: `ignore-value overused-font Inter --shard` stored "inter --shard", matched nothing, and reported success. Reject it, as the sibling command does. Config churn: normalizeIgnoreValueEntries runs on every write and emitted keys as rule, value, files, reason, createdAt while the config on disk uses createdAt before reason. Any edit therefore rewrote every untouched entry (35 churned lines for a one-line change). Pin the canonical order in both copies of the normalizer and in ignores.mjs, and add a test that the two copies cannot drift apart. Also point the hook's own footer and reference/hooks.md at the file-scoped form first, and say plainly what ignore-file costs. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Correct the prose-gate docs and write down the no-bump-in-a-PR rule CLAUDE.md said the prose validator "deliberately skips skill/", which is only half true and cost a build failure this week: validateProse skips it, but validateSkillProse then scans skill/**/*.md and fails the build on em dashes plus the phrases with no technical reading. Document both gates, which files each one reads, and the line that actually matters in practice: an em dash in skill/reference/*.md fails the build, one in a skill/scripts/*.mjs comment does not. Each claim was checked against a real `bun run build`. Also record that feature PRs do not bump manifest versions or add changelog entries. It was not written down anywhere: not CLAUDE.md, not AGENTS.md, not the PR template. CLAUDE.md's "Bump when: CLI code changes" reads as an instruction to bump inside the PR that touches cli/, so say plainly that it names which component a change belongs to rather than when to edit the manifest. Put the rule in AGENTS.md too. That is the guide the agents opening PRs here actually read, so a rule about PR hygiene living only in CLAUDE.md would not reach them. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Bring Live progressive delivery and the generator subagent to Claude Code 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> * Fix inset-order detection, the unlocked artifact discard, and stray boolean flags Three of the four open review findings. The fourth is declined below. - The inset-stripe scan only matched layers starting with `inset`, but the keyword is order-independent: `box-shadow: 4px 0 0 var(--brand-accent) inset` paints the same stripe and was silently missed. Strip the keyword wherever it sits, but only as a standalone token, so a color like var(--inset-accent) is not mangled into `var(-- -accent)` and quietly reclassified as neutral. The fixture now covers both orders plus that token, and a trailing-inset neutral still passes. - The source-artifact discard deleted the preview without the source lock, unlike every other discard path. Take the lock. Narrower than reported, though: the server journals `discard_requested` as a fenced phase before live-accept runs and the publisher checks it three times, so a publish could never land on a discarded session. What this actually prevents is deleting the artifact under a publisher mid-critical-section, turning a clean stale_generation_epoch into an ENOENT crash. - benchmark-live-providers.mjs still compared `--headed` and `--skip-cleanup-control` against a boolean sentinel, so the `=true` spelling silently did nothing. My gap: I introduced boolFlag and converted benchmark-live.mjs but not this one. skipCleanupControl is now read once rather than twice, so the two call sites cannot drift. Declined: tightening the selector guard that skips `active` / `current` / `selected` tokens. It does cause false negatives on names like `.selected-feature`, but the rule's contract makes selection and focus indicators its one exception, and `.active-tab` / `.current-step` / `.selected-row` are syntactically identical to `.selected-feature`. No regex separates them, so tightening the guard trades missed stripes for false positives on exactly the case the rule exempts. The conservative skip is the intended behavior. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Classify failed accepts as errors, and fix parallel lane race/all misuse Two of the three new findings, plus the bug that chasing them exposed in my own earlier fix. The third is mitigated rather than broken; details below. Failed accepts reported success: live/completion.mjs only classifies a result as `error` when it carries `mode: 'error'`. Everything else unhandled falls through to `agent_done` with an ok ack, which is deliberate for the documented fallback paths (two tests pin it) but wrong for a real failure. So `accept_receipt_conflict` reported success, and reference/live.md's `handled: false` without `mode` bullet told the agent to "read file, find markers, edit" — hand-applying a second accept on top of the one the receipt already recorded. The same hole swallowed `source_locked`, which is mine: the earlier commit made lock contention return clean JSON so the agent could retry, but the classifier turned that failure into agent_done/ok, so the accept was dequeued and silently lost. Mark genuine failures with `mode: 'error'` through one `operationFailure` helper, and give live.md a `mode: "error"` bullet with per-error guidance: retry the same command on `source_locked`, never hand-edit, and on a receipt conflict report what the session actually resolved to. The deliberate fallback and markers-not-found handoffs stay untouched. parallel-compact lane orchestration: `Promise.race` settles on the first *settlement*, so one lane failing fast rejected the whole first-variant step while two lanes were still on their way to succeeding. `Promise.any` now takes the first success and only a total wipeout is fatal, reporting every lane's reason. The tail step's `Promise.all` surfaced a raw lane error non-deterministically; `Promise.allSettled` now reports how many lanes failed and why. Added a `requestImpl` seam so lane orchestration is testable without a provider key. Not a defect: the browser releasing Accept before the source write. That is the intended optimistic design, and it is safe because poll-lanes ranks accept at priority 0 against generate at 2, so a queued accept is always leased before a generate the user queues afterwards, even if the generate arrived first. Its source write lands inside the poll script before the next generate preflights. That invariant is load-bearing and had no tests at all; poll-lanes.mjs now has a suite covering it plus lease and type filtering. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Finish the failed-accept classification my last commit only half did All three new findings are the same root cause, and it is my incomplete fix: operationFailure only covered results built from a *thrown* error. Two paths it missed: - Two catches wrote the failure result as a multi-line literal, so the single-line replace skipped them. The Vue accept catch was still bare, exactly as reported; the Svelte one too, though its failures happened to be caught by completion.mjs's Svelte-only special case. - The accept implementations also *return* `{handled: false, error}` for their own checks (variant missing, template empty, original text ambiguous). Those never throw, so no catch ran and no `mode` was set. Both layers now agree, because each is reachable on its own: - live-accept marks any unhandled preview-path result via markPreviewFailure, keyed on `previewMode` — a clean discriminator, since only the preview branches set it and a plain wrapper never does. This is what the agent reads: reference/live.md routes on `mode`, so without it the agent was told "read file, find markers, edit" for a preview that has no markers in source. - completion.mjs replaces its arbitrary svelte-component special case with the set of preview modes whose variants live outside the user's source. That case existed for precisely this reason; Vue and source-artifact were simply never added, so the identical failure on those paths acknowledged as success. The plain wrapper keeps its manual handoff, which is the one shape with editable markers in source. Both deliberate handoffs (mode: 'fallback' and markers not found) still classify as agent_done, now pinned by a test so the generalization cannot swallow them. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Stop the progressive benchmark agent inventing a second variant on count:1 `Math.max(1, event.count - 1)` floored the tail request at one variant, so a one-variant request fetched a second direction and assembled two. Ask for `count - 1` and return the first variant untouched when there is no tail. Latent rather than live: the only caller hardcodes `count: 3`. The reason it is worth fixing is the caller inconsistency it exposed. tests/live-e2e/agent.mjs gates its split-progressive path on `event.count > 1`; benchmark-live-providers.mjs had no such guard, so it would have run the tail for a one-variant request, and the parallel strategy would have assembled its three fixed lanes regardless of what was asked for. Guard the caller the same way. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Drop the live generator subagent; fix the artifact decoy that broke accept The first real Claude Code Live run failed, and the subagent was not the cause. Root cause: progressive publication stages each revision as `.impeccable/live/artifacts/<id>-r<n>.<source-ext>`, nothing ever deleted them, and findSessionFile's walker skipped only node_modules/.git/dist/build. It searches src, app, pages, ... then `.`; a project whose source is not under one of those (this repo's own site lives in site/pages/) falls through to the `.` walk, where dot-directories sort before letters. So accept found the artifact instead of the real file. Two outcomes, both reproduced: where isGeneratedFile returns true it declines with mode: 'fallback' (what the run hit, after which the agent hand-carbonized several hundred lines across three stylesheets, including unrequested drive-by edits); where it returns false, accept writes the variant into the throwaway artifact and reports handled: true while real source never changes. The E2E suite could not have caught this. Every fixture puts source under `src/`, which is searched before the `.` walk can reach `.impeccable`. Five framework fixtures and three progressive scenarios pass because of fixture layout, not because the path works. I read that as evidence and shouldn't have. - Never search `.impeccable`: it is Impeccable's own state, never project source. - Retire a session's staged artifacts on accept/discard, so they cannot outlive the session and become a decoy for anything else that walks the tree. - Regression tests use a site/pages layout with artifacts present. All three fail against the previous code. Generator subagent removed, on both harnesses: The parent must hand-compress the design system into the handoff, and compression is lossy. Measured on the real run: a 6,826-char handoff carrying exactly one token reference, after the parent had itself read kinpaku-tokens.css. The subagent then spent 3 of its first 9 turns hunting DESIGN.md, gave up, and emitted 0 var(--token) uses and 22 raw oklch literals — violating its own spec's "Never invent raw colors when tokens exist" — including a 1:1 gold-on-gold contrast bug. Isolation is not a benefit here; knowing the design system is the job. Generation stays in the main thread, which already holds the tokens and writes them from the first byte, so carbonize is a move rather than a translation. Copy edits keep their subagent: applying a known set of ops to a named file is self-contained, so an isolated context costs nothing. That is the line. Progressive delivery stays for Codex and Claude Code, main-thread driven. Claude Code keeps the full benefit because its poll is a background task. Codex's poll blocks the foreground, so with no subagent the user sees variant 1 early via HMR but cannot accept it until the trio finishes; that is the cost of the simplification and it is worth naming. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Rip out the dead isolated-preview mode and the private repo's job Comparing this branch's live against main's turned up two whole features that never made sense here. -2,466 lines. 1. The isolated source-artifact preview was never switched on. `scaffoldSourceArtifactSession` is only reachable via live-wrap's `--isolated`, and nothing passes it: not the server's preflight, not live.md, nothing. Proved it end-to-end — the default wrap writes markers straight into real source and creates no previews/ session. So the mode was wired through three modules, carried its own accept/discard branches, browser branches, server metadata resolution, preview-mode classifier entry, and test suites, and none of it could run. Worse, live.md documented it as the active path and told the agent "The true source is only the publisher's hash fence and must remain byte-identical until Accept." That is false: the wrapper lands in source at scaffold time and each revision rewrites it. An agent following that sentence believes source is protected when it isn't, and the leftover artifacts are what made accept resolve the wrong file in the first real run. live.md now describes what actually happens, including that markers are visible in source until Accept or Discard. Removed: source-artifact.mjs, --isolated, the preflight's isolated option, the accept/discard branches, four dead browser branches, the server's previews/ resolution, the classifier entry, and their tests. Kept the previews/ gitignore pattern: an ignore line for a directory that cannot exist is free, and a test pins it. 2. Quality judging belongs to the private evals repo, which says so. runner/live/README.md there is explicit: the public repo owns protocol correctness, framework coverage, timing, source commit, recovery, and a rubric-free evidence bundle; the private repo owns the task corpus, baselines, comparative judges, and release-quality decisions — "Do not add quality rubrics, competitor comparisons, or broad fixture corpora to the public Live benchmark." This branch added exactly those: an LLM judge scoring 1-10 on "off-brand, generic-AI" (live-rendered-quality.mjs, judge-live-rendered.mjs), a cross-provider comparison with a BRAND_CONTRACT rubric (live-provider-benchmark .mjs, benchmark-live-providers.mjs), and a brand-fidelity fixture corpus. All removed, with bench:live:providers and their suite entries. Also removed tests/framework-fixtures/README.md's "External quality-eval fixtures" section: it documented a bench:live workflow using --fixture-dir, --agent=codex, --action and --evidence-bundle, none of which benchmark-live.mjs implements, plus an evidenceCapture block nothing reads. Kept: timing benchmarks (the public repo's half of that boundary), progressive publication, the source lock, poll lanes, and Nuxt/Vue component previews. Coverage note: deleting the isolated suites took the only tests for `source_locked` classification with them, so the plain wrapper path — now the only non-component preview — gets equivalent accept and discard coverage. Both new tests fail if mode:'error' is removed. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Flag inset stripes written with the two-length box-shadow form box-shadow takes <length>{2,4}: only the two offsets are required, so `inset 4px 0 red` is valid and paints the same single-edge stripe as `inset 4px 0 0 red`. The scan demanded a third length, so the short form was silently missed. Blur and spread now default to 0 when omitted, which is exactly the stripe shape the rule looks for. The neutral-color and blur/spread exclusions still hold: `inset 4px 0 #000` and `inset 4px 0 5px var(--brand-accent)` both pass. Fixture covers both orders of the short form plus those two exclusions, and fails against the previous regex. Third false negative found in this rule (after trailing `inset` and literal neutral colors), all from the same cause: the scan was written against one spelling of the syntax rather than the grammar. Prepared with AI assistance under maintainer direction. Co-Authored-By: Claude <noreply@anthropic.com> * Live: polling rework, source locks, preflight scaffolding, Vue previews Carved out of #371, minus progressive publication. Everything here works against real project source the way main's Live already does: the agent writes variants into the file the browser loaded, HMR fires, Accept promotes and carbonizes. Nothing is staged anywhere. Poll lanes. Events now carry an explicit priority: accept/discard/exit ahead of manual_edit_apply/steer/carbonize_cleanup ahead of generate. A long generate can no longer sit in front of the Accept the user just clicked. leaseEvent claims its lease before awaiting, so a slow prepare cannot hand the same event to two pollers. Source locks. A per-file mutex around every accept and discard path, keyed on a digest of the absolute path. Staleness is decided by owner-pid liveness rather than mtime, so a wedged lock clears when its owner dies instead of after an arbitrary timeout, and a slow-but-live accept is never stolen from. Only the owning process can release a lock. Preflight scaffolding. The server runs live-wrap (or live-insert) before the poll returns and hands the result back as event.scaffold. That walk is measured at ~7.6s on a large repo; moving it off the agent's critical path removes a deterministic tool round trip without touching the generated design. Falls back cleanly to the agent running the helper itself. Vue previews. previewMode: "vue-component" for Nuxt/Vue targets, matching the existing Svelte component path: variants compile as real SFCs from a dev-only directory so the route is never rewritten during generation, and Vite mounts them without invalidating page state. Accept is the only route write. Includes a Vue attr tokenizer that normalizes shorthand bindings (@x, :x, #x) to their canonical forms. Accept hardening. Every thrown failure now returns mode: 'error' rather than an ambiguous unhandled result, so a real failure is never classified as a deliberate manual handoff and silently dropped. The marker search skips node_modules/.git/dist/build/.impeccable. Shared CLI arg parsing extracted to scripts/lib/cli-args.mjs. Assisted-by: Claude Code * Drop the progressive benchmark, remove dead wrap scaffolding Review fallout from removing progressive publication. The Live benchmark existed to compare atomic against progressive delivery: compareModelBackedReports measures goToFirstVariantMs improvement of one over the other. With progressive gone it measures nothing against nothing. Worse, benchmark-live.mjs still passed `progressive` to bootFixtureSession, which no longer accepts it, so `--delivery progressive` was silently ignored and would have emitted reports labeled progressive that actually ran atomic. Silent wrong data is worse than a crash. It was built for progressive, so it goes with progressive: benchmark-live.mjs, its lib, its test, and the bench:live script. If an atomic latency baseline is wanted later, that is a smaller thing built on purpose. live-wrap.mjs: sourceOriginalLines was assigned and never read. Both found by review bots on #381 (Copilot). Assisted-by: Claude Code * Drop the Vue preview mode; it never reached Svelte's accept path Cursor found that inlineVueComponentAccept never receives paramValues, while the Svelte equivalent uses them in 23 places: Accept on a tuned Vue variant silently persisted the default and threw the user's tuning away. Chasing that corrected something I had asserted the other way round. I said Vue's raw-CSS-append was inherited from the Svelte path. It is not. svelte-component.mjs calls sanitizeAcceptedSvelteCss before writing, which sanitizes the CSS and bakes tuned params into it. vue-component.mjs had no sanitize step at all — it appended the variant's <style scoped> body into whatever style block came last, so a variant could leak CSS site-wide when the last block was global, and brace CSS landed in a lang="sass" block. Both are the same defect: the Vue mode mirrored Svelte's preview path without its accept-side subsystem (bakeParamValuesInCss, sanitizeAcceptedSvelteCss, appendSanitizedCssRule, rewriteAcceptedSvelteSelector, rewriteParamSelectors — roughly 200 lines of CSS rewriting). Both were introduced here, not inherited. A shipped Vue session could leak styles and discard tuning without saying so. So it comes out. The poll lanes, source locks, preflight scaffolding, and accept hardening do not depend on it and are worth landing now. Vue returns when its accept path reaches parity. The nuxt-vite7 fixture goes back to main's plain-wrapper shape. Assisted-by: Claude Code * Stop the lease redelivery test racing the scheduler CI failed `does not drop polled events until the agent acknowledges them` on a commit whose content was byte-identical to one that passed, which is the signature of a flake rather than a regression. The test leased an event for 50ms, then asserted a second poll saw a timeout because the lease was still held. That gave the whole second HTTP round trip a 50ms real-time budget: cross it and the lease expires, the event is redelivered, and the assertion fails for a scheduling hiccup instead of a bookkeeping bug. Locally it passed 6/6; a loaded runner is where it bites. Hold the lease for 1000ms so a round trip cannot cross it, and wait LEASE_MS + 300 before asserting redelivery, so each half has headroom in the direction it asserts. Verified by injecting a 60ms stall before the second poll: the old test fails with exactly the CI message, the new one passes. Assisted-by: Claude Code * Recover live sessions that reload past the generation done broadcast The preflight scaffold write (new in this PR) triggers a framework full-reload — Astro reloads the page for any .astro edit. When the agent's variant write and its done SSE land while the browser is mid-reload, the resumed page misses both the second HMR reload and the done broadcast: it comes back up on the scaffold-only source and waits in GENERATING at 0/N forever, with the finished variants sitting in source. This is the astro-vite7 CI timeout; the failure artifacts show the full sequence (scaffold at 26.319s, done at 26.515s, the new page's browser_resumed checkpoint at 26.653s, DOM still scaffold-only). Three-part fix: - session-store: agent_done now stamps a monotone generationCompletedAt on the snapshot. Browser checkpoints legitimately regress phase and arrivedVariants (a resumed page reports what it sees), so completion needed a field checkpoints cannot un-set. - live-browser: on every SSE (re)connect, compare the session summary's generationCompletedAt against local progress; when behind while GENERATING, pull the finished variants from source (same settle delay as the done handler's HMR-first fallback). Covers both orderings of resumed-checkpoint vs agent_done. Also, the source-fallback empty- wrapper branch no longer tears the session down mid-generation — a scaffold-only wrapper is a legitimate in-flight state, so stay in GENERATING instead of destroying a session the agent is still filling. - live-server: a browser checkpoint reporting generating/behind for a session whose generation already completed re-broadcasts the stored done (idempotent for every other tab), and connected-payload summaries expose generationCompletedAt for the browser-side check. Coverage: live-server unit tests for redelivery, the no-redelivery guard, and marker durability across checkpoint regression; plus a deterministic live-e2e scenario on astro-vite7 that blocks the reloaded page's SSE stream and mocks its HMR websocket dead until after the agent finishes, forcing the missed-broadcast window every run. All new tests fail against the pre-fix code. The e2e harness additionally gains an IMPECCABLE_E2E_ATOMIC_DELAY_MS lever (widens the scaffold-to-write window) and env-gated console/nav tracing (IMPECCABLE_E2E_CONSOLE=1) used to diagnose this. The hypothesis that preflight opens a wrapper-with-no-variants window came from Copilot's review sketch in the follow-up WIP PR; the killing mechanism differs from that sketch (nothing calls recoverEmptyCycling in the CI trace — the session hangs precisely because no code path runs at all), but the window is real and the guard it suggested is folded into the source-fallback fix. Assisted-by: Claude Code Co-Authored-By: Claude Code <noreply@anthropic.com> * Retry a completion-driven source fallback that reads only the scaffold Greptile flagged a hole in the previous commit's empty-scaffold guard: when a `done` has already been delivered, the source fallback gets exactly one read. If that read returns the preflight-only scaffold (a stale source view, or an agent whose write lands in multiple steps), the guard's silent return left the tab in GENERATING with no further event ever coming — the same stuck state the previous commit fixed, reintroduced through a different door. Callers that know generation finished (the done handler's fallback and the SSE-reconnect self-heal) now pass generationCompleted, and an empty read on that path re-reads the source up to 3 times before surfacing recoverEmptyCycling instead of hanging. Mid-generation callers are unchanged and still wait indefinitely — a real agent can legitimately take minutes between scaffold and write, and tearing that down was the original #385 hazard. The missed-done e2e scenario now also serves a captured scaffold-only copy for the first post-reconnect /source read, forcing the retry path every run. Verified failing against the pre-retry code (tab stranded in GENERATING, test timeout) and passing with it. Assisted-by: Claude Code Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2b1f36c43e |
Add concept world catalog and review workflow
AI-assisted: prepared by Codex at Paul's request. |
||
|
|
77c7d8e0fc | Refine product and visual work lifecycle | ||
|
|
bbed6eef08 |
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. |
||
|
|
e7ed663ecd |
Fix target-mode Live evidence capture
Capture below-fold selected targets and each progressive variant reliably for portable evidence bundles. AI-assisted implementation under maintainer direction. |
||
|
|
e0c19eff28 |
Export portable Live evidence bundles
AI-assisted implementation under maintainer direction. |
||
|
|
efa2ce9ed2 |
Preserve failed Live benchmark evidence
AI-assisted implementation under maintainer direction. |
||
|
|
4c07124198 | Deliver Live variant two independently | ||
|
|
2f619f9bdb | Prove Live benchmark target selection | ||
|
|
d293b803d0 | Calibrate Live rendered quality review |