Two failures the trace test found were structural, not model quirks. critique.md described the report's format and then went straight to writing a temp file, with no step saying to output the report. gemini-3.6-flash and luna both responded by bundling heredoc, snapshot write, trend read, and cleanup into one bash call and stopping, leaving a perfect archive nobody had read. A "Deliver the Report" step now precedes persistence, and persistence describes itself as a copy of what was already sent. gemini-3.6-flash failed three consecutive runs before this and its failures afterward all show the report reaching chat. The close is also step 6 of the persistence list rather than a section after it, since the same shape is what fixed delivery. Lineup: gpt-5.6-luna and deepseek-v4-flash out, gpt-5.6-terra in at reasoningEffort high (IMPECCABLE_SKILL_BEHAVIOR_EFFORT overrides), gemini 3.5 to 3.6. Provider options resolve from the model object inside the harness so the 21 runTurn call sites are untouched. Verified the effort actually reaches the API rather than being silently dropped. The Gemini bump was not cosmetic: 3.5-flash passed critique closes twice and 3.6-flash then failed three times against identical text. A version bump inside one family changed the outcome, so the README now treats cross-version carryover as unmeasured. Known floor, recorded: critique closes is flaky on gemini-3.6-flash, 1 run in 3. Two structural attempts moved it from consistently failing to intermittently passing and then stopped paying. claude-sonnet-5 and gpt-5.6-terra are clean. Prepared with AI assistance (Claude Code). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Skill-behavior tests
LLM-backed scenarios that verify how the impeccable skill drives context, command-reference, new-work, and native-platform loading. Each scenario runs against one current model from each supported provider (Anthropic, OpenAI, Google, DeepSeek).
These are the tests you re-run when you refactor anything in SKILL.md's
## Setup section. They fail when the agent stops following the loading
contract.
Run
bun run test:skill-behavior
IMPECCABLE_SKILL_BEHAVIOR_VERBOSE=1 bun run test:skill-behavior # dump per-scenario traces
IMPECCABLE_SKILL_BEHAVIOR_MODELS=claude-sonnet-5 bun run test:skill-behavior # scope to one model
IMPECCABLE_SKILL_BEHAVIOR_EFFORT=xhigh bun run test:skill-behavior # OpenAI reasoning effort (default: high)
Requires .env at repo root with at least one of ANTHROPIC_API_KEY,
OPENAI_API_KEY, GOOGLE_CLOUD_API_KEY, DEEPSEEK_API_KEY. Providers without a key are
skipped, not failed.
How it works
Each scenario:
prepareWorkspace()mints a temp dir, symlinks the canonical skill into<workspace>/.claude/skills/impeccable, and optionally writesPRODUCT.md/DESIGN.mdfixtures.runTurn()inlinesSKILL.md(placeholders neutralized) as the system prompt and runs Vercel AI SDKgenerateTextwith four workspace-scoped tools:bash,read,write,list, and a fake provider-neutralask_user_questionbacked by a deterministic simulated user.- The tools record every call into a
tracethat the test asserts on. - For scenario 4, a second
runTurnreuses turn 1'sresponseMessagesso the model sees a real multi-turn conversation.
The trace is the source of truth, not the model's free-form reply.
Scenarios
| # | Setup | Assertion |
|---|---|---|
| 1 | empty workspace | runs context.mjs; loads reference/init.md before implementation; automation is not an init bypass |
| 2 | PRODUCT.md only | runs context.mjs 1-3 times; loads reference/new-work.md to resolve visual authority, establish a world when needed, and develop the surface |
| 3 | PRODUCT.md + DESIGN.md | runs context.mjs 1-3 times; receives the committed design system and loads reference/new-work.md for the task-scoped concept |
| 4 | PRODUCT.md + DESIGN.md, context already loaded in turn 1 | turn 2 does not re-run context.mjs |
| 5 | PRODUCT.md without the legacy ## Register field and no DESIGN.md |
runs context.mjs; greenfield craft loads reference/new-work.md, not init, to establish the missing world |
| 6 | PRODUCT.md + DESIGN.md + a minimal index.html; prompt is /impeccable polish |
loads reference/polish.md |
| 7 | same fixture; prompt is /impeccable audit |
loads reference/audit.md |
| 8 | PRODUCT.md + DESIGN.md + a SvelteKit scaffold (src/app.css, components, +page.svelte); prompt is /impeccable polish src/routes/+page.svelte |
reads at least one project code file (CSS / component / page) — not just the skill's reference files |
| 9 | PRODUCT.md + index.html + a seeded update cache with a newer version (skillVersion copy-mode so context.mjs has a SKILL.md to version-check against); prompt is /impeccable polish index.html |
context.mjs runs and its output carries the UPDATE_AVAILABLE directive (proven via captured bash output); the agent does not auto-run npx impeccable update (it must ask first) |
| 10 | no PRODUCT.md + a minimal index.html; prompt is /impeccable polish index.html |
runs context.mjs, loads reference/polish.md, and does not divert into reference/init.md |
| 11 | empty workspace; prompt is /impeccable shape ... |
runs context.mjs; resolves reference/init.md before planning the surface |
| 12 | empty workspace; prompt is natural-language build intent with no command word | runs context.mjs; resolves reference/init.md before implementation |
| 13 | empty workspace; prompt is /impeccable teach |
runs context.mjs and diverts into reference/init.md because teach aliases init |
| 14 | PRODUCT.md with ## Platform: ios (native iOS app); prompt is /impeccable craft a tide detail screen |
context.mjs runs and emits the contents of reference/ios.md directly, placing native conventions in context without a second model-directed read |
| 15 | same iOS fixture; prompt is /impeccable audit |
agent loads reference/audit.native.md (the Commands-table native variant, routed instead of audit.md) |
The workflow-contract file adds end-to-end assertions for attended fresh init, an initialized natural build request, replacement-world redesign, scope-preserving bolder refinement, and critique's closing question. It checks question order and context/artifact writes rather than only reference-file loading.
critique closes with the question or an explicit skip line is a regression
guard, not a routing check. A critique that prints its report and then stops,
asking nothing and printing no Questions skipped: <reason> line, is an
incomplete run: the close is half the deliverable, and polish downstream has
no priorities to inherit without it. The fixture page is deliberately broken
enough to put the report past the three-Priority-Issue threshold, so the run
cannot reach the skip branch on merit. The assertion is deliberately loose about
how the run closes, because either close is valid; what it forbids is neither.
Workflow-contract baseline (2026-08-13, current lineup)
Measured while checking whether an {{ask_instruction}} rewrite had regressed
anything.
The last two columns are no longer in the default lineup. gpt-5.6-luna and
deepseek-v4-flash were dropped in 2026-08 for being below the frontier tier:
they fail scenarios by stopping mid-run or archiving a report without stating
it, which is model-floor behavior rather than a skill-text defect. Their columns
stay here because they are the record of what a weaker model does with this text,
and that is the useful part. Reproduce with
IMPECCABLE_SKILL_BEHAVIOR_MODELS=gpt-5.6-luna,deepseek-v4-flash.
Against the current default lineup, two cells are the known floor:
redesign replaces DESIGN is flaky, and critique closes is flaky on
gemini-3.6-flash. A regression is a failure beyond those two.
| Scenario | claude-sonnet-5 | gpt-5.6-terra | gemini-3.6-flash | luna / deepseek (dropped) |
|---|---|---|---|---|
| attended fresh init | not measured | not measured | not measured | not measured |
| initialized natural build | not measured | not measured | not measured | not measured |
| redesign replaces DESIGN | flaky | not measured | not measured | not measured |
| bolder refinement | not measured | not measured | pass (on 3.5) | luna pass, deepseek fail |
| critique closes | pass (2 of 2) | pass (2 of 2) | flaky (1 of 3) | luna fail (1 of 6), deepseek flaky |
Gemini cells marked on 3.5 were measured on the superseded gemini-3.5-flash
and have not been re-run on 3.6. That distinction is not pedantic. critique closes passed twice on 3.5-flash, then failed three times in a row on 3.6-flash
against identical instruction text, and only passed once the report delivery step
was made explicit. A version bump inside one family changed the outcome, so treat
cross-version carryover as unmeasured rather than inherited.
not measured means exactly that: the cell was never run in isolation on this
lineup. Only the scenarios under investigation were scoped per model. The rows
are worth keeping anyway, since a scenario absent from the table is easy to
mistake for a scenario that passed.
bolder refinement, deepseek-v4-flash. The model runs context.mjs, reads
bolder.md, craft-floor.md, and current.html, then ends its turn without
editing anything: empty writePaths, no ask_user_question call, well short of
the 16-step cap. Confirmed identical on HEAD with bolder.md reverted, so it is
not a skill-text problem. Same shape as the gpt-5.4-mini scenario 6/7 failures
below: the model consumes the references and then declines to act.
critique closes: the three ways a critique fails to land. The scenario
asserts emission order, not just the presence of a question, because the command
fails in three distinct ways and only one of them was the reported bug:
- No close. Report lands, no question, no skip line.
polishdownstream inherits nothing. - Question before report. The question is emitted first and the report after it, so the report is withheld until the user answers. Observed directly on gpt-5.6-luna, and the reason the invariant is a position rule ("the question is the LAST thing in the response") rather than a statement about prose order.
- Report never spoken. The report is authored straight into the persistence heredoc, archived, and never written to chat. A perfect snapshot and a user who sees nothing.
Mode 3 is the one worth understanding, because it was structural rather than a
model quirk. critique.md described the report's format and then went directly
to writing a temp file, with no step that said to output the report. Both
gemini-3.6-flash and luna responded by bundling heredoc, snapshot write, trend
read, and cleanup into a single bash call and stopping. The Deliver the Report
section exists to close that gap, and it worked: gemini-3.6-flash failed three
consecutive runs before it, and its failures afterward all show the report
reaching chat.
Mode 1 is not fixed on gemini-3.6-flash. It passes 1 run in 3 on the final
text. Two structural attempts were made and neither settled it: the close was
promoted into Hard Invariants with a printable Questions skipped: <reason>
string, then made step 6 of the persistence list so it would sit inside the
numbered flow rather than after it (the shape that fixed mode 3). Both moved it
from consistently failing to intermittently passing, and further prose tuning
was not paying, so it stopped. claude-sonnet-5 and gpt-5.6-terra are clean.
Treat this cell as the known floor and re-measure rather than tuning blindly:
the next useful move is probably a structural one, such as making the close
something the run cannot syntactically finish without, not another paragraph.
Read the counts here as what they are: small samples on a nondeterministic system, several of them gathered while the instruction text was still changing between runs. They support "the close works on the current lineup" and not much finer than that. Re-measure rather than assuming when the lineup changes.
redesign replaces DESIGN, flaky. It has failed on two different assertions
across runs (designWrite > question and implementation > designWrite), and on
one run claude-sonnet-5 exhausted the 300s per-test timeout instead of asserting.
The traces never load document.md; the ordering under test comes from
new-work.md. Re-run before believing a single red result here. Which model
produced which failure was not pinned down, so the row records only that the
scenario is unstable.
The bolder claude-sonnet-5 cell is unmeasured for a specific reason: the scoped
run that produced this table used a 180s cap, which sonnet exceeded. That is a
timeout, not a failure, and it is why the guidance below insists on 300000.
Scoping a run while investigating
Both files honor --test-name-pattern, which is much cheaper than a full sweep
when bisecting one scenario:
IMPECCABLE_QUESTION_DISABLED=1 CI=1 IMPECCABLE_SKILL_BEHAVIOR_MODELS=deepseek-v4-flash \
node --test --test-timeout=300000 --test-force-exit \
--test-name-pattern="bolder refinement" tests/skill-behavior/workflow-contract.test.mjs
Keep --test-timeout at 300000. A tighter cap turns claude-sonnet-5's slower
runs into timeouts that look like failures. Set IMPECCABLE_QUESTION_DISABLED=1
and CI=1 so serve-question.mjs cannot open a browser window on the host. Pipe
to a file rather than tail; node prints the failing-test summary at the end,
and truncating it costs you the per-model attribution.
Baseline state (2026-05-20, previous cheap tier)
Historical record. The default models are now
claude-sonnet-5andgemini-3.6-flash. The table below was measured on an older cheap tier (claude-haiku-4-5/gpt-5.4-mini) and is kept as the historical record. Re-measure on the current lineup and update this section; the stronger models are expected to clear the scenario 6/7 routing failures that the old gpt tier showed.
Captured after moving sub-command reference loading from step 4 to step 2
of Setup (so the agent loads reference/<command>.md right after
context.mjs, before "doing the work" preempts it), and tightening
step 3 to require at least one project code read even when a sub-command
reference loads first. Use this table when comparing pre/post refactor:
a regression is "more failures than baseline", not "any failures at all".
| Scenario | claude-haiku-4-5 | gpt-5.4-mini | gemini-3.1-flash-lite |
|---|---|---|---|
| 1 (no context) | pass (rare flake — agent stops after context.mjs without loading init.md) |
pass | pass |
| 2 (product only) | pass | pass | pass |
| 3 (product + design) | pass | pass | pass (rare flake — sub-command ref loads but world ref doesn't) |
| 4 (already loaded) | pass | pass | pass |
| 5 (no register field, task-cue cascade) | pass | pass | pass |
6 (polish routing) |
pass | fail | pass |
7 (audit routing) |
pass | fail | pass |
| 8 (existing project, explore design system) | pass | pass | pass |
21-22 / 24 typical. The stable failures are gpt-5.4-mini scenarios 6 and 7:
the model reads index.html (the target file), recognizes "polish" or
"audit" as a familiar action, and proceeds with the work without ever
loading the sub-command reference. Stronger SKILL.md wording (MUST,
"non-optional", reordered earlier) didn't move it; this looks like a
model-floor behavior rather than a skill ambiguity. Claude and Gemini
honor the load.