Restore dice assignment, fusion, and the commitment counterweights

The ship40 concept pipeline had reversed the proven a-series mechanisms:
the seed's roll decayed into a shortlist nomination that taste functions
(model ranking, candidate floor, simulated user) then argmaxed into the
safest card; the costume check returned as the Translation veto and
carrier-removal test; and the 07-15 rewrite deleted the calibration,
reflex-font lanes, color strategies, and commit-every-atom language that
had held off the cream-editorial default since the alpha era. Five of six
frozen craft directions converged on the same warm-paper family and both
builders obeyed them.

This lands the repair on top of the in-progress simplification:

- new-work.md: the script assigns the build index again on both scopes;
  catalog challengers are fused (challenger supplies form and grammar,
  product supplies every fact, clarity wins conflicts) and weighed on the
  two proven axes only; attended runs present one fully committed
  direction with re-roll and an optional steer instead of a ranked
  lineup; the color-strategy picker, reflex-face list, saturated-look
  calibration, first-viewport thesis and memory test, commit-every-atom,
  scroll pacing, and prove-don't-claim return; the direction contract
  returns as five lean blocks audited by the separate-agent finish.
- concept-seed.mjs: PROMOTED INDEX becomes ASSIGNED INDEX with
  build-assignment semantics; self re-roll only on named factual grounds.
- craft-floor.md: hook-active sessions act on findings instead of
  re-auditing; the Refuse list is framed as category defaults the brief
  can earn; a closing commitment line keeps a ban list from being the
  last word before code.
- codex.md / shape.md: contract references restored for flow coherence.

Adopts the concurrent session's ceremony cuts, softened challenger
instruction, seed SOURCE IDs and --candidate-count, detector-ownership
fix, and the removal of the hook-side contract audit (the audit now
belongs to the separate reviewer at finish).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-21 10:43:44 -07:00
co-authored by Claude Fable 5
parent a9540b5fe3
commit 36e3c05ca7
11 changed files with 165 additions and 629 deletions
+2 -2
View File
@@ -21,7 +21,7 @@ Show the probes together and ask what should carry forward, what feels false to
Do not begin code until the user approves a direction or explicitly delegates the choice. If they delegate, choose using the task brief, PRODUCT.md, and DESIGN.md, and state the evidence. Approval refines the task concept; it does not modify DESIGN.md.
After approval, summarize the composition and the parts of the comp that must not be literalized. Return to new-work.md and write the direction contract from the approved surface concept before code.
After approval, summarize the composition and the parts of the comp that must not be literalized. Return to new-work.md, record the direction contract from the approved surface concept, then build.
## Inventory implementation fidelity
@@ -35,4 +35,4 @@ Treat the comp as a north star, not something to trace. Do not rasterize core UI
When clean raster ingredients are required and a scoped subagent is available and authorized, use `impeccable_asset_producer`. Give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets with the native image-generation capability in the current thread.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and finishing pass.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+4 -4
View File
@@ -1,6 +1,6 @@
# Craft floor
Load this after the direction is settled, and build without announcing the checklist. A pinned brief or the committed visual world overrides anything here; your own habit does not. <!-- rule:skill-craft-floor -->
Load this after the direction is settled, and build without announcing the checklist. A pinned brief or the committed visual world overrides anything here; your own habit does not. When the design hook is active it already enforces the mechanical checks below as you edit: act on its findings instead of re-auditing each rule. <!-- rule:skill-craft-floor -->
## Verify
@@ -17,7 +17,7 @@ Each of these is a check on the built result, not an intention.
## Refuse
These are the shapes you reach for when you are not deciding. Recognizing one means rewriting the element, not softening it.
These are the category's defaults, not bans: the brief's own words can earn any of them. Reaching for one when the axis is free means you were not deciding; recognizing that means rewriting the element, not softening it.
Page scaffolds:
@@ -36,8 +36,6 @@ Surface habits:
- Monospace as a costume for "technical" rather than for code, data, or measurement. <!-- rule:skill-reflex-mono-as-technical -->
- Light or dark picked by category. Pick it from the use scene: who, where, under what ambient light. <!-- rule:skill-reflex-theme-by-habit -->
When this session has no automatic hook, run the mechanical detector once over the finished web UI: `node {{scripts_path}}/detect.mjs --json <changed targets>`. Never during concept selection, and never twice. <!-- rule:skill-detector-finish-mode -->
<codex>
- Tracking stops at -0.04em. -0.02 to -0.03em usually reads better. <!-- rule:skill-typo-codex-tracking-repeat -->
- Declare elevation once, border or shadow. A 1px border under a wide soft shadow is the ghost card. Card radii stay at 1216px; pills are for small controls. <!-- rule:skill-codex-elevation-radius --> <!-- rule:skill-ban-codex-ghost-card --> <!-- rule:skill-ban-codex-over-round -->
@@ -49,3 +47,5 @@ When this session has no automatic hook, run the mechanical detector once over t
<gemini>
Never animate an image on hover, directly or through its parent. It is not an action target. Give the container the feedback. <!-- rule:skill-interaction-gemini-no-image-hover -->
</gemini>
The floor holds the mechanics; it never picks the direction. With every check green, spend the page on the committed world, and when torn between refined and committed, commit. <!-- rule:skill-floor-not-ceiling -->
+1 -1
View File
@@ -355,7 +355,7 @@ For projects with no visual system to extract yet. Produces a user-chosen visual
PRODUCT.md is the prerequisite. If it is missing, load [init.md](init.md) and complete its product interview first. Do not create a visual identity without durable product context.
If PRODUCT.md exists, load [new-work.md](new-work.md) and resolve visual authority. Seed mode requires a concrete first surface: use the target the user named, or ask what they want to make first. Run **Select one direction** for A, D, or E so the visual world and its first expression are chosen as one pair. Stop after the directional DESIGN.md seed and surface brief; do not implement. A structured simulated user counts as the user and must get the same choice.
If PRODUCT.md exists, load [new-work.md](new-work.md) and resolve visual authority. Seed mode requires a concrete first surface: use the target the user named, or ask what they want to make first. Run **Select one direction** for A, D, or E so the visual world and its first expression are chosen together. Stop after the directional DESIGN.md seed and surface brief; do not implement. A structured simulated user counts as the user and must get the same choice.
If new-work already completed the workshop in this session, use its chosen direction directly. Do not ask again.
+54 -136
View File
@@ -1,179 +1,97 @@
# New visual work
This flow owns the durable visual world when authority is absent, expanding, or explicitly replaced, plus only as much task-level shaping as the requested scope needs. PRODUCT.md owns product truth, DESIGN.md confirmed visual truth, and `.impeccable/surfaces/` durable task strategy. Complete [init.md](init.md) first when PRODUCT.md is missing. Missing DESIGN.md does not route back to init.
Use this flow when making a new surface or replacing a visual identity. PRODUCT.md owns product truth. DESIGN.md owns durable visual decisions. A surface brief keeps strategy that belongs only to one route or artifact. Complete [init.md](init.md) first when PRODUCT.md is missing; a missing DESIGN.md does not route back to init.
## Vocabulary
## 1. Decide what is already true
Terms this file uses throughout, defined once:
Read DESIGN.md, representative code, tokens, components, and assets.
- **World:** the durable visual identity: palette, materials, type voice, ornament logic, and component character that outlive any single surface.
- **Coupled pair:** one world joined to one concrete first-surface expression, selected together as a single decision, never as two tournaments.
- **Staging:** an identity-free structural idea for a surface (hierarchy, sequence, interaction). The seed may append several; they bring no palette, typeface, or material.
- **Candidate floor:** the five veto tests in section 4. A candidate passes all five or is discarded and replaced.
- **Direction contract:** six promise blocks written into the artifact's opening comment before code, audited against the render by the hooks.
- **Surface brief:** durable surface strategy persisted to `.impeccable/surfaces/` via `surface-brief.mjs`.
- **Attended run:** a user is present to answer questions. Unattended fallbacks apply only when no one can answer.
- **Redesign:** preserve product truth, content, function, constraints, and explicit brand commitments; replace the old visual world rather than polishing it. The old look is evidence of what the subject is, not authority over what it becomes.
- **Established world:** inherit it. A missing DESIGN.md does not erase a coherent identity already present in code; document that identity instead of inventing a replacement.
- **Incomplete brand:** preserve confirmed assets and recognizable traits, then help the user expand the system for this new surface.
- **No visual authority:** create a new world with the user.
## 1. Name the intent
A section, component, feature, or state inside an established surface inherits that surface. Do not turn a local addition into a new identity exercise.
- **Greenfield:** no coherent visual implementation.
- **Local extension:** a section, feature, component, or state inside an established surface and world.
- **New surface:** a whole page, route, screen, flow, or standalone experience inside an established world.
- **Expression expansion:** an established brand entering an unresolved whole-surface family or app boundary.
- **Redesign/rebrand:** replace the world while preserving unchanged product truth, content, function, native affordances, constraints, and brand commitments.
- **Refinement:** leave this flow for the scoped command; preserve the world and scope.
## 2. Ask what will change the work
Classify before ideation: work that must join an existing page is a local extension, and a novel layout it would benefit from does not promote it to a new surface. A plain "redesign this page/site" authorizes replacement. "Redesign this within the current brand/system" means extension or refinement. Ask once only when the wording is genuinely ambiguous.
Ask one round of two or three related questions through the structured question tool when available. Skip settled facts; a precise request may need only a compact confirmation.
## 2. Resolve visual authority
- **Persuade:** clarify who must act, what they should believe, and which real proof, content, or assets can earn that belief.
- **Operate:** clarify the task, information, important states, frequency, and constraints.
- **Read:** clarify the reader's question, source material, structure, and wayfinding.
- **Experience:** clarify what leads, how exploration unfolds, and which interaction or transition matters.
Read DESIGN.md and representative code, tokens, components, and assets. Choose one path:
Across modes, ask what success looks like, what must remain untouched, and what would make a polished result feel wrong. Do not ask for CSS values or canned aesthetic lanes.
- **A. Explicit redesign.** The old DESIGN.md and implementation are not authority. Keep only unrevoked product facts, content, function, native expectations, constraints, and brand commitments. Establish a replacement world.
- **B. DESIGN.md covers this kind of surface.** Use its invariants and normative tokens; skip world-building and discover the surface.
- **C. Coherent implementation, no DESIGN.md.** Code, assets, tokens, type, and component behavior are incumbent authority. Run [document.md](document.md) in scan mode, confirm the extracted invariants, then write DESIGN.md. Do not offer replacement worlds unless the user asked for a redesign.
- **D. Brand exists, whole-surface family unresolved.** Preserve logo, color/type assets, voice, recognizable component/motion traits, and constraints. Ask what must carry and where expression may expand; offer two or three compatible ranges, not replacement identities, and merge the choice into DESIGN.md (child-app DESIGN.md when the range is local).
- **E. No confirmed visual authority.** Establish a world. Scaffolds, framework defaults, and stray utilities are not identity.
## 3. Choose the right amount of invention
A local extension stays on B or C: its surrounding surface is authority even when DESIGN.md is incomplete.
### Extend an existing surface
## 3. Discover the requested surface
Inherit its world and composition. Resolve only the new purpose, content, hierarchy, states, interaction, and how the addition joins the surrounding experience. Do not run a concept tournament or change DESIGN.md unless the user approves a durable system change.
Name this surface's audience, job, visitor mode, real content, primary action/task, evidence, constraints, and memorable moment. PRODUCT.md supplies truth and DESIGN.md the world; neither decides narrative or composition.
### Create a whole surface inside an established world
In an attended run, ask one round of at most three material questions without repeating durable facts: CTA hierarchy, proof sequence, content gaps, and interaction outcomes belong here, not in PRODUCT.md. For a fully specified narrow request, state the interpretation and invite correction. When `shape` has already completed its discovery interview, reuse those confirmed answers and do not ask again.
Keep the visual system fixed. Derive five to seven materially different structures from the content, task, and user behavior, ordered by resonance. For a genuinely open whole page, screen, or flow, run:
## 4. The candidate floor
`node {{scripts_path}}/concept-seed.mjs --scope surface --mode <mode>`
When generating choices, veto rather than rationalize. Before a candidate reaches the user, discard and replace it if any floor fails:
The script assigns which structure gets built: your top-ranked structure is what every run would ship, and a single ranking is deterministic, so the dice come from outside. Dress its staging challengers in the committed identity and weigh them against your list before building. Never run the script for a local extension or a precisely specified narrow request; shape those directly.
- **Truth:** every product relationship it visualizes exists; resemblance is not evidence.
- **Translation:** remove the literal source prop and label. A coherent product-native experience must remain, still carrying the source's aesthetic and compositional laws. Function without that character is safe flattening; character without product structure is costume.
- **Signature:** one authored move makes the experience unmistakable and materially shapes implementation. Name what the visitor experiences and what becomes possible; routine state or polish does not pass.
- **Survival:** its identity and signature remain compelling on the primary device within the real asset and tool budget.
- **Fit:** its risk is an honest tradeoff, not a probable violation of the brief or audience.
### Create or replace the visual world
Passing some floors never rescues a failed one. The selected signature becomes the direction contract's `BAR-RAISER`.
1. Name the product's unique mechanism in one sentence, the audience's real scene, its cultural home, and what this first surface must prove. Note the page this category always ships and its predictable opposite; treat both structures as the rut, not the range.
2. From that cultural world, list seven concrete visual systems, artifacts, places, or rituals the audience knows by heart, each with one line on why it resonates and can carry the mechanism, ordered by resonance. What would this thing look like as a physical object; what did its world look like before the web? Near-duplicates count once, and a list confined to one material or medium family is not divergent.
3. Turn that material into complete directions: each joins a reusable visual world to a concrete first-surface experience.
4. Run `node {{scripts_path}}/concept-seed.mjs --scope direction --mode <mode>` and follow what it prints. The script assigns which direction gets built and deals catalog challengers. Fuse each challenger before judging it: the challenger supplies the form and its system grammar, the product supplies every fact, and clarity wins conflicts. Weigh fused challengers against the assigned direction on exactly two axes, audience identification and product clarity; losing to strong grounded material is a valid outcome, and beating a thin or tool-monoculture list is the point. <!-- rule:skill-concept-procedure -->
5. Present one direction, fully committed: its world, first viewport, visitor path, signature interaction, cross-surface reach, and honest risk. Offer re-roll with an optional one-line steer instead of a ranked menu; a lineup invites the safest card. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. <!-- rule:skill-assigned-plus-reroll -->
## 5. Shape or select the direction
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states.
Do not select a new world and its first surface concept in separate tournaments. That creates a safe global choice followed by a more interesting local choice whose "lineage" exists only in prose. Pick the one lane that matches the intent and authority path.
Every direction the roll can land on must already be viable: every relationship and claim it visualizes true, a real palette and component family, a distinctive composition with one product-specific experience, workable at full-surface scale within the available assets, tools, and performance budget. A candidate that fails on truth is replaced before the roll, never rescued by it.
### Local extension (B or C): inherit, do not reseed
For **Persuade**, the opening must make the offer intelligible and desirable, expose a clear action, and demonstrate something only this product can prove. For **Operate**, expression may never obscure the task, state, or familiar affordance. For **Read**, comprehension and wayfinding remain intact. For **Experience**, the work itself leads from the first viewport.
Inherit both the world and the surrounding surface's direction. Resolve only the decisions the addition actually introduces: purpose, content, hierarchy, state or interaction, and how it joins the existing sequence; in an attended run use short, related question rounds while those are open. Do not run `concept-seed.mjs`, generate competing surface metaphors, offer alternate worlds, write a direction contract, or change DESIGN.md without user approval. The result may still have an authored, surprising layout; its novelty must come from the material and the established grammar, not a new identity thesis. If the request reveals a genuine gap in the brand system, name the gap and ask before treating it as path D; never silently turn a section into an expression-expansion exercise.
## 4. Commit the world
### New surface inside stable authority (B or C)
Pick a color strategy before picking colors: Restrained (neutrals plus one accent; the default when the visitor came to operate or read), Committed (one saturated color carries 30-60% of the surface), Full palette (3-4 named roles), or Drenched (the surface IS the color). Persuade and Experience surfaces have permission for the bolder strategies; take them when the brief allows. Color commits at page scale: fields that own whole regions, not accents scattered over a neutral ground. Dark or light is never a default: write one sentence of physical scene (who uses this, where, under what light) and let it force the answer. <!-- rule:skill-color-strategy -->
The world supplies the vocabulary; the task concept supplies the sentence.
Choose faces like objects from the subject's world, in the mode's register. Operate and Read surfaces are well served by system stacks and workhorse UI faces; Persuade and Experience surfaces want faces with a point of view, and these training-data defaults mean you stopped looking: Fraunces, Playfair Display, Cormorant, Lora, Crimson, Newsreader, Syne, Space Grotesk, Space Mono, IBM Plex, Inter-as-display, DM Sans, DM Serif, Outfit, Plus Jakarta Sans, Instrument Sans. <!-- rule:skill-typo-reflex-faces -->
1. Derive five to seven structural candidates from the content, mechanism, audience, and confirmed authority. Translate its relationships and behavior, not just its styling.
2. Run `node {{scripts_path}}/concept-seed.mjs --scope surface --mode <mode>` (the surface's mode: persuade, operate, read, or experience) only when a whole page, route, screen, flow, or standalone experience calls for high-concept exploration. Otherwise shape the strongest grounded structure directly. Never run it for a local extension. Translate every supplied STAGING CHALLENGER into this product's content and behavior, dress it in the committed identity, and compare it with the grounded structures; the point is to challenge the habitual layout, not append a flourish.
3. Name the habitual arrangement and predictable contrarian response. Apply the candidate floor, then judge survivors skin-blind: topology, sequence, or interaction must remain different after names and styling disappear.
4. When materially different whole-surface choices would help, present two or three neutral options with thesis, sequence, focal moment, signature, implementation consequence, and concrete inherited world rules, following **Present, visualize, re-roll** below. Let the user select or revise before code.
Calibration: AI-generated interfaces cluster around a few looks regardless of subject: warm cream ground, high-contrast serif display, and a terracotta or signal-red accent; near-black with one neon accent and glowing edges; broadsheet-editorial hairlines, italic display serif, and small tracked mono labels. All are legitimate when the brief calls for them; the brief always wins. Where the brief leaves the aesthetic free, landing in one of them means the self-check failed: if someone could guess your aesthetic from the category alone, or from category-plus-avoidance, rework until neither answer is obvious. <!-- rule:skill-calibration-saturated-looks -->
Unattended: use the promoted candidate when a roll ran, otherwise the strongest grounded structure; it must survive both tests.
## 5. Record the decision
### New or replacement world (A, D, or E): choose a coupled pair
Before code, state the chosen direction as a contract in the artifact's opening comment, five short blocks, 150 words at most. THESIS: the one idea this surface owns and the category-default arrangement it refuses. OWN-WORLD: the palette and component language, specific enough to be recognizable with all content removed. STORY: what the visitor understands, believes, and does. FIRST VIEWPORT: the exact composition, what is where and at what scale. FORM: the chosen form, its position on your ordered list, and the seed key the script printed. If a block reads like a mood, the direction is not decided yet; the finishing review audits the render against this contract. <!-- rule:skill-decide-then-build -->
1. **Ground.** Derive the product mechanism, user scene, audience's cultural home, and what this surface uniquely proves. Name the category default and its predictable contrarian response; neither may enter the shortlist unchanged.
2. **Derive pairs.** Generate five to seven grounded coupled pairs and order them by product fit. Each joins a durable system to a concrete first-surface structure, native behavior, non-routine signature move, and implementation consequence. Different names or materials on the same experience are one candidate.
3. **Break the ranking rut once.** Run `node {{scripts_path}}/concept-seed.mjs --scope direction --mode <mode>`, where the mode is the first surface's: persuade, operate, read, or experience. The seed names a PROMOTED INDEX; elevate the pair at that position of your own ranked list into the serious shortlist and judge it as a peer of your top picks. For each world challenger, translate its system laws (material behavior, type/composition, topology, state, and motion) into product-native equivalents. Remove the literal carrier, not the character that made the source worth entering. Compare every translated pair with the grounded list on audience identification, product clarity, system leverage, and use of the medium. Translate each supplied FIRST-SURFACE STAGING into one concrete product structure and use the set to challenge your own habitual composition; when one survives, bind it to the world as one coupled decision.
4. **Test at full strength.** Apply the candidate floor. Strip names, styling, and source carrier; survivors must still differ in structure, sequence, or interaction. Their world must also govern the whole product: the navigation, a dense surface, and a quiet surface, with the last two dissimilar.
5. **Offer coupled choices.** Present two or three equally viable pairs without recommendation cues, following **Present, visualize, re-roll** below. For each, show the world rules, first-surface expression, signature move and implementation consequence, cross-surface breadth, and risk. If fewer than two clear every veto, derive replacements rather than padding the choice. Ask what is closest, should combine, or feels wrong; rejection is allowed.
6. **Resolve once.** The user selects or revises the pair. Extract the durable rules into DESIGN.md and the task-specific strategy into the surface brief; do not reopen either half independently.
When a new or replacement world is chosen, write DESIGN.md at the appropriate project or app boundary using [document.md](document.md). Record only durable system rules; exact tokens may remain provisional until the first build establishes them. An ordinary extension does not rewrite DESIGN.md.
Unattended: use the assigned grounded pair only if it survives product fit, coupling, and breadth; mark assumptions. This is fallback, not user choice.
### Present, visualize, re-roll
These rules govern every candidate presentation above, in either lane.
When the harness can generate images, visualizing the finalists is required, not decoration: render each presented candidate as a world board (palette, materials, type voice, component character) plus a first-surface mock at a realistic viewport; a surface-scope candidate inside a committed world gets the mock alone. Render designed artifacts: invented product names, real English interface copy, no instruction text transcribed onto the image, no dashboard or game chrome the concept itself did not earn. The images are selection aids only; the direction contract and DESIGN.md stay the sole authority, and no image persists as a project file.
Always offer **re-roll** beside the candidates, with an optional one-line steer for what is missing. Re-roll eliminates every candidate presented so far, grounded and challenger alike; none may return reworded. Derive genuinely new grounded candidates from unexplored angles of the same grounding, honor the steer, rerun the seed with `--from <key> --reroll <round>` so it draws challengers it has not yet shown, apply the candidate floor, rerank, and present again under these same rules. After two consecutive re-rolls, ask what quality is missing before rolling a third time; silent re-rolls converge on guessing.
An unattended run has no one to re-roll or eye-test. When image generation is available there, render one mock of the chosen direction and correct material drift between mock and build before finishing.
For a substantial high-fidelity surface with native image generation, load [codex.md](codex.md) after selection; probes stay inside the selected direction. For `shape`, stop after selection and continue in [shape.md](shape.md).
## 6. Expand, then contract, the chosen direction
If a competent default could satisfy the concept, sharpen its focal moment until one product-specific move changes implementation and raises the experience above competent convention.
Before compressing the decision, expand one coherent studio plan in working context:
- **Spatial:** the whole-surface composition, navigation, reveals, and rhythm, not only section order.
- **Motion:** the coordinated motion story and its major moments, not a list of repeated effects.
- **Interaction:** the flagship experience, its feedback, and what the visitor can understand or do through it.
- **Narrative:** how copy, evidence, and action build one persuasive or usable arc.
- **System:** how the world governs components, dense and quiet surfaces, states, and responsive behavior.
Make concrete decisions in every relevant discipline and make them cause one another. Do not use the list to decorate an unchanged page skeleton.
Before code, write a direction contract of at most 150 words in the artifact's opening HTML or framework comment; the first 200 characters must name `DIRECTION CONTRACT`. The artifact comment is the contract's only home: hooks audit the render against it, and the surface brief references it rather than duplicating it. <!-- rule:skill-decide-then-build -->
- `THESIS`: the product-specific idea and the category-default arrangement it refuses;
- `OWN-WORLD`: the confirmed DESIGN.md invariants, tokens, and materials it uses;
- `STORY`: what the visitor understands, believes, and does;
- `FIRST VIEWPORT`: exact composition, hierarchy, action, and where the concept exceeds competent convention;
- `BAR-RAISER`: the selected signature move: what the visitor experiences, what it makes possible, and the implementation consequence that keeps the surface from collapsing to a competent default;
- `FORM`: chosen structure or behavior, signature, implementation consequence, and seed key.
The contract is task-scoped, inspectable, and subordinate to the user's choice. A local extension skips it unless the user explicitly wants the addition to become a distinct authored moment; the shaped decisions are its implementation plan instead.
## 7. Persist the surface brief
Once the primary target or route is known, persist durable surface-level product/UX strategy separately from PRODUCT.md and DESIGN.md. `<primary-target>` is a concrete repo-relative source file path or route URL, never a natural-language description; the script derives the clone-stable slug mechanically, so never hand-write one. Prefer a clone-stable source file; map routes and alternate entries as related targets. For a local extension, update the parent surface's record only when the work establishes durable product strategy; do not create a component-level brief by reflex. Read any record first:
If the work establishes durable strategy for a route or artifact, read its existing surface brief, then update it:
`node {{scripts_path}}/surface-brief.mjs read <primary-target>`
Exit 0: preserve still-valid decisions and change only what the user changed; for redesign, retain valid product strategy, content, function, and open decisions while replacing the visual direction. Exit 2: no brief exists. Write with `node {{scripts_path}}/surface-brief.mjs write <primary-target> <body-file> [related-target ...]`, which persists `.impeccable/surfaces/<target-slug>.md` as durable context for later sessions; leave version control to the user's normal flow. Exclude global truth, exact tokens, transient notes, and work logs. The concise body:
`node {{scripts_path}}/surface-brief.mjs write <primary-target> <body-file> [related-target ...]`
```markdown
# Surface brief: [name]
Keep the brief small: scope and visitor mode; audience, job, action/task, proof/content, and constraints; chosen direction and memorable moment; unresolved decisions. Do not copy global product truth or DESIGN.md tokens into it.
## Scope
[Primary/related route or artifact, visitor mode, and what this surface owns.]
When native image generation is available and words are not enough to judge the choice, use [codex.md](codex.md) to mock the direction before committing. The mock is a selection aid, not authority.
## Product strategy
[Surface-specific audience and job, desired outcome, primary/secondary action or task, content and proof sequence, factual constraints, and explicitly open decisions.]
For `shape`, return the selected direction to [shape.md](shape.md) and stop before persistence or implementation.
## Selected direction
[Reference to the applicable DESIGN.md world or expression range, selected surface concept, focal moment, narrative/interaction sequence, implementation consequence, and a pointer to the artifact carrying the direction contract.]
## 6. Build with full commitment
## Open decisions
[Only unresolved items that later work must not silently invent. Omit when empty.]
```
Build the assigned direction, not a safer interpretation of it. The form supplies structure, reading order, component conventions, and native motion; the product supplies every fact. Commit every atom: nav, buttons, inputs, and links are rebuilt in the form's vocabulary, and a stock component inside a committed form is a lapse. Land the first build fully committed; committing is the hard part, and the passes that follow exist to make the committed thing clear and effective, never to dilute it. In unattended work, the safe rendition is the known risk. <!-- rule:skill-commit-every-atom -->
## 8. Write or update DESIGN.md
- **The first viewport is a thesis, not a header.** Demonstrate the mechanism immediately, at the scale the form has in life; do not trap the concept inside a standard hero or card shell. The memory test: if someone left after one viewport, what would they describe an hour later? If the honest answer is a mood, the concept has not committed yet.
- **Prove, don't claim.** Show the subject doing its job: the interface at work, the mechanism dramatized, specifics a competitor could not copy-paste. Sections that restate a claim in different words add length, not substance.
- **Pace the scroll like a studio.** Vary density, scale, image, motion, and quiet inside one grammar; a dense passage earns a quiet one, and the page ends anchored by a real close. One spacing rhythm throughout, with more space above a heading than below it.
- **Use real, verified imagery when the brief implies it.** Search for the subject's physical object rather than the category; one decisive photo beats five mediocre ones. Verify stock URLs resolve.
- **Author motion as material.** The form has native motion, what it does in life between states; give the page that motion once, orchestrated, rather than scattered hover effects. Bound expensive effects and keep content visible by default.
For A or E, write or replace DESIGN.md at the resolved project/app boundary; for D, merge only the approved expansion range. The format, in brief: YAML frontmatter carrying machine-readable tokens (colors, typography, rounded, spacing, components), then the canonical markdown sections in fixed order, with tokens normative and prose as application context. [document.md](document.md) carries the full schema and examples; never fetch a remote spec. Record the chosen durable rules and add:
Preserve semantics, accessibility, performance, responsiveness, project conventions, and working behavior.
`<!-- STATUS: DIRECTIONAL SEED; exact tokens remain provisional until the first implementation pass. -->`
## 7. Inspect and finish
Do not fabricate YAML tokens; add exact values only after the user, assets, or implementation establishes them. The selected pair authorizes its world and first expression together without another confirmation. A local extension does not change DESIGN.md unless the user approves a durable system addition.
Inspect desktop and mobile, critique the render against the user's request, the direction contract, and DESIGN.md, fix material gaps, and re-inspect. After a first implementation of a new world, update DESIGN.md with the exact tokens and behaviors that survived the build.
## 9. Plan and build
Plan from the selected direction or local shape and real content, never a category skeleton. In redesign, remove inherited visual tokens. Load only needed specialist references; focal interaction or authored animation reads [animate.md](animate.md), even without the `animate` command.
Build the strongest coherent direction once. Its grammar governs navigation, actions, controls, content, and transitions without disguising affordances. Give the focal form the scale that gives it force; do not trap it inside a conventional opening panel.
- **Make the opening a thesis.** Demonstrate the mechanism immediately; leave an idea, interaction, or evidence, not merely mood.
- **Commit before correcting.** Land the hard move at full strength before refining it. In unattended work, safety is the known risk.
- **Commit at page scale.** Let color, material, image, or type own a region when the world calls for it. Scattered signature decoration is not commitment.
- **Pace the whole surface.** Vary density, scale, image, motion, and quiet inside one grammar. Cut repeated claims; prove the mechanism with real artifact, interaction, data, transformation, or content.
- **Author motion as material.** Motion expresses the world and task. Premium moments go beyond repeated transform/opacity through earned focus, depth, masks, continuity, light, or material change. Bound expensive effects, test in-browser, and keep content visible by default.
Preserve semantics, affordances, accessibility, performance, responsiveness, and project conventions. Operate/Read express through topology, hierarchy, density, rhythm, and state; Persuade/Experience may earn drama.
## 10. Solidify the visual record
After first implementation of a new/replacement world or approved expansion, refresh DESIGN.md from the build: replace provisional direction with the exact type, color roles, tokens, spacing/radii, components, states, and motion that survived; add normative YAML tokens only for values the implementation actually uses; remove the directional-seed status once record and implementation agree; preserve broader world invariants and expression ranges. Do not promote the task's story, opening composition, or one-off motif into a global rule unless it is intentionally reusable. Ordinary extension does not rewrite DESIGN.md; only approved durable changes do.
## 11. Finish like a studio
Inspect desktop and mobile; critique against the brief, DESIGN.md, and the applicable shape, concept, or contract; patch material defects; recheck skin-blind. Follow the quality guidance supplied by `context.mjs` and hooks. Context requests a manual scan only when no automatic detector is active; never add another detector pass. Fix real gaps and classify false positives until none remain. <!-- rule:skill-finish-like-studio -->
When the harness can run a separate agent, this finishing review belongs there, not in the build thread: hand the reviewer the artifact path, its direction contract, and the detector command, and have it return a short list of material fixes; apply them and finish. A fresh reader catches what the builder's own eyes slide past. <!-- rule:skill-finish-separate-reviewer -->
When the harness can run a separate agent, this review belongs there, not in the build thread: give it the original request, confirmed answers, the artifact path, its direction contract, DESIGN.md, and existing hook findings. Ask for a short list of material fixes, promise by promise against the contract, apply them, and finish. Do not run a second detector. <!-- rule:skill-finish-separate-reviewer -->
-2
View File
@@ -2,8 +2,6 @@
Discover what should be made and how it should work, then return a confirmed design brief without code.
**Product gate:** when context reports that PRODUCT.md is missing, load and complete [init.md](init.md) before Phase 1. Do not substitute shape questions for the product interview. Once PRODUCT.md exists, return here; product context does not replace task discovery.
## Phase 1: Discovery interview
Do not write code or choose visual direction yet.
+75 -67
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env node
/**
* External concept seed: the dice half of new-work's coupled-direction and
* External concept seed: the dice half of new-work's complete-direction and
* established-world surface procedures.
*
* The model derives a grounded shortlist of candidate FORMS from the
* audience's world and the subject's cultural home (see
* Before this script runs, the model retrieves cultural material and derives
* a grounded shortlist of complete candidate directions from it (see
* reference/new-work.md). Left alone, it then always builds its #1 —
* and a single model's resonance ranking is deterministic, so every run
* in a category ships the same one or two concepts. Measured: 30/35
@@ -13,19 +13,23 @@
*
* This script rolls them from outside, the same trick that made the
* palette seed work:
* - PROMOTED INDEX: which entry of the model's own resonance-ordered
* shortlist must be taken seriously beside its favorites. The dice never
* choose an ungrounded ingredient; they only refuse the argmax rut.
* - ASSIGNED INDEX: which entry of the model's own resonance-ordered
* shortlist gets built. The assignment is the dice: it never chooses an
* ungrounded ingredient, it only refuses the argmax rut. Attended runs
* present the assigned direction and offer re-roll instead of a ranked
* lineup, because a lineup hands selection back to a taste function
* (model or user) and taste functions pick the safest card.
* - CHALLENGERS (6): outside forms from concept-ingredients.json, two from
* each challenger tier (graphic system, instrument language, atmosphere
* world), weighed
* against the derived candidates on audience identification, product
* clarity, system leverage, and use of the medium. They win only when they beat the
* grounded list; measured behavior is that they lose to strong cultural
* material and win over thin categories, which is the intended shape.
* world), fused with the product first (challenger supplies form and
* system grammar, product supplies every fact, clarity wins conflicts),
* then weighed against the derived candidates on audience identification
* and product clarity. They win only when they beat the grounded list;
* measured behavior is that they lose to strong cultural material and
* win over thin categories, which is the intended shape.
* - RE-ROLL (--reroll <n>): round n of the same base key. The script
* recomputes what rounds 0..n-1 drew, excludes all of it, and rolls a
* fresh promoted index, challengers, and staging. One base key therefore
* fresh assigned index, challengers, and staging. One base key therefore
* reproduces the entire chain of rounds.
* - RATINGS: the reviewer's approval ratings weight the challenger draw
* (3-star doubles the odds, 1-star sits out); the approved pool itself
@@ -34,6 +38,7 @@
* Usage:
* node scripts/concept-seed.mjs --scope direction --mode persuade
* node scripts/concept-seed.mjs --scope surface --mode operate --from <key>
* node scripts/concept-seed.mjs --scope direction --candidate-count 6
* node scripts/concept-seed.mjs --scope direction --mode persuade --from <key> --reroll 1
* node scripts/concept-seed.mjs --chosen <challenger-id> --from <key> --scope direction
*
@@ -43,7 +48,7 @@
*
* Challenger data resolves in order: a local catalog directory (the private
* service repo, evals, and tests set IMPECCABLE_CATALOG_DIR), then the roll
* API at impeccable.style, then a degraded promotion-only seed when both are
* API at impeccable.style, then a degraded assignment-only seed when both are
* unavailable. --chosen sends the anonymous choice ping for API-dealt rolls;
* DO_NOT_TRACK or IMPECCABLE_NO_TELEMETRY disables it.
*
@@ -70,7 +75,7 @@ const here = dirname(fileURLToPath(import.meta.url));
// Data resolution order: a local catalog (the private service repo, evals, and
// tests point IMPECCABLE_CATALOG_DIR at one), then the roll API, then a
// degraded promotion-only seed. The full catalog does not ship with the skill.
// degraded assignment-only seed. The full catalog does not ship with the skill.
const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here;
const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, '');
const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000);
@@ -157,6 +162,7 @@ export async function pingChosen({ chosenId, key, scope, mode }) {
export function renderChallenger(concept, index) {
const system = concept.system.map(rule => ` - ${rule}`).join('\n');
return ` ${index + 1}. ${concept.form}
SOURCE ID: ${concept.id}
CREATIVE SPARK: ${concept.spark}
SYSTEM GRAMMAR:
${system}
@@ -166,6 +172,7 @@ ${system}
export function renderStaging(composition, index = null) {
const grammar = composition.grammar.map(rule => ` - ${rule}`).join('\n');
return ` ${index == null ? '' : `${index + 1}. `}${composition.form}
SOURCE ID: ${composition.id}
SPARK: ${composition.spark}
STAGING GRAMMAR:
${grammar}
@@ -309,6 +316,7 @@ export function renderConceptSeed({
key = process.env.IMPECCABLE_CONCEPT_SEED || crypto.randomBytes(4).toString('hex'),
reroll = 0,
mode = null,
candidateCount = 7,
catalogDir = CATALOG_DIR,
_resolvedData = undefined,
} = {}) {
@@ -321,15 +329,18 @@ export function renderConceptSeed({
if (mode !== null && !SEED_MODES.has(mode)) {
throw new Error('concept-seed: --mode must be persuade, operate, read, or experience');
}
if (!Number.isInteger(candidateCount) || candidateCount < 5 || candidateCount > 7) {
throw new Error('concept-seed: --candidate-count must be an integer from 5 to 7');
}
const unit = (salt) => {
const h = crypto.createHash('sha256').update(`${scope}:${salt}:${key}`).digest();
return h.readUInt32BE(0) / 0xffffffff;
};
const indexSalt = reroll === 0 ? 'index' : `index:reroll-${reroll}`;
const buildIndex = 3 + Math.floor(unit(indexSalt) * 5); // 3..7
const buildIndex = 3 + Math.floor(unit(indexSalt) * (candidateCount - 2)); // 3..candidateCount
// Local catalog first (private repo, evals, tests), then the roll API,
// then a degraded promotion-only seed. The promoted index is pure local
// then a degraded assignment-only seed. The assigned index is pure local
// math, so even a fully offline run keeps the anti-argmax mechanism.
let data = _resolvedData ?? null;
if (_resolvedData === undefined) {
@@ -357,6 +368,7 @@ export function renderConceptSeed({
key,
reroll,
mode,
candidateCount,
catalogDir,
_resolvedData: roll ? {
source: 'api',
@@ -371,68 +383,62 @@ export function renderConceptSeed({
}
const promotedInstruction = scope === 'direction'
? `After ordering the grounded coupled directions by product fit, promote
candidate ${buildIndex} into the serious shortlist. Each candidate must join
a durable visual system to a concrete expression for the requested first
surface; select or revise that pair as one decision. It must survive the
current task plus navigation, quiet and dense content, interaction and state,
and a substantially different future surface.`
? `After ordering the grounded directions by resonance, build candidate
${buildIndex}. The assignment is the roll, not a suggestion: your top-ranked
direction is what every run would ship, so the script decides which grounded
direction gets built. Each direction joins a durable visual system to a
concrete expression for the requested first surface, decided as one. It must
survive the current task plus navigation, quiet and dense content,
interaction and state, and a substantially different future surface. In an
attended run, present the assigned direction fully committed and offer
re-roll; never present a ranked lineup to choose from. Re-roll yourself only
on named factual grounds, when the assignment cannot carry the product's
truth or task; taste is never grounds.`
: `After ordering the task's grounded structural candidates by resonance,
promote candidate ${buildIndex} into the serious shortlist. In an attended
run, present it beside the strongest materially different candidates and
let the user select or revise the surface concept. In a truly unattended
run, use it when it survives audience identification, product clarity,
system leverage, and use of the medium.`;
build candidate ${buildIndex}. The assignment is the roll, not a suggestion.
In an attended run, present the assigned structure and offer re-roll; never
present a ranked lineup to choose from. Re-roll yourself only when the
assignment fails audience identification or product clarity on named
factual grounds.`;
const challengerInstruction = scope === 'direction'
? `Translate each challenger's organizing logic into reusable identity grammar
and a strong first-surface structure before judging it. Noticeable form is
allowed when the product stays clear. Compare audience identification,
product clarity, system leverage, and use of the medium.`
: `A challenger wins only when it beats the grounded list on audience
identification, product clarity, system leverage, and use of the medium. It may change task topology or
? `Fuse each challenger before judging it: the challenger supplies the form
and its system grammar, the product supplies every fact, and clarity wins
conflicts. Weigh the fused result against the assigned direction on exactly
two axes, audience identification and product clarity. Losing to strong
grounded material is a valid outcome; beating a thin or tool-monoculture
list is the point. A fused challenger that wins both axes becomes the build.`
: `A challenger wins only when its fused result beats the grounded list on
audience identification and product clarity. It may change task topology or
interaction, but never the committed visual identity.`;
const authorityInstruction = scope === 'direction'
? `PRODUCT.md and explicit incumbent brand commitments constrain every coupled
direction. The seed never chooses exact colors, fonts, tokens, or a user
preference, and it never permits the world and first surface to be selected
independently.`
? `PRODUCT.md and explicit incumbent brand commitments constrain every direction.
The seed never chooses exact colors, fonts, tokens, or a user preference, and
it never permits the world and first surface to be selected independently.`
: `PRODUCT.md and DESIGN.md constrain every surface candidate's identity
vocabulary; they do not cancel task-level composition. The seed never
authorizes a new palette, type system, material world, or unfamiliar control
behavior.`;
const richnessInstruction = `The CREATIVE SPARK is a visual world, artifact, or graphic tradition people
would genuinely choose to enter, study, or explore, and whose palette,
materials, type voice, and component grammar a designer could sketch on
sight, not decorative art direction. The challengers are drawn two from each
translation tier: graphic systems that map to interface almost directly,
instrument or display languages that carry interaction physics, and material
or performed worlds that need the largest translation step; judge
each in its own register and pay that translation cost honestly. Translate
its scale, material, spatial or compositional law, tension, rhythm, and
memorable human experience into product structure. Inherit a movement's or
artifact's rules, never just its name: grid, geometry, ornament logic,
material behavior, and information structure become the interface's. Preserve
the spark's imaginative distance: do not collapse a galaxy into a mission
dashboard, a forest into a taxonomy app, or a performance into a control
console. Translation is complete only when the source name and physical prop
can disappear while a product-native relationship, state change, or proof
remains. A carrier survives only when product evidence makes it functional;
never name a candidate after the prompt merely to preserve the spark. Use
Three.js, generative motion, film language, typography, craft,
or another ambitious medium when it materially strengthens the task; keep
semantic structure and graceful fallbacks fully capable.`;
const richnessInstruction = `The CREATIVE SPARK is a complete visual system, not a theme or decorative
reference. Translate every supplied system rule into the product: palette and
material, type and composition, topology, controls and states, and adaptation.
Keep the source's visible character, scale, rhythm, and interaction instead of
reducing vivid grammar to generic nouns. When the source is already a credible
interface language, commit to it across navigation, content, controls, and
states. Otherwise keep a literal carrier only when it becomes functional.
Ambitious motion, spatial media, or interaction is welcome when it strengthens
the product without weakening semantics, performance, or fallback behavior.`;
if (!data) {
return `${scope.toUpperCase()} CONCEPT SEED (key: ${key}; mode: ${mode ?? 'unscoped'}; source: degraded; rerun with --scope ${scope}${mode ? ` --mode ${mode}` : ''} --from ${key}${reroll > 0 ? ` --reroll ${reroll}` : ''})
PROMOTED INDEX: ${buildIndex}
return `${scope.toUpperCase()} CONCEPT SEED (key: ${key}; mode: ${mode ?? 'unscoped'}; source: degraded; rerun with --scope ${scope}${mode ? ` --mode ${mode}` : ''} --from ${key}${reroll > 0 ? ` --reroll ${reroll}` : ''} --candidate-count ${candidateCount})
ASSIGNED INDEX: ${buildIndex}
${promotedInstruction}
The promotion exists to refuse the model's ranking rut, not to outrank the
user or the brief. Never expose promotion metadata in choice labels or order.
The assignment exists to refuse the model's ranking rut, never to outrank
the user or the brief. Never expose assignment metadata in user-facing labels.
No challengers this run: the roll service was unreachable and no local
catalog exists. Proceed with the grounded candidates alone; the promotion
catalog exists. Proceed with the grounded candidates alone; the assignment
above still applies at full strength.
${authorityInstruction}
A user- or brief-pinned decision beats the roll, always.
@@ -461,11 +467,11 @@ habitual composition, but keep only structures that strengthen this product.\n`
after resolution. The ping is anonymous (chosen id only) and is skipped
automatically when DO_NOT_TRACK or IMPECCABLE_NO_TELEMETRY is set.\n`
: '';
return `${scope.toUpperCase()} CONCEPT SEED (key: ${key}; mode: ${mode ?? 'unscoped'}; source: ${data.source}; approved pool: ${data.poolRevision}; ${data.approvedCount}/${data.catalogCount} human-approved; rerun with --scope ${scope}${mode ? ` --mode ${mode}` : ''} --from ${key}${reroll > 0 ? ` --reroll ${reroll}` : ''} to reproduce this roll against this catalog revision)
${rerollBlock}PROMOTED INDEX: ${buildIndex}
return `${scope.toUpperCase()} CONCEPT SEED (key: ${key}; mode: ${mode ?? 'unscoped'}; source: ${data.source}; approved pool: ${data.poolRevision}; ${data.approvedCount}/${data.catalogCount} human-approved; rerun with --scope ${scope}${mode ? ` --mode ${mode}` : ''} --from ${key}${reroll > 0 ? ` --reroll ${reroll}` : ''} --candidate-count ${candidateCount} to reproduce this roll against this catalog revision)
${rerollBlock}ASSIGNED INDEX: ${buildIndex}
${promotedInstruction}
The promotion exists to refuse the model's ranking rut, not to outrank the
user or the brief. Never expose promotion metadata in choice labels or order.
The assignment exists to refuse the model's ranking rut, never to outrank
the user or the brief. Never expose assignment metadata in user-facing labels.
CHALLENGERS:
${data.challengers.map(renderChallenger).join('\n')}
${stagingBlock}${challengerInstruction}
@@ -481,6 +487,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur
const scopeIdx = args.indexOf('--scope');
const rerollIdx = args.indexOf('--reroll');
const modeIdx = args.indexOf('--mode');
const candidateCountIdx = args.indexOf('--candidate-count');
const chosenIdx = args.indexOf('--chosen');
try {
if (chosenIdx !== -1) {
@@ -500,6 +507,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur
: (process.env.IMPECCABLE_CONCEPT_SEED || crypto.randomBytes(4).toString('hex')),
reroll: rerollIdx !== -1 ? Number(args[rerollIdx + 1]) : 0,
mode: modeIdx !== -1 ? args[modeIdx + 1] : null,
candidateCount: candidateCountIdx !== -1 ? Number(args[candidateCountIdx + 1]) : 7,
}));
}
} catch (error) {
+4 -133
View File
@@ -26,7 +26,6 @@
* loadDetector() -> Promise<{ detectText, detectHtml }>
* matchesAnyGlob(filePath, globs)
* normalizeScanTargets(primaryTargets, projectCwd)
* extractDirectionContract(content) / renderContractAudit(entries, opts)
* runHook(deps) -> { exitCode, stdout, audit, reason? }
* runStopHook(deps) -> { exitCode, stdout, audit, emission? }
*
@@ -1888,100 +1887,6 @@ export async function runHook({ stdinJson, env = {}, cwd = process.cwd(), now =
}
}
// ── Direction-contract audit ─────────────────────────────────────────────
// The skill's decide-then-build step opens the built HTML artifact with a
// DIRECTION CONTRACT comment (THESIS / OWN-WORLD / STORY / FIRST VIEWPORT /
// BAR-RAISER / FORM blocks). At Stop time the deep pass extracts that
// comment and feeds it back so the model audits the render against its own
// promises. Proven in the eval harness: sample contracts promised radical
// compositions and the build shipped the standard template anyway, because
// nothing ever judged the build against the contract. Zero extra API calls:
// this is a message, not a judge. It fires at most once per file per session
// (a `contractAudited` flag on the session cache entry, the same state the
// deep pass uses for finding dedupe) and rides the Stop pass's existing
// emission rather than adding another block round.
// How far into the file to look for the leading comment. A contract lives at
// the very top of the artifact; anything deeper is not the contract.
export const CONTRACT_HEAD_CHARS = 6000;
// The contract/concept marker must appear early in the comment body, so a
// license header or unrelated note does not get mistaken for a contract.
export const CONTRACT_MARKER_CHARS = 200;
// Cap the extracted contract so a rambling comment cannot blow up the
// Stop message.
export const CONTRACT_MAX_CHARS = 1800;
// Cap contract sections per Stop emission so many touched artifacts cannot
// stack an unbounded message.
export const CONTRACT_AUDIT_MAX_FILES = 3;
export const CONTRACT_EXTS = new Set(['.html', '.htm', '.astro', '.svelte', '.vue', '.jsx', '.tsx']);
export const CONTRACT_REQUIRED_FIELDS = ['THESIS', 'OWN-WORLD', 'STORY', 'FIRST VIEWPORT', 'BAR-RAISER', 'FORM'];
/**
* Extract the artifact's own direction-contract comment from the head of an
* HTML or component file. Supports HTML-family comments and JSX block
* comments so the contract works in the Astro/Svelte/Vue/React scaffolds the
* skill actually builds. Returns the trimmed, length-capped body, or null
* when the file carries no valid contract block.
*/
export function extractDirectionContract(content) {
if (typeof content !== 'string' || !content) return null;
const head = content.slice(0, CONTRACT_HEAD_CHARS);
const candidates = [];
for (const pattern of [/<!--([\s\S]*?)-->/g, /\{\/\*([\s\S]*?)\*\/\}/g]) {
for (const match of head.matchAll(pattern)) {
const index = match.index ?? 0;
const linePrefix = head.slice(head.lastIndexOf('\n', index - 1) + 1, index).trim();
if (linePrefix.startsWith('//')) continue;
candidates.push({ index, body: match[1].trim() });
}
}
candidates.sort((a, b) => a.index - b.index);
for (const candidate of candidates) {
if (!candidate.body || !/direction\s+contract|concept\s+contract/i.test(candidate.body.slice(0, CONTRACT_MARKER_CHARS))) continue;
return candidate.body.slice(0, CONTRACT_MAX_CHARS);
}
return null;
}
export function missingDirectionContractFields(contract) {
const body = typeof contract === 'string' ? contract : '';
return CONTRACT_REQUIRED_FIELDS.filter((field) => {
const label = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s+');
return !new RegExp(`\\b${label}\\s*:`, 'i').test(body);
});
}
/**
* Render the contract-audit section of the Stop message. `entries` is
* [{ filePath, contract }]; at most CONTRACT_AUDIT_MAX_FILES are shown.
* The two failure shapes named here are the ones observed in practice:
* a promise the pixels do not deliver, and a contract whose own plan is
* the standard template wearing the concept's nouns.
*/
export function renderContractAudit(entries, opts = {}) {
if (!Array.isArray(entries) || entries.length === 0) return '';
const cwd = opts.cwd || process.cwd();
const shown = entries.slice(0, CONTRACT_AUDIT_MAX_FILES);
const blocks = shown.map(({ filePath, contract }) => {
const display = relativize(filePath, cwd);
const missing = missingDirectionContractFields(contract);
const integrity = missing.length > 0
? `\n\nContract integrity defect: missing ${missing.join(', ')}. Repair the contract and the implementation together.`
: '';
return `${display} opens with this direction contract, written when the direction was decided:\n\n${contract}${integrity}`;
});
return [
`${ENVELOPE_PREFIX} Direction-contract audit. Before finishing, audit the rendered page against the contract it opens with, promise by promise.`,
'',
blocks.join('\n\n'),
'',
'Two failure shapes to check honestly:',
'1. A promise that is not in the pixels: the contract describes a composition or structure the built page does not deliver, because the build fell back to a standard arrangement, possibly one the contract explicitly rejects. Rebuild that part until the render matches the promise.',
"2. A contract whose own section plan is the standard template wearing the concept's nouns: if a neighboring product could ship the same sequence of sections under different labels, revise the plan and the page together.",
'State each promise and whether the render delivers it, and fix every gap before finishing.',
].join('\n');
}
// Cap on files the Stop deep pass will scan. The touched-file list is
// session-scoped and already capped per edit, but a very long session could
// accumulate more than the 30s hook timeout comfortably covers.
@@ -1990,10 +1895,7 @@ export const STOP_MAX_FILES = 20;
/**
* Run the Stop-event deep pass: the FULL detector rule set over every UI
* file touched this session, surfaced once, deduped against everything the
* per-edit hook already reported. Touched HTML artifacts that open with a
* direction-contract comment additionally get a one-time contract-audit
* section appended after the detector findings (see extractDirectionContract
* above). Same result contract as runHook():
* per-edit hook already reported. Same result contract as runHook():
* { exitCode, stdout, audit, emission? }
*
* Never throws; exits silent (and fast) when the session touched no UI
@@ -2060,7 +1962,6 @@ export async function runStopHook({ stdinJson, env = {}, cwd = process.cwd(), no
const scanOptions = designSystemOptions(config, det, projectCwd);
const freshGroups = [];
const contractEntries = [];
let scanned = 0;
for (const filePath of touched) {
if (scanned >= STOP_MAX_FILES) break;
@@ -2082,22 +1983,6 @@ export async function runStopHook({ stdinJson, env = {}, cwd = process.cwd(), no
? configuredExt.engine === 'html'
: (ext === '.html' || ext === '.htm');
// Direction-contract audit: HTML and component artifacts, at most once per file
// per session. The flag lives on the same session cache entry the
// finding dedupe uses, so a second Stop fire stays quiet about it.
const contractCapable = useHtmlEngine || CONTRACT_EXTS.has(ext);
if (contractCapable) {
const fileEntry = ensureFile(cache, sessionId, filePath);
if (!fileEntry.contractAudited) {
const contract = extractDirectionContract(content);
if (contract) {
fileEntry.contractAudited = true;
ensureSession(cache, sessionId).updatedAt = Date.now();
contractEntries.push({ filePath, contract });
}
}
}
if (useHtmlEngine && typeof det.detectHtml === 'function') {
try { findings = await det.detectHtml(filePath, scanOptions); } catch { findings = []; }
} else {
@@ -2116,41 +2001,27 @@ export async function runStopHook({ stdinJson, env = {}, cwd = process.cwd(), no
}
audit.scannedFiles = scanned;
if (freshGroups.length === 0 && contractEntries.length === 0) {
if (freshGroups.length === 0) {
return result({ emitted: false, skipped: 'stop-clean', durationMs: Date.now() - started });
}
// Fresh findings and first-time contract audits earn the cache write;
// both mark this batch as surfaced so the next Stop fire is silent
// Fresh findings earn the cache write so the next Stop fire is silent
// unless new issues appear.
persistCache(projectCwd, cache);
// Detector findings first, then the contract audit. Both ride the same
// single Stop emission: the audit never adds an extra block round.
const parts = [];
if (freshGroups.length > 0) {
parts.push(renderGroupedTemplate(freshGroups, config, { cwd: projectCwd }));
}
if (contractEntries.length > 0) {
parts.push(renderContractAudit(contractEntries, { cwd: projectCwd }));
}
const text = appendDesignSystemNote(parts.join('\n\n'), scanOptions);
const text = appendDesignSystemNote(renderGroupedTemplate(freshGroups, config, { cwd: projectCwd }), scanOptions);
return {
exitCode: 0,
stdout: payload(text, 'Stop', harness),
emission: {
kind: 'stop-deep-pass',
groups: freshGroups,
...(contractEntries.length > 0
? { contractFiles: contractEntries.map((entry) => entry.filePath) }
: {}),
},
audit: {
...audit,
emitted: true,
freshFiles: freshGroups.length,
freshFindings: freshGroups.reduce((sum, group) => sum + group.findings.length, 0),
...(contractEntries.length > 0 ? { contractAudits: contractEntries.length } : {}),
chars: text.length,
durationMs: Date.now() - started,
},
+1 -3
View File
@@ -10,9 +10,7 @@
* `hookSpecificOutput.additionalContext` when findings exist.
* - Stop: runs the FULL detector rule set over every UI file touched this
* session (the deep pass), deduped against what the per-edit pass already
* surfaced, and emits once via the Stop additionalContext channel. Touched
* HTML artifacts opening with a direction-contract comment get a one-time
* contract-audit section appended to the same emission.
* surfaced, and emits once via the Stop additionalContext channel.
*
* Contract: never break a turn. Always exit 0. Clean files emit a small ack
* unless quiet mode is enabled; a clean Stop pass is silent.
+23 -3
View File
@@ -37,7 +37,7 @@ function run(scope, extraArgs = [], env = {}) {
}
describe('concept seed scopes', () => {
it('keeps coupled-direction and established-world surface rolls reproducible but independent', () => {
it('keeps complete-direction and established-world surface rolls reproducible but independent', () => {
const directionA = run('direction');
const directionB = run('direction');
const surface = run('surface');
@@ -48,10 +48,12 @@ describe('concept seed scopes', () => {
assert.match(directionA.stdout, /source: local/);
assert.match(directionA.stdout, /selected\s+independently/);
assert.match(directionA.stdout, /substantially different future surface/);
assert.match(directionA.stdout, /Never expose promotion metadata/);
assert.match(directionA.stdout, /Never expose assignment metadata/);
assert.match(directionA.stdout, /SYSTEM GRAMMAR:/);
assert.match(directionA.stdout, /CREATIVE SPARK:/);
assert.match(directionA.stdout, /WEB LEVERAGE:/);
assert.match(directionA.stdout, /credible\s+interface language/);
assert.match(directionA.stdout, /commit to it across navigation/);
assert.doesNotMatch(directionA.stdout, /undefined/);
assert.match(surface.stdout, /SURFACE CONCEPT SEED/);
assert.match(surface.stdout, /committed visual identity/);
@@ -63,6 +65,24 @@ describe('concept seed scopes', () => {
assert.match(result.stderr, /direction or surface/);
});
it('never promotes a rank outside the grounded candidate ledger', () => {
for (const count of [5, 6, 7]) {
for (let index = 0; index < 30; index += 1) {
const result = spawnSync(process.execPath, [SCRIPT, '--scope', 'direction', '--from', `count-${count}-${index}`, '--candidate-count', String(count)], {
cwd: ROOT,
encoding: 'utf-8',
env: { ...process.env, IMPECCABLE_CATALOG_DIR: FIXTURE_DIR },
});
assert.equal(result.status, 0);
const promoted = Number(result.stdout.match(/ASSIGNED INDEX: (\d+)/)?.[1]);
assert.equal(promoted >= 3 && promoted <= count, true, `rank ${promoted} must fit ${count} candidates`);
}
}
const invalid = run('direction', ['--candidate-count', '4']);
assert.notEqual(invalid.status, 0);
assert.match(invalid.stderr, /integer from 5 to 7/);
});
it('degrades to a promotion-only seed when catalog and API are both unreachable', () => {
const degraded = run('direction', ['--mode', 'persuade'], {
IMPECCABLE_CATALOG_DIR: '/nonexistent-catalog-dir',
@@ -71,7 +91,7 @@ describe('concept seed scopes', () => {
});
assert.equal(degraded.status, 0);
assert.match(degraded.stdout, /source: degraded/);
assert.match(degraded.stdout, /PROMOTED INDEX: [3-7]/);
assert.match(degraded.stdout, /ASSIGNED INDEX: [3-7]/);
assert.match(degraded.stdout, /No challengers this run/);
assert.doesNotMatch(degraded.stdout, /CHALLENGERS:/);
});
-273
View File
@@ -53,12 +53,6 @@ import {
IMMEDIATE_TIER_RULES,
splitFindingsByTier,
perEditTieringActive,
extractDirectionContract,
missingDirectionContractFields,
renderContractAudit,
CONTRACT_MAX_CHARS,
CONTRACT_HEAD_CHARS,
CONTRACT_AUDIT_MAX_FILES,
payload,
extractFindingIgnoreValue,
resolveProjectPlatform,
@@ -3335,270 +3329,3 @@ describe('runStopHook()', () => {
assert.equal(reentrant.stdout, '');
});
});
describe('extractDirectionContract()', () => {
const contractComment = [
'<!--',
'DIRECTION CONTRACT',
'THESIS: the page is a boarding pass and refuses the centered travel hero.',
'-->',
].join('\n');
it('extracts the leading contract comment body', () => {
const html = `${contractComment}\n<!doctype html><html><body>hi</body></html>`;
const body = extractDirectionContract(html);
assert.ok(body);
assert.match(body, /^DIRECTION CONTRACT/);
assert.match(body, /boarding pass/);
assert.doesNotMatch(body, /<!--|-->/);
});
it('finds the contract even after a doctype line', () => {
const html = `<!doctype html>\n${contractComment}\n<html></html>`;
assert.match(extractDirectionContract(html) || '', /boarding pass/);
});
it('extracts a JSX direction-contract block', () => {
const jsx = `{/*\nDIRECTION CONTRACT\nTHESIS: the timeline folds around the evidence.\n*/}\nexport default function Page() { return <main />; }`;
assert.match(extractDirectionContract(jsx) || '', /timeline folds/);
});
it('reports missing contract fields deterministically', () => {
assert.deepEqual(
missingDirectionContractFields('DIRECTION CONTRACT\nTHESIS: own idea\nFORM: evidence timeline'),
['OWN-WORLD', 'STORY', 'FIRST VIEWPORT', 'BAR-RAISER'],
);
assert.deepEqual(missingDirectionContractFields([
'THESIS: x', 'OWN-WORLD: x', 'STORY: x',
'FIRST VIEWPORT: x', 'BAR-RAISER: x', 'FORM: x',
].join('\n')), []);
});
it('returns null when there is no comment at all', () => {
assert.equal(extractDirectionContract('<!doctype html><html></html>'), null);
assert.equal(extractDirectionContract(''), null);
assert.equal(extractDirectionContract(null), null);
});
it('returns null when the first comment is not a contract', () => {
const html = '<!-- Copyright 2026 Example Corp. All rights reserved. -->\n<html></html>';
assert.equal(extractDirectionContract(html), null);
});
it('requires the marker word inside the first 200 chars of the comment', () => {
const padding = 'x'.repeat(250);
const html = `<!-- ${padding} contract -->\n<html></html>`;
assert.equal(extractDirectionContract(html), null);
});
it('caps the extracted body at CONTRACT_MAX_CHARS', () => {
const longBody = `DIRECTION CONTRACT\n${'promise '.repeat(600)}END-MARKER`;
const html = `<!--${longBody}-->\n<html></html>`;
const body = extractDirectionContract(html);
assert.ok(body);
assert.equal(body.length, CONTRACT_MAX_CHARS);
assert.doesNotMatch(body, /END-MARKER/);
});
it('returns null for an unclosed (malformed) comment', () => {
const html = '<!-- DIRECTION CONTRACT: never closed\n<html><body></body></html>';
assert.equal(extractDirectionContract(html), null);
});
it('returns null when the comment starts past the head window', () => {
const html = `${' '.repeat(CONTRACT_HEAD_CHARS)}${contractComment}<html></html>`;
assert.equal(extractDirectionContract(html), null);
});
it('renderContractAudit shows at most CONTRACT_AUDIT_MAX_FILES sections', () => {
const entries = Array.from({ length: CONTRACT_AUDIT_MAX_FILES + 2 }, (_, i) => ({
filePath: `/tmp/page-${i}.html`,
contract: `DIRECTION CONTRACT ${i}`,
}));
const text = renderContractAudit(entries, { cwd: '/tmp' });
assert.match(text, new RegExp(`DIRECTION CONTRACT ${CONTRACT_AUDIT_MAX_FILES - 1}`));
assert.doesNotMatch(text, new RegExp(`DIRECTION CONTRACT ${CONTRACT_AUDIT_MAX_FILES}\\b`));
assert.equal(renderContractAudit([], {}), '');
});
it('renderContractAudit flags an incomplete promise contract', () => {
const text = renderContractAudit([{ filePath: '/tmp/page.html', contract: 'DIRECTION CONTRACT\nTHESIS: one idea' }], { cwd: '/tmp' });
assert.match(text, /Contract integrity defect/);
assert.match(text, /BAR-RAISER/);
});
});
describe('runStopHook() — direction-contract audit', () => {
let cwd;
beforeEach(() => { cwd = mkTmp(); });
afterEach(() => fs.rmSync(cwd, { recursive: true, force: true }));
const CONTRACT_HTML = [
'<!--',
'DIRECTION CONTRACT',
'THESIS: the page is a boarding pass.',
'FIRST VIEWPORT: gate number dominates.',
'BAR-RAISER: live gate status changes the reading path.',
'-->',
'<!doctype html><html><body><h1>Gate 12</h1></body></html>',
].join('\n');
function write(rel, body) {
const abs = path.join(cwd, rel);
fs.mkdirSync(path.dirname(abs), { recursive: true });
fs.writeFileSync(abs, body);
return abs;
}
function editEvent(file, sessionId) {
return {
session_id: sessionId,
cwd,
hook_event_name: 'PostToolUse',
tool_name: 'Edit',
tool_input: { file_path: file },
};
}
function stopEvent(sessionId) {
return {
session_id: sessionId,
cwd,
hook_event_name: 'Stop',
stop_hook_active: false,
};
}
// Seed the session's touched-file list. `.impeccable/` must exist so a
// clean per-edit pass still persists the cache (see runHook write gating).
async function touch(file, sid, det) {
fs.mkdirSync(path.join(cwd, '.impeccable'), { recursive: true });
await runHook({ stdinJson: JSON.stringify(editEvent(file, sid)), env: {}, cwd, detector: det });
}
it('appends the contract audit after the detector findings', async () => {
const sid = 'contract-with-findings';
const file = write('index.html', CONTRACT_HTML);
const det = fakeDetector([finding('em-dash-overuse', 3)]);
await touch(file, sid, det);
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: det });
assert.equal(stop.audit.emitted, true);
assert.equal(stop.audit.contractAudits, 1);
const text = JSON.parse(stop.stdout).hookSpecificOutput.additionalContext;
assert.match(text, /em-dash-overuse/);
assert.match(text, /Direction-contract audit/);
assert.match(text, /boarding pass/);
assert.match(text, /not in the pixels/);
assert.ok(
text.indexOf('em-dash-overuse') < text.indexOf('Direction-contract audit'),
'detector findings come before the contract audit',
);
assert.deepEqual(stop.emission.contractFiles, [file]);
// Included exactly once.
assert.equal(text.match(/Direction-contract audit/g).length, 1);
});
it('emits the audit even when the detector deep pass is clean', async () => {
const sid = 'contract-clean-detector';
const file = write('index.html', CONTRACT_HTML);
const det = fakeDetector([]);
await touch(file, sid, det);
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: det });
assert.equal(stop.audit.emitted, true);
const out = JSON.parse(stop.stdout);
assert.equal(out.hookSpecificOutput.hookEventName, 'Stop');
assert.match(out.hookSpecificOutput.additionalContext, /Direction-contract audit/);
assert.doesNotMatch(out.hookSpecificOutput.additionalContext, /findings requiring review/);
});
it('audits contracts in Astro, Svelte, Vue, JSX, and TSX artifacts', async () => {
const htmlFamily = ['astro', 'svelte', 'vue'];
const jsxFamily = ['jsx', 'tsx'];
for (const ext of htmlFamily) {
const sid = `contract-${ext}`;
const file = write(`src/Page.${ext}`, `<!--\nDIRECTION CONTRACT\nTHESIS: ${ext} evidence ribbon.\n-->\n<main />`);
await touch(file, sid, fakeDetector([]));
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: fakeDetector([]) });
assert.match(stop.stdout, new RegExp(`${ext} evidence ribbon`));
}
for (const ext of jsxFamily) {
const sid = `contract-${ext}`;
const file = write(`src/Page.${ext}`, `{/*\nDIRECTION CONTRACT\nTHESIS: ${ext} evidence ribbon.\n*/}\nexport default function Page() { return <main />; }`);
await touch(file, sid, fakeDetector([]));
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: fakeDetector([]) });
assert.match(stop.stdout, new RegExp(`${ext} evidence ribbon`));
}
});
it('adds no audit section when the HTML has no contract comment', async () => {
const sid = 'no-contract';
const file = write('index.html', '<!doctype html><html><body>plain</body></html>');
const det = fakeDetector([finding('em-dash-overuse', 3)]);
await touch(file, sid, det);
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: det });
const text = JSON.parse(stop.stdout).hookSpecificOutput.additionalContext;
assert.match(text, /em-dash-overuse/);
assert.doesNotMatch(text, /Direction-contract audit/);
assert.equal(stop.audit.contractAudits, undefined);
assert.equal(stop.emission.contractFiles, undefined);
});
it('a second Stop never repeats the audit, even with fresh detector findings', async () => {
const sid = 'contract-once';
const file = write('index.html', CONTRACT_HTML);
await touch(file, sid, fakeDetector([]));
const first = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: fakeDetector([]) });
assert.match(first.stdout, /Direction-contract audit/);
// No new findings: fully silent.
const second = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: fakeDetector([]) });
assert.equal(second.stdout, '');
assert.equal(second.audit.skipped, 'stop-clean');
// New findings ride a fresh emission, but the audit does not come back.
const third = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: fakeDetector([finding('side-tab', 7)]) });
const text = JSON.parse(third.stdout).hookSpecificOutput.additionalContext;
assert.match(text, /side-tab/);
assert.doesNotMatch(text, /Direction-contract audit/);
});
it('ignores marker text outside a supported contract comment block', async () => {
const sid = 'contract-non-html';
const file = write('src/Card.tsx', '{/* stub */}\n// <!-- DIRECTION CONTRACT: not an artifact -->\nexport default 1;\n');
const det = fakeDetector([finding('em-dash-overuse', 3)]);
await touch(file, sid, det);
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: det });
const text = JSON.parse(stop.stdout).hookSpecificOutput.additionalContext;
assert.match(text, /em-dash-overuse/);
assert.doesNotMatch(text, /Direction-contract audit/);
});
it('stays silent on a malformed contract with nothing else to report', async () => {
const sid = 'contract-malformed';
const file = write('index.html', '<!-- DIRECTION CONTRACT never closed\n<html><body></body></html>');
await touch(file, sid, fakeDetector([]));
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: fakeDetector([]) });
assert.equal(stop.stdout, '');
assert.equal(stop.audit.skipped, 'stop-clean');
});
it('truncates an oversized contract in the emitted message', async () => {
const sid = 'contract-huge';
const huge = `<!--\nDIRECTION CONTRACT\n${'promise '.repeat(600)}TAIL-MARKER\n-->\n<html></html>`;
const file = write('index.html', huge);
await touch(file, sid, fakeDetector([]));
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: fakeDetector([]) });
const text = JSON.parse(stop.stdout).hookSpecificOutput.additionalContext;
assert.match(text, /Direction-contract audit/);
assert.doesNotMatch(text, /TAIL-MARKER/);
});
});
@@ -99,11 +99,7 @@ for (const modelId of resolveModelList()) {
assert.ok(fileLoaded(trace, 'new-work.md'), `new-work.md was not loaded.\n${workflowTraceMessage(trace)}`);
assert.ok(question >= 0, `task concept was never put to the user.\n${workflowTraceMessage(trace)}`);
assert.ok(implementation > question, `implementation began before the attended concept checkpoint.\n${workflowTraceMessage(trace)}`);
const artifact = fs.readFileSync(path.join(workspace, 'index.html'), 'utf8');
assert.match(artifact.slice(0, 1400), /DIRECTION CONTRACT/i);
for (const field of ['THESIS', 'OWN-WORLD', 'STORY', 'FIRST VIEWPORT', 'BAR-RAISER', 'FORM']) {
assert.match(artifact.slice(0, 1800), new RegExp(`${field}:`, 'i'));
}
assert.equal(fs.existsSync(path.join(workspace, 'index.html')), true, 'new-work must still produce the requested artifact');
} finally {
cleanupWorkspace(workspace);
}