Install any skill in seconds. Free to start, no credit card required.
Get Started Free →create a fraimz, make fraimz, prove it works, frame proof, PR proof, validate experience, e2e evidence, fraimz.html. The full fraimz loop — frame the claim, drive the real app via CDP, validate/repair, output fraimz.html. Use whenever a task ends with "please create a fraimz" or any change needs end-to-end proof.
.claude/skills/devin-axis-fraimz/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 40% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 17% | 0% |
fraimz is the canonical proof artifact: a single fraimz.html (evals/results/<run-id>/fraimz.html) with one frame per step. Each frame binds a claim, the action the end user took, the assertion that witnesses the side effect, a voiceover that narrates the step, and a validated screenshot. It is the thing a human looks at (and listens to) to understand, at a glance, that an experience works.
This skill owns the prove-it loop. In the demo-driven journey (voiceover skill: script → worktree → build → fraimz → PR) this is how the build is verified; when orchestrating, the orchestrator drives this loop and delegates code repairs to the executor.
A fraimz is never a bare test log. The flow declares which demo it is via kind in evals/flows/<id>.flow.mjs:
kind: "user-facing" — the end user is the protagonist; the frames walka real journey through the UI. Default for any feature, fix, or UX change.
kind: "internal" — for changes with no visible surface (perf, storageswaps, invariants, refactors): the frames demonstrate the internal claim in a way a reviewer can still follow. Terminal/tooling demos may set requiresApp: false to run without CDP; their frames carry claims, assertions, and ctx.output command output instead of screenshots (see evals/flows/voiceover-first-dx.flow.mjs).
If you cannot say which of the two your fraimz is, you have not framed the experience yet.
The narration is the spec and it comes BEFORE the flow (and ideally before the feature) — alignment and approval belong to the voiceover skill / /voiceover. The approved script lives at evals/voiceovers/<flow-id>.md; pnpm fraimz scaffold <flow-id> generates the flow from it, and the runner fails any flow whose narration drifts from the approved file. Every ctx.prove must carry a voiceover (one or two spoken-style sentences about what the viewer sees, never implementation); frames without one are flagged in the artifact — treat that as a failure for any flow you touch.
Make fraimz whenever a change can alter behavior observable outside the process: filesystem, runtime DB, server endpoints, sessions, config, provisioning, cloud sync, network. Also for changes you expect to be inert (refactors, renames, dead-code removal): the job is then to prove the canonical core flow is unchanged — open the app → write a message → get a response → close → reopen with the session intact (evals/flows/core-flow.flow.mjs). Pure docs/comments and types-only changes with no runtime path may skip — but say so explicitly.
Never report success from a click or a return value alone. Every meaningful step is: observe → act → observe → assert — and repair before verdict.
and pick the demo kind. State both back before proceeding.
evals/voiceovers/<id>.md(via the voiceover skill), then pnpm fraimz scaffold <id> — or reuse an existing flow in evals/flows/.
REST/DB/filesystem checks only witness the side effects. Every meaningful step uses ctx.prove("claim", { voiceover, action, assert, screenshot }).
pnpm fraimz --flow <id> --cdp-url <electron-cdp-url>.
route, error state, missing text, stale dialog, duplicate image, missing voiceover), fix the visible state or the code and rerun until every claim has a passing assertion, a narrated voiceover, and a valid screenshot.
fraimz.html + report.md /report.json to evals/results/<run-id>/; post it as a PR comment with pnpm fraimz --flow <id> --pr [number] (--pr alone targets the current branch's PR). Report Passed only when fraimz exists and every claim is backed by an observable assertion; otherwise Incomplete / Failed, stated honestly with repro steps.
Local Electron (fastest for a worktree you changed):
bashIPOLLOWORK_ELECTRON_REMOTE_DEBUG_PORT=9826 pnpm dev # & in background pnpm fraimz --flow <id> --cdp-url http://127.0.0.1:9826
Daytona sandbox (isolated, VNC-visible; see daytona-electron-test):
bashbash .devcontainer/test-on-daytona.sh <branch> --artifacts-volume pnpm fraimz --flow <id> --cdp-url <printed-electron-cdp-url>
The runner default-probes :9825 (Daytona) then :9823 (local pnpm dev); pass --cdp-url for any other port. pnpm evals --all --stack den brings up the cloud stack for env-gated cloud flows.
cdpTarget on the coded flow when the default iPolloWork page is not the intended surface; do not depend on an ambient browser-tool target.
__ipolloworkControl readiness. Attaches after boot and resets on configreload: ctx.waitFor("Boolean(window.__ipolloworkControl)") before driving, and re-wait after any "Reloading OpenCode config".
dirty; start steps by restoring the state you need (e.g. Escape a stale dialog).
assert on (the rendered row), not for a spinner to disappear.
waitFor that returns true onlyonce the DOM actually reflowed, then a separate ctx.eval to measure.
requireText: ["foo"],rejectText: [...], hashIncludes: "/route"). A screenshot with no validation metadata is a checkpoint, not proof.
guarantees; don't fail the run on a cosmetic extra the fix never promised.
import.meta.env.DEV onexisting eval.* control actions; keep them out of production paths and say so.
ctx.eval, ctx.waitFor, ctx.waitForText, ctx.clickText, ctx.fill, ctx.navigateHash, ctx.control(actionId, args), ctx.expectText, ctx.expectNoText, ctx.expectHashIncludes, ctx.assert, ctx.screenshot(name, { claim, voiceover, requireText, rejectText, hashIncludes }), ctx.output(name, text), and the headline ctx.prove("claim", { voiceover, action, assert, screenshot }). Reference flow: evals/flows/session-search-grouped.flow.mjs.
evals/README.md — runner, flags, conventions, full ctx.* reference.evals/flows/ — existing coded flows to reuse or pattern-match.evals/voiceovers/ + evals/runner/voiceover.mjs — approved scripts,parser, drift check, scaffolder; the voiceover skill owns the journey around this loop.
daytona-electron-test (sandbox launch), the runner's Daytona-nativecomputer-use helpers, and daytona-recording-artifacts (video).
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | fail→pass | 19,771 | 12,058 | -39% | 1 | 1 | 0% | 2,230 | 3,271 | +47% | 0 | 0 | — |
case-01 | fail→fail | 14,941 | 15,956 | +7% | 1 | 1 | 0% | 360 | 2,311 | +542% | 0 | 0 | — |
case-02 | fail→fail | 42,695 | 16,550 | -61% | 1 | 1 | 0% | 8,249 | 2,549 | -69% | 0 | 0 | — |
case-03 | fail→fail | 21,753 | 50,122 | +130% | 1 | 1 | 0% | 2,703 | 10,671 | +295% | 0 | 0 | — |
case-05 | fail→pass | 17,980 | 10,137 | -44% | 1 | 1 | 0% | 2,057 | 2,872 | +40% | 0 | 0 | — |
case-06 | fail→pass | 19,165 | 12,407 | -35% | 1 | 1 | 0% | 2,152 | 3,304 | +54% | 0 | 0 | — |
case-07 | fail→pass | 17,714 | 8,478 | -52% | 1 | 1 | 0% | 1,946 | 2,567 | +32% | 0 | 0 | — |
case-08 | fail→pass | 20,875 | 10,248 | -51% | 1 | 1 | 0% | 2,458 | 2,872 | +17% | 0 | 0 | — |
case-09 | fail→pass | 18,424 | 12,150 | -34% | 1 | 1 | 0% | 2,046 | 3,310 | +62% | 0 | 0 | — |
case-10 | pass→pass | 20,472 | 11,568 | -43% | 1 | 1 | 0% | 2,362 | 3,122 | +32% | 0 | 0 | — |
case-11 | fail→pass | 13,907 | 8,451 | -39% | 1 | 1 | 0% | 1,518 | 2,613 | +72% | 0 | 0 | — |
case-12 | fail→pass | 16,127 | 8,993 | -44% | 1 | 1 | 0% | 1,723 | 2,588 | +50% | 0 | 0 | — |
case-13 | fail→pass | 16,382 | 10,387 | -37% | 1 | 1 | 0% | 1,782 | 2,915 | +64% | 0 | 0 | — |
case-14 | fail→fail | 13,354 | 6,705 | -50% | 1 | 1 | 0% | 1,205 | 2,246 | +86% | 0 | 0 | — |
case-15 | fail→pass | 20,353 | 10,558 | -48% | 1 | 1 | 0% | 2,630 | 2,989 | +14% | 0 | 0 | — |
case-16 | pass→pass | 19,134 | 12,692 | -34% | 1 | 1 | 0% | 2,183 | 3,359 | +54% | 0 | 0 | — |
case-17 | fail→fail | 21,189 | 7,897 | -63% | 1 | 1 | 0% | 2,556 | 2,522 | -1% | 0 | 0 | — |
case-18 | fail→pass | 21,433 | 7,374 | -66% | 1 | 1 | 0% | 2,632 | 2,397 | -9% | 0 | 0 | — |
case-19 | pass→pass | 13,831 | 8,133 | -41% | 1 | 1 | 0% | 1,462 | 2,425 | +66% | 0 | 0 | — |
case-20 | fail→fail | 19,813 | 18,902 | -5% | 1 | 1 | 0% | 2,349 | 4,335 | +85% | 0 | 0 | — |
case-21 | pass→pass | 54,571 | 25,548 | -53% | 1 | 1 | 0% | 4,305 | 5,875 | +36% | 0 | 0 | — |
case-22 | pass→pass | 14,949 | 13,045 | -13% | 1 | 1 | 0% | 1,957 | 3,518 | +80% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +50 percentage points is the difference between those two pass rates over the 19 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.