Commit Graph
60 Commits
Author SHA1 Message Date
Paul BakausandClaude 7394bb41a1 Rebase reconciliation: fold main's post-freeze work into the swapped tree
The rebase onto origin/main brought changes whose JS engine halves left the
tree with the swap. This commit reconciles what survives:

- Suite map: register main's comp-fidelity unit tests (build-phase,
  comp-diff, font-match, hero-checks) in the core suite and
  live-browser-ignores in the live suite.
- Payload guard: the skill scripts payload now allowlists the comp-fidelity
  build pipeline (comp-spec/comp-diff/build-phase/font-match and their libs),
  the one Node toolchain that has not moved into the engine.
- Drop skill/scripts/live/project-ignores.mjs, lib/live-path-globs.mjs, and
  their test: they import hook-lib/live-inject/impeccable-paths, which the
  swap deleted, and their consumer (the JS live server) is the engine now.
- skill text: the comp pipeline's calls to engine verbs (generate-image,
  embed-prompt) use the launcher spelling.
- Oracle: re-record 17 detect goldens over the fixture set main changed
  (oklch #592, color-mix #578, 1D grid #615, the two comp-fidelity rules)
  and record the gap in DELTAS.md; those JS rule changes are not yet ported
  to the engine, and the goldens pin its current behavior.

bun run test (oracle included) and bun run build are green on this tree.

AI-assisted change: implemented with Claude Code.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaJv2c4oN8wS7Ttq4XRqyx
2026-08-31 20:00:04 -07:00
Paul Bakaus dac3f77d89 Scripts dir: replace the Node scripts with the impeccable launcher
skill/scripts keeps command-metadata.json and the page JS; every .mjs entry
point, lib/, and live/ are gone (the binary owns those verbs). Adds the POSIX
launcher, impeccable.cmd, VERSION (copied from the new root ENGINE_VERSION),
scripts/fetch-engine.mjs (bun run fetch:engine) to pull the pinned binary
into skill/scripts/bin/<os>-<arch>/, and gitignores that bin dir.

Prepared with AI assistance (Claude Code).
2026-08-31 19:57:32 -07:00
Paul Bakaus f1e8d4e70e Simplify named rule extraction
AI-assisted by OpenAI Codex under maintainer pbakaus's scheduled-refactor authorization.
2026-08-31 12:59:53 -07:00
Paul Bakaus 9434dde9af Merge main: skipScan visual-contrast coverage, live overlay waivers, generated output sync
The generated browser bundle is rebuilt from the merged engine sources in the next commit's build step (both branches had regenerated it).

AI-assisted (Claude Code).
2026-08-28 16:01:35 -07:00
Paul BakausandAbdul Wahab 08b03e8763 Centralize live path glob matching
AI-assisted change prepared by Codex under scheduled architecture-simplification authorization from maintainer pbakaus.
2026-08-28 15:01:21 +05:00
Paul BakausandAbdul Wahab 2debb7078f Hero refuses inline SVG illustrations; finish cannot record ship over an open phase; the comp-led path names its model tier
From the human review's most repeated pin ('terrible svg instead of asset', on every model) and from sessions that wrote 'ship' with the hero open. Icons, arrows, chevrons, and runtime data charts stay code; diagrams, notation, and leader lines are plates.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 38170564c6 Plates gate refuses a comp crop shipped as a plate; text readings include letter-spacing
From the final review batch: 'bad asset crop (crops are never allowed)' twice, 'letter spacing way too wide'. A crop resampled to the region scores 99.8% structure against the raw region; a produced plate scores 30-60.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 77acd73cd3 Hero readings: sibling regions fold into one line; ink colour only on type at cap 16+
A build reached hero 81% and stalled on eight staff rows read one at a time (and both ways on ink colour). The session asked the user and was told to build as written; the force was legitimate and recorded.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab f80846ad85 Side-by-side shows the capture, not the shift-padded copy; colour reading on unmeasurable text; line pitch on 3+ lines
The shifted copy's padding read as a white 'letterbox' on the build in every human review. A vertical spine came back white on red where the comp had black in five builds; its ink colour is now compared even though its type cannot be measured.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 07e3fad3b6 Spec refuses a plate box that cuts its artwork; hero counts strong small invented inserts; painted-note regex no longer matches a label
From the second review batch: the best build of the fifth sweep passed the hero at 87% with the cover arch cut flat on the left (object-fit: cover on a box narrower than the shape), and legends, badges, and extra controls one or two cells wide slipped under the invented-ink floor. comp-spec measures the artwork's contiguous contact with each box edge against the page ground; the spec gate refuses such a box unless bleed is set. Human pass line landed at comp-diff 72-73; HERO_MIN stays 0.72.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab bae0c24f45 Hero readings go advisory after three unchanged attempts; overall shows a decimal near the floor; a single link is not a strip
One cf6 session spent 27 attempts on the same three readings and read '72% < 72%'.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 00a180380e Hero gate reads type, strips, and invented ink as numbers; every region needs a note; missing beats the text relaxation
From the first human review of sweep-3 builds (pins on 12 samples): fonts at the wrong size, weight, colour, or place; nav bars too tall; kickers and dividers the comp does not have; a footer strip pushed off the frame that read as drift on ground colour alone; a drawing filed as chrome with no note to catch it.

- lib/hero-checks.mjs: textRegionCheck (cap height, line count, ink density, ink colour, first-line offset vs the comp crop, measured fresh), chromeStripCheck (first rule row), inventedInk (build energy over a calm comp cell and neighbourhood). Wired into gateHero as reasons; invented ink vetoes at 4% of cells.
- comp-diff verdictFor: detailRaw < 0.15 is missing whatever the palette.
- comp-spec: every region carries a note.
- font-fingerprint: the tall-line filter takes its median over lines with real mass, so two display lines above a small line are not 'tall'.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 3e7e85daf7 font-fingerprint: full-height inked columns leave the row profile; forceAllowed needs the user's reported words and a downgrade
Staff rules and a black page edge fused eight track rows into one 389px 'line'. A session forced two gates by quoting a brief line ('should feel like an extension of her artwork') as permission; a force now needs the user's words reported or quoted, a downgrade verb, and the comp noun in one reason.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 2cc60a5348 font-fingerprint: measure the dominant lettering class in a mixed crop; NEXT prefers generate-image --plate
A comp region drawn on the 10x10 grid over-covers: a body-copy crop carries the last headline line above it and a drawing beside it, and one session measured 'thread-body' at cap 160px off a carburetor drawing and ranked Londrina Shadow for it. Tall non-text 'lines' leave the mass reference; lines cluster by cap height and the cluster holding the most ink (multi-line first) is measured, re-applied after upsampling.

The plates NEXT line now names generate-image.mjs --plate as the tool (harness image tool only as fallback) after a session spent 25 turns keying plates with magick.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 99f73390db Comp fidelity: font ranking that holds without a browser, spec refuses painted chrome, control-box veto only for discrete controls
font-match / catalog index
- Index schema 2 adds a third render, 48c (48px cap, ALL CAPS text). Caps headline crops have no x-height band and ranked against mixed-case renders as barcode faces; they now route to the caps render.
- Non-text families (barcodes, redacted, flow, dingbats, effect faces) are excluded from candidates.
- The distance adds a gross width and weight gap (log ratio of advance and ink density) so a face 50% wider or 35% lighter cannot rank first on run-length detail alone; the index stores those readings.
- Multi-line all-caps crops vote on x-height across lines: one line's crossbars no longer give the crop a spurious x band.
- With no browser, --rank records the catalog's nearest face (source catalog, size estimated) so the spec gate can close; the NEXT line and new-work.md say not to install a browser or hand-write a choice.
- font-match stamps the choice it writes; the spec gate refuses a chosen face it did not write (sessions typed Arial Narrow into spec.json to pass).
- IMPECCABLE_NODE_MODULES lets a harness lend a playwright.

comp-spec
- A region note that describes painted material (diagram, drawing, photo, texture...) under a code kind is refused at the spec unless codeDrawn is set: the exploded carburetor filed as chrome is a plate about to be redrawn in SVG.

build-phase
- The control ink-box veto applies only when the comp's ink is a discrete element and the build's box is too; a full-width bar told one session six times that 1376x87 was 1382x102 with no edit able to move it.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
Paul BakausandAbdul Wahab 48350ffb14 Read WebP/JPEG comps through a sibling PNG cache instead of forcing PNG
comp-spec, comp-diff, build-phase, font-match, and generate-image now decode any comp raster via loadRaster(), converting non-PNG input to <file>.png next to the source. Sessions used to hit 'not a PNG' and rewrite the .webp in place with PNG bytes, which broke transcript replay (a later step rewrites the comp beyond the cut) and left a mislabeled file.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00
95294e464a font-match v2: fingerprint the comp lettering and pick candidates from a Google Fonts catalog index
lib/font-fingerprint.mjs replaces the three-number fingerprint with
size-invariant shape features (x-height ratio, stroke contrast, stem width,
run-length quantiles, roundness, serif signal, width spread) and a
noise-normalized distance; family recall on a held-out self-test rose from
13% to 72% top-5. data/font-index.json carries the whole Google Fonts
catalog (3,092 faces at two cap sizes, 707 KB); font-match --rank fingerprints
the comp crop, takes the 25 nearest faces from the index (plus the model's
own names), renders them at the comp's cap height, ranks by the same
distance, and prints a proof sheet and the CSS to use. scripts/build-font-
index.mjs rebuilds the index at release time.

AI-assisted (Claude).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-28 06:13:59 +05:00
cacb2868ac No comps outside the state: generate-image refuses .impeccable/mocks/ output while a roll is pending and build-phase has not started
The first paid confirmation sweep showed the failure: models rendered the
three comps first and ran build-phase.mjs start after, so a session cut at
the composition pick carried no state.json and the resumed model followed
the conversation ('translate the comp into HTML now') instead of the
phases. Decision comps (.impeccable/mocks/decision/) are unaffected;
--force-mock overrides.

AI-assisted (Claude).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-28 06:13:59 +05:00
d9a155be8c Gates that cannot be talked past: metric hardening, one start command, uncovered-ink refusal
From a forensics pass over twelve runs and two adversarial passes over
the metrics:

- comp-diff: detail is signed and penalizes invented energy; regions with
  structure under 0.3 (or painted regions under 0.45 / added detail over
  0.4) are contradicted whatever the mean says; palette ramp tightened;
  region crops inherit the whole-image best translation so a shifted page
  is not eight contradicted regions.
- hero gate: fails on any contradicted plate/image/text region (chrome and
  controls keep the one-third allowance), on a capture that is not the
  comp's frame, on a palette that is not the comp's, and on an organic
  clip-path drawn inside a raster region's box.
- plates gate: scored against the comp crop with overlapping text/chrome
  painted out (comp-spec plateReference; generate-image uses it too), with
  a structure floor and an added-detail veto; the real plate passes, noise,
  mirrors, mosaics, and other regions do not.
- comp-spec refuses a regions file that leaves comp ink unnamed.
- The direction-choice ping is folded into build-phase.mjs start
  --direction --kind; the roll writes .impeccable/build/pending.json and
  context.mjs / detect.mjs report COMP_ROUND_OPEN until the hero gate
  passes. A code-led config makes start print the contract step and stop.

AI-assisted (Claude).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-28 06:13:59 +05:00
b0fc2e8801 Add comp-diff, comp-spec, and build-phase: measured comp fidelity for the build phase
Dependency-free PNG codec, perceptual metrics (structure / color / detail /
bands), side-by-side + heatmap + per-region crops, a measured spec from the
approved comp (grid overlay, sampled palette, plate list), and a phase state
machine whose spec / plates / hero gates run the diff instead of asking the
model to remember the image.

AI-assisted (Claude).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-28 06:09:46 +05:00
Paul Bakaus 809976638d Share doctor boot finding policy
Centralize the shared boot artifact checks so doctor adds only its deep checks while preserving the existing finding order and CLI contracts.

AI-assisted: prepared by Codex under maintainer pbakaus scheduled-refactor authorization.
2026-08-22 11:58:26 -07:00
Paul Bakaus 8d62b135fe Remove dead design parser paths
Delete unreachable inline color parsing helpers and unused regular expressions without changing the DESIGN.md parser contract.

AI-assisted: prepared by Codex under pbakaus’s scheduled architecture-refactor authorization.
2026-08-19 11:29:00 -07:00
Paul BakausandGitHub 21ad321a97 Centralize surface route normalization (#600)
Consolidate explicit and inferred surface route canonicalization behind one private rule, with characterization coverage for equivalent and invalid inputs.\n\nAI assistance: OpenAI Codex prepared this change under pbakaus's scheduled architecture-simplification authorization.
2026-08-16 14:24:31 -07:00
Paul BakausandClaude Opus 5 c489335799 Build path becomes a config key existing projects can actually reach
The build-path preference shipped as a question only `init` asks, written to
a file only `init` writes. Nothing routes an initialized project back through
init, so every existing project took the comp-first default without anyone
choosing it, and the only recourse was a footer toggle that binds one session.

Neither the setting nor the round that preceded it ever reached a release
(skill-v4.0.4 has no `buildPath`, no `comp-led`, no `.impeccable/settings.json`),
so the PRODUCT.md standing-commitment fallback describes an era that never
existed publicly. It is deleted rather than honored: told a field might exist,
models go hunting for it and preserve it.

- `buildPath` moves from `.impeccable/settings.json` into the unified
  `.impeccable/config.json`, which already has a known-keys registry, doctor
  coverage, and a gitignored `config.local.json` override. Whether a machine
  has an image tool is a property of that machine, so the local file wins.
- new-work captures the answer from behavior instead of an interview: a toggle
  flip on a project recording nothing asks once, after the round closes,
  whether to keep it. The answer is written either way, because a declined
  offer nothing writes down is an offer the next session makes again.
- Two findings: `config-invalid-build-path` (an unread value rides the default
  rather than the opposite path) and `config-build-path-unset`, gated on a
  product record plus evidence of direction work so polish-and-audit projects
  never hear about a setting they do not use.
- init treats a recorded value as a confirmed answer, resolving its conflict
  with Step 1's "do not reopen confirmed fields".
- The setting was undocumented in the README and doctor.md. Both now cover it.

Also records a measured skill-behavior baseline. Three cells fail on unmodified
main (scenarios 9 and 15, `initialized natural build`), verified against a clean
worktree; the suite README now says so, so the next person does not spend the
hour attributing them to their own branch.

Written with AI assistance (Claude Code).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 21:52:17 -04:00
490dcfd678 Fix #476: stop using JSON.stringify/double quotes as shell quoting in four exec sites (#533)
* Fix: use argv exec and single-quote escaping for the four #476 shell-injection sites

JSON.stringify and raw double-quote interpolation were used as shell quoting,
but /bin/sh still expands $(...), backticks, and ${} inside double quotes.

- is-generated.mjs / live.mjs runScript: switch execSync string commands to
  execFileSync argv form, which never invokes a shell. Closes the remote path
  where a source file named `$(...)` executes during the live-mode walk.
- skills.mjs hook command + hook-lib.mjs ignore-value suggestion: values that
  must stay shell strings now use POSIX single-quote escaping instead of
  JSON/double quotes. The doctor's hook-token parser learns the single-quoted
  absolute form so it keeps verifying user-level installs.

Adds regression tests for the single-quoted absolute hook form and the
single-quoted ignore-value suggestion. Verified end to end in a browser through
a real live-mode wrap walk against a hostile-named source file.

Prepared with AI assistance (Cursor) under maintainer instruction.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Test: lock in POSIX single-quoting for a $(...) absolute install path (#476)

Follow-up from security review: prove an install path embedding $(...) is
single-quoted in the written hook manifest, not double-quoted.

Prepared with AI assistance (Cursor) under maintainer instruction.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: quote ignore-command args per platform so Windows cmd.exe keeps spaces (#533)

Greptile flagged that switching quoteCommandArg to POSIX single quotes fixed
$(...) injection on /bin/sh but regressed Windows cmd.exe, where single quotes
are literal, so a --file path containing spaces was split and the ignore scope
was stored malformed.

The suggested command runs on the same machine the hook fired on, so branch on
process.platform (the pattern skills.mjs already uses): single-quote on POSIX
for the #476 fix, and keep the original double-quote escaping on Windows so
that path's behavior is unchanged. Adds a regression test asserting both forms.

Prepared with AI assistance (Cursor) under maintainer instruction.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Test: prove the POSIX hook guard is inert under /bin/sh and Windows keeps double quotes (#533)

Greptile's probe could not reach the generated manifest, leaving the hook
command contract unverified. Convert that into committed proof:

- POSIX: install with a $(touch pwned) absolute path, then actually execute the
  generated guard under /bin/sh from a clean cwd and assert no marker file
  appears and the guard exits 0 (single-quoted substitution stays inert).
- Windows: drive copyProviderHooks as win32 in-process and assert the command
  keeps the double-quoted absolute path (usable when the install path has
  spaces; $(...) is inert on cmd.exe anyway).

Test-only; source quoting is unchanged.

Prepared with AI assistance (Cursor) under maintainer instruction.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 18:43:17 -07:00
Paul BakausandGitHub a37b3f6b02 Fix Windows question browser opening (#510)
AI assistance: Codex reproduced the issue, implemented the fix, and ran the validation described in the pull request.
2026-08-04 14:09:59 -07:00
Paul BakausandClaude Opus 5 ebaf9f1d5b Let a world declare the slop it is personally at risk of
Optional `avoid`, two or three negations of 12 to 160 characters. A world built
from posters is at risk of shouting; one built from instruments is at risk of
dead greys. The global detector cannot know which and the author can, so the
"do not" belongs beside the "do" rather than in a rulebook that applies to
everything equally.

Optional on purpose: 541 entries predate it and none of them are wrong for
lacking it, so nothing needs backfilling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 13:32:08 -07:00
Paul BakausandClaude Opus 5 3b35161000 Flatten the challenger draw so the same worlds stop coming back
A 3-star held two tickets and a 1-star held none. On a pool this size that is
not a nudge, it is the shape of the draw. Measured against the live catalog:
3-star worlds absorbed 57% of the graphic draw from 65 of 163 eligible worlds,
46% of atmosphere from 13 of 43, and 75% of interaction from 15 of 25. The
reviewer's report that the same worlds keep returning is exactly what a rating
multiplier does to a corpus whose thinnest tier holds 25 worlds.

Now a 3-star draws level with a 2-star, and a 1-star draws at half rather than
not at all. Excluding a marginal keep made rating do a job breadth already does
properly: breadth still removes a niche world from the pool entirely, which is
the honest way to say "too narrow to challenge an arbitrary build", while a
1-star records "unexceptional" and is still worth showing sometimes.

Effect on the same catalog: the 3-star share falls to 39% on graphic, 30% on
atmosphere and 60% on interaction. That last one is no longer a weighting
artefact, it is simply what the tier contains, since 15 of its 25 eligible
worlds are rated 3.

Compositions get the same treatment; the two ticket functions had the identical
shape and no reason to disagree. Both tests asserted the old policy directly
and now assert the new one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 11:26:02 -07:00
CypherPoetandGitHub 85f84bf620 🐛 Fix DESIGN.md Layout and Shapes parsing (#481)
* 🐛 Fix DESIGN.md Layout and Shapes parsing

Prepared with AI assistance.

* ♻️ Refine canonical design parser coverage

Prepared with AI assistance.
2026-08-03 14:59:00 -07:00
Abdul WahabandGitHub b33feacbe9 Fix: unescape YAML quote escapes in DESIGN.md frontmatter scalars (#473)
* Fix: unescape YAML quote escapes in DESIGN.md frontmatter scalars (#428)

parseScalar() stripped a double-quoted scalar's outer quotes without
processing the backslash escapes inside, so a font stack that quotes a
multi-word family the CSS way, e.g.

  fontFamily: "\"IBM Plex Sans\", system-ui, sans-serif"

reached allowedFonts as '\"ibm plex sans' and design-system-font flagged
fonts DESIGN.md declares. Also collapses the doubled-quote escape in
single-quoted scalars and keeps a lone quote literal instead of slicing
it to an empty string. Applied to both copies of the parser
(cli/engine/design-system.mjs and skill/scripts/lib/design-parser.mjs).

Co-authored-by: Cursor Agent (AI-assisted change, reviewed and directed
by a maintainer)

* Decode YAML hex and Unicode escapes in double-quoted scalars

Review follow-up: the escape scanner only handled the simple set, so
\xNN, \uNNNN, and \UNNNNNNNN sequences stayed encoded and an escaped
token like "\x23b8422e" never matched #b8422e in CSS. Decode validated
hex escapes in both parser copies; malformed or out-of-range sequences
stay literal. Regression coverage for all three forms.

Co-authored-by: Cursor Agent (AI-assisted change, reviewed and directed
by a maintainer)

* Complete the YAML 1.2 double-quote escape set

Review follow-up: the escape map omitted the escaped space (\ ) and
non-breaking space (\_) forms, so fonts declared with them kept a
literal backslash in allowedFonts and their CSS declarations were
reported as undeclared. Map the full spec 5.7 set (\a \b \v \f \e
\N \L \P included) in both parser copies instead of chasing one escape
at a time. Regression coverage for both named forms.

Co-authored-by: Cursor Agent (AI-assisted change, reviewed and directed
by a maintainer)
2026-08-03 14:53:36 -07:00
Paul BakausandGitHub 68f13a225e Merge pull request #468 from pbakaus/codex/issue-463-wrapped-characteristics
Fix wrapped Key Characteristics parsing
2026-07-31 17:45:14 -07:00
Paul Bakaus a3d7b247aa Cover provider seed markers
Recognize both slash- and dollar-prefixed prescribed seed markers and exercise each variant in coverage tests.

AI assistance: Codex addressed Cursor and Copilot review feedback and reran validation under maintainer authorization.
2026-07-31 17:32:57 -07:00
Paul Bakaus c91047d66a Fix seed design coverage
Treat Components as optional only when DESIGN.md carries the prescribed seed marker, while retaining Colors and Typography checks.

AI assistance: Codex reproduced the issue, implemented the fix, and added regression coverage under maintainer authorization.
2026-07-31 17:21:35 -07:00
Paul Bakaus c5eb38a381 Fix wrapped design characteristics
Join indented Markdown bullet continuations and keep them out of Overview philosophy text.

AI assistance: Codex reproduced the issue, implemented the fix, and added regression coverage under maintainer authorization.
2026-07-31 17:21:29 -07:00
Paul BakausandClaude Opus 5 166e4481e1 Let a concept record its aesthetic axis values
Three of the six axes cannot be read from a world's prose, and widening their
keywords manufactures signal rather than finding it. Depth's probe matched
worlds that said "no cast shadow anywhere" and "without perspective or depth";
motion and colour strategy describe properties the system rules never state, so
they place 28% and 7%.

An optional axes object on the concept records the value instead. Absent means
inferred from the rules as before, so nothing needs backfilling. Validated
against the axes definition when the caller supplies it, because a typo would
read as "unrecorded" and fall back to a probe already known not to work, which
is the quietest way for this to fail.

This is what makes an assigned wave measurable. If a wave draws "drenched" and
"simulated physics" before designing anything, the world it produces has to
carry those values or the assignment is lost the moment it lands, and occupancy
goes back to guessing at prose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 10:48:41 -07:00
Paul Bakaus f274ca2c01 Reject empty collection coverage
AI assistance: Codex validated and addressed the Greptile empty-collection review finding with focused regression coverage.
2026-07-30 09:43:58 -07:00
Paul Bakaus de9d543825 Reject scalar frontmatter coverage
AI assistance: Codex validated and addressed the Greptile scalar-frontmatter review finding with regression coverage.
2026-07-30 09:34:31 -07:00
Paul Bakaus 7a0489bd91 Require populated frontmatter coverage
AI assistance: Codex validated and addressed the Greptile review finding with focused regression coverage.
2026-07-30 09:23:37 -07:00
Paul Bakaus a209eeb0bd Fix DESIGN.md frontmatter coverage
AI assistance: Codex reproduced the issue, implemented the focused fix, and added regression coverage.
2026-07-30 09:06:27 -07:00
Paul BakausandClaude Opus 5 86b91a2003 Replace the invented area axis with grain and platform
The area taxonomy was wrong, and wrong in a way worth recording. It was
derived from Mobbin-style categories in the abstract rather than from what
the skill can be asked for, and measured against the catalog most of it
described problems that were not there: onboarding, settings, empty-state and
search each had zero entries.

Reframed against demand instead. A user asks for a docs site, an onboarding
flow, a landing page, or a data table, and those differ in how much of the
product is in play. Register already says what kind of work it is; grain says
how much: product, flow, view, region.

Named grain rather than scope because scope already means direction-or-surface
on every roll and 'surface' is already a register value, so a scope of
'surface' would have collided with both.

Platform is the second axis: web, ios, android. Unlike grain it is a hard
filter with no fallback, because a composition that leans on hover or a
pointer does not degrade on a phone into something slightly worse, it stops
working, and an empty deal is a visible gap where a broken one is not.

Both fields are optional and absence means eligible everywhere, so nothing
needs backfilling and no existing roll changes.

The third piece is the one a trace turned up. Asking for an onboarding flow
resolves to register=operate, grain=flow, and the catalog holds zero
flow-grain compositions, so the top-up would have dealt three plausible
single-screen compositions with no signal that none matched. The model would
have improvised the flow structure while believing it was handed one, which is
the same silent plausibility the axis exists to remove. Selection now returns
a match alongside the picks, and the rendered seed says when the structure is
borrowed and why.

Measured at the time of writing: 137 of 173 approved compositions are view
grain, product grain is empty, flow grain holds one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 17:36:43 -07:00
Paul BakausandClaude Opus 5 9b2659f9c7 Move the area taxonomy to the dependency-free leaf
The roll API validates its `area` parameter against the surface's list, which
meant importing the taxonomy into a Pages Function. composition-catalog.mjs
reads the filesystem, so importing from there would have pulled node:fs into
the Worker bundle, the same trap WELL_TIERS hit. roll-selection.mjs has no
imports at all and is what both callers already load, so it owns the taxonomy
and composition-catalog re-exports it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 16:47:05 -07:00
Paul BakausandClaude Opus 5 a94331baf0 Add the mode and area axes to the roll
Two gaps, both reported from real use. Worlds were drawn with no mode
awareness at all: selectApprovedChallengers never received the mode, so a
build asking for an app UI could draw six worlds that only make sense on a
landing page. And surface alone is too coarse for compositions, because
"operate" spans onboarding, dashboards, editors and settings, so an
onboarding flow could legitimately be dealt a settings composition.

Worlds gain `allowedModes` on the review record, beside breadth and rating,
because it is a reviewer judgment rather than authored content. Absent means
eligible in every mode, so nothing needs backfilling and no existing roll
changes. Applied per tier and skipped where it would empty one, matching how
minRating and strength already degrade. It is a ceiling the reviewer lowers,
not a category they assign: a world is an identity, and identities transfer
across modes further than compositions do.

Compositions gain an optional `area`, one level below surface, with a
taxonomy per surface (COMPOSITION_AREAS). Area is a preference rather than a
filter: a request reorders the ranking to put area matches first and tops up
from the rest of the surface, because the per-area pools are small and
dealing one on-target composition would be worse than three good ones. A
stable partition of an already deterministic ranking stays deterministic.

`--area` on the CLI requires `--mode`, since areas are scoped to a surface,
and is validated against that surface's list so a wrong-surface area fails
loudly instead of silently matching nothing.

Also validated `breadth`, which selection has honoured for a while with
nothing checking it, so a typo read as "general" and quietly returned a
narrow world to the pool.

Four new tests: worlds excluded from a mode stay out, absent allowedModes
stays eligible everywhere, a tier whose every world excludes the mode falls
back instead of starving, and an area-scoped deal prefers its area, tops up
to three, and reproduces from its key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 16:45:20 -07:00
Paul BakausandClaude Opus 5 a92ba5f2b0 Call them compositions; single-source WELL_TIERS
The data layer has said compositions since the catalog was split, while the
code, the model-facing text, and the UI still said stagings. The rename was
held back by the selection logic existing twice; it exists once now, so this
is one pass instead of two coordinated ones.

Renamed: selectApprovedStagings, selectApprovedStaging, renderStaging, and
the model-facing STAGING GRAMMAR / STAGING CHALLENGERS / FIRST-SURFACE
STAGING INPUTS headings. The block that introduces them now states what
they are for rather than only what they are not: what is the cleverest way
to present, organize, or make interactive the problem in front of you.

Three places keep the old word on purpose:

- The rank salt, `${scope}:${key}:staging`. It is hash input, so renaming
  it would re-deal every roll anyone has ever reproduced by key. Verified:
  240 seeder rolls and 252 API rolls reproduce exactly.
- `Staging/hierarchy:`, the first composition grammar prefix. Inside a
  composition, staging names one of its four aspects, which is a different
  word-sense from staging as the name for the whole artifact. It is also a
  schema constant that 317 catalog entries are validated against.
- The wire fields. The API keeps emitting `stagings` and `staging` beside
  `compositions`, because the wire is the one place a rename cannot be
  coordinated with already-installed skills. Clients prefer the new field
  and fall back through both old ones.

Separately, WELL_TIERS had two definitions after the extraction.
roll-selection.mjs owns it now and concept-catalog.mjs imports it, in that
direction because concept-catalog reads the filesystem and a Pages Function
must not pull node:fs into its bundle. Imported and re-exported rather than
re-exported alone: a bare `export { X } from` does not bind X locally, and
validateConceptCatalog needs it, which cost one round of red tests.

Dropped concept-catalog's synchronous deterministicRank. Nothing imports it
since selection moved out, and leaving a second ranking implementation
around is how the first drift started.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 16:26:46 -07:00
Paul BakausandClaude Opus 5 9b43e9f176 Extract roll selection into one module both callers drive
concept-seed.mjs and the service repo's functions/api/_worldroll-core.js
were two implementations of the same selection, and the API core's header
claimed they matched "exactly". They did not: it had no breadth gate on
either pool, no rating weighting for compositions, and dealt one
composition where the seeder dealt three. Since the catalog never ships
with the skill, every real user rolls through that API, so those gates
reached nobody. Two copies is the defect; this removes the second.

Written as generators rather than plain functions because the callers
cannot agree on a hash. Node has a synchronous one, Workers only have
async crypto.subtle, and renderConceptSeed's local path is deliberately
synchronous so prepared eval sessions and tests can call it without
awaiting. The selection yields batches of strings to hash and resumes
with their digests; runSyncSelection and runAsyncSelection are the only
runtime-specific code, eight lines each. Forcing the seeder async would
have broken the eval harness; forking the logic is what got us here.

No roll changes. Node's crypto.createHash('sha256') and Web Crypto's
SHA-256 return the same bytes, verified, and 240 seeder rolls plus 252
API rolls across both scopes, five modes, three reroll depths and the
rating gate reproduce their pre-refactor output exactly. The 23 existing
concept-seed tests pass unmodified, which is the point: the synchronous
contract survived.

The service repo's core keeps its own copy until this is on main, because
its deploy materializes skill/ from main and would fail to resolve an
import that is not there yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 15:57:20 -07:00
Paul BakausandClaude Fable 5 a68b74e787 Weight staging draws by rating, with catalog validation for composition grades
Stagings now honour approval ratings exactly as world challengers do, a
3-star earning a second ticket and a 1-star marginal keep leaving the
pool, which matters more here because per-surface staging pools are
small enough that an unweighted shuffle repeats a weak staging often.
Each ticket carries its index into the deterministic ranking so the
id-dedupe cannot silently collapse the doubled odds into a no-op, and
an all-marginal pool still deals rather than starving. The composition
catalog validates the new grades: 1-3, approved entries only. Tests
cover the weighting, the dedupe subtlety, and the fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 16:26:38 -07:00
Paul BakausandClaude Fable 5 55094aaa0d Fix false hook-script-missing in doctor when ${CLAUDE_PROJECT_DIR} is unexpanded
The deep staleness pass extracted a hook-script path with a greedy `\S*`
prefix that swallowed the `${CLAUDE_PROJECT_DIR}/` placeholder, then
existsSync'd the literal string. That string never exists, so every project
installed by `impeccable hooks on` got a `hook-script-missing` finding with
text claiming UI edits were going unscanned — the opposite of the truth.

Split extraction from resolution. hookScriptTokenFrom now pulls the path
token (quoted-first, so it handles the #399 guarded `[ ! -f "PATH" ] || node
"PATH"` form and absolute user-level installs) without absorbing shell
syntax. resolveHookScriptPath then applies a per-placeholder policy:

- ${CLAUDE_PROJECT_DIR} expands to the scanned root (the runtime mapping).
- ${CLAUDE_PLUGIN_ROOT} / ${PLUGIN_ROOT} / ${GROK_PLUGIN_ROOT}, $(...) command
  substitution (GitHub's $(git rev-parse)), and any other $VAR are SKIPPED:
  the doctor cannot know those locations and must never assert a negative it
  cannot verify.

The check stays real: a placeholder that expands to a genuinely absent path
still flags. Adds TDD coverage for every command form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:50:10 -07:00
Paul BakausandClaude 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>
2026-07-21 21:50:40 -07:00
Paul BakausandClaude 153b416f2e Move the slop defects back into the craft floor
The detector-blind slop review existed because the AI-tell rules had been
stripped out of SKILL.md and nothing carried them. The floor is a better
home: it loads after concept ideation and immediately before editing UI,
which is the placement that made stripping them necessary in the first
place. Models tread lightly when a ban list is present during ideation;
by the time the floor loads, the direction is already committed.

- Rename build-floor.md to craft-floor.md and restore the absolute bans
  (side-stripes, gradient text, glassmorphism, hero-metric, identical card
  grids, eyebrow-on-every-section, numbered markers, text overflow), the
  codex and gemini defect lists, and the reflexes no scanner catches.
  Rule ids match the ones the ablation catalog already knows.
- Delete lib/slop-review.mjs and both injections. The Stop hook is now
  purely a mechanical pass and stays silent with nothing to report.
- context.mjs replaces AI_SLOP_REVIEW_REQUIRED with the narrower
  MANUAL_DETECTOR_REQUIRED, emitted only when a session has no hook at
  all. A per-edit hook already covers the mechanical gap, and the floor
  covers the judgment one either way.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-21 02:04:36 -07:00
Paul BakausandClaude 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>
2026-07-21 01:45:42 -07:00
Paul BakausandClaude Fable 5 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>
2026-07-20 22:10:10 -07:00