Fix: keep direction contracts out of production output (#673)

Store direction contracts in development-only surface briefs and guard against browser-delivered leakage with regression coverage.

AI-assisted merge: reviewed and executed by Codex under maintainer direction.
This commit is contained in:
Morgan
2026-08-31 19:07:44 -04:00
committed by GitHub
parent ee1442d7af
commit 1130d00ebc
3 changed files with 44 additions and 4 deletions
+3 -2
View File
@@ -336,7 +336,7 @@ As you run commands, Impeccable writes working files under `.impeccable/`: criti
# Unanchored: .impeccable may sit at the repo root or under a nested
# workspace (apps/web/.impeccable/...); anchored patterns would miss it.
# Shared artifacts stay tracked: config.json, live/config.json,
# design.json, critique/*.md.
# design.json, surfaces/*.md, critique/*.md.
.impeccable/config.local.json
.impeccable/hook.cache.json
.impeccable/hook.pending.json
@@ -364,6 +364,7 @@ The block is wrapped in `# impeccable-ignore-start` / `# impeccable-ignore-end`
- `.impeccable/config.json` (unified shared config)
- `.impeccable/live/config.json` (live-mode framework wiring)
- `.impeccable/design.json` (shared design spec)
- `.impeccable/surfaces/*.md` (route- or artifact-specific strategy and direction contracts)
- `.impeccable/critique/*.md` (review reports)
If an ephemeral file (a screenshot, `config.local.json`) was committed before you added the block, `.gitignore` will not untrack it automatically. Run `git rm --cached <path>` to stop tracking it without deleting your local copy.
@@ -388,7 +389,7 @@ For debugging, set `hook.auditLog` in `.impeccable/config.json` to a path (or th
## Build path: comp-first or code-first
When a new surface gets designed, Impeccable either generates a full-fidelity comp first and builds to match it, or builds straight in code with the ambition written into the direction contract and checked at the finish. Comp-first composes bolder and takes longer; code-first is leaner and faster. `/impeccable init` asks once and records the answer as `buildPath` in `.impeccable/config.json`:
When a new surface gets designed, Impeccable either generates a full-fidelity comp first and builds to match it, or builds straight in code with the ambition written into a development-only direction contract in the surface brief and checked at the finish. Comp-first composes bolder and takes longer; code-first is leaner and faster. `/impeccable init` asks once and records the answer as `buildPath` in `.impeccable/config.json`:
```json
{ "buildPath": "comp" }
+6 -2
View File
@@ -72,16 +72,20 @@ Your measured rendition prior: warm, bookish, family, and child-facing subjects
## 5. Record the decision
Before code, state the chosen direction as a contract in the artifact's opening comment, five short blocks, 150 words at most, in a form that survives the production build: an HTML comment in the emitted markup, never only a templating-frontmatter comment, placed as the first child of the document's body in the root layout, never inside a slotted or child component (some compilers, Astro among them, strip a slot's leading comment while keeping deeper ones). After the first production build, grep the built output for the seed key; a contract the build erased is a contract nobody can audit. 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, and where the primary action sits. FORM: the chosen form, its position on your ordered list, and the seed key the script printed. Close with one more line, FINISH: the run's exit condition, verbatim "unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, DESIGN.md, and every shipping raster carrying its provenance". The comment tops the artifact you re-open on every edit, the one reminder that survives a long build: a page that looks complete with the FINISH line undischarged is not done, it is abandoned at the finish line. 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 -->
Before code, record the chosen direction as a development-only contract under `## Direction contract` in the relevant surface brief. A direction contract is durable route or artifact strategy, so create or update the brief even when no other surface strategy needs persistence. Keep the contract to six short blocks and 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, and where the primary action sits. FORM: the chosen form, its position on your ordered list, and the seed key the script printed. Close with one more line, FINISH: the run's exit condition, verbatim "unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, DESIGN.md, and every shipping raster carrying its provenance". The surface brief is the reminder later agents reload across edits and sessions: a page that looks complete with the FINISH line undischarged is not done, it is abandoned at the finish line. 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 -->
Never copy the direction contract into implementation source or any browser-delivered artifact. This includes HTML or framework comments, hidden DOM, `<template>` elements, `data-*` attributes, rendered JSX or TSX output, serialized props or state, React Server Component payloads, client bundles, metadata or JSON-LD, accessibility-only text, and files served beside the artifact. A compiler or optimizer removing development metadata is not a safety boundary. Reviewers and documenters receive the contract from the surface brief.
On a new or replacement world, DESIGN.md is written at finish, from the built world, by the shipped documenter (section 7); a rulebook written before the build gets defended against reality instead of describing it, and hands the design-system detector an unstable target. A new world shipped with no DESIGN.md is still an incomplete run. An ordinary extension does not rewrite DESIGN.md. <!-- rule:skill-design-md-from-the-build -->
If the work establishes durable strategy for a route or artifact, read its existing surface brief, then update it:
Read the existing surface brief before updating it:
`node {{scripts_path}}/surface-brief.mjs read <primary-target>`
`node {{scripts_path}}/surface-brief.mjs write <primary-target> <body-file> [related-target ...]`
After writing, read the brief once more and verify that all six contract blocks and the seed key are present before building.
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.
On a comp-led build, whenever any image generation is available (a harness-native tool or the API fallback context.mjs reports), the locked direction is visualized before it is built, never skipped: load [visualize.md](visualize.md) and follow it, three compositional options put before the user for approval, the chosen card's decision comp plus two variations. This step is proven to produce the most compositional and ambitious work. On a code-led build the comp round is skipped by contract, never by drift: the ambition it would have carried lives in the direction contract's FIRST VIEWPORT block and named signature interaction, and the finish reviewer audits those promises in behavior. <!-- rule:skill-visualize-before-build -->
+35
View File
@@ -7,6 +7,41 @@ import { fileURLToPath } from 'node:url';
const ROOT = fileURLToPath(new URL('..', import.meta.url));
describe('skill reference authoring contracts', () => {
it('keeps direction contracts in development-only surface briefs', () => {
const newWork = readFileSync(join(ROOT, 'skill/reference/new-work.md'), 'utf-8').replace(/\r\n?/g, '\n');
const recordDecision = newWork.match(/## 5\. Record the decision\n([\s\S]*?)\n## 6\./)?.[1] ?? '';
assert.match(recordDecision, /development-only contract/);
assert.match(recordDecision, /under `## Direction contract` in the relevant surface brief/);
assert.match(recordDecision, /read the brief once more/i);
assert.match(recordDecision, /all six contract blocks and the seed key/);
for (const block of ['THESIS', 'OWN-WORLD', 'STORY', 'FIRST VIEWPORT', 'FORM', 'FINISH']) {
assert.match(recordDecision, new RegExp(`${block}:`));
}
for (const browserArtifact of [
/HTML or framework comments/,
/hidden DOM/,
/<template>/,
/`data-\*` attributes/,
/serialized props or state/,
/React Server Component payloads/,
/client bundles/,
/metadata or JSON-LD/,
/accessibility-only text/,
]) {
assert.match(recordDecision, browserArtifact);
}
assert.match(recordDecision, /Never copy the direction contract into implementation source or any browser-delivered artifact/);
assert.doesNotMatch(newWork, /contract in the artifact's opening comment/);
assert.doesNotMatch(newWork, /survives the production build/);
assert.doesNotMatch(newWork, /grep the built output/);
assert.doesNotMatch(newWork, /emitted markup/);
assert.doesNotMatch(newWork, /first child of the document's body/);
});
it('keeps reduced-motion guidance on the animation build path', () => {
const animate = readFileSync(join(ROOT, 'skill/reference/animate.md'), 'utf-8').replace(/\r\n?/g, '\n');
const accessibility = animate.match(/## Accessibility and control\n([\s\S]*?)\n## Verify/)?.[1] ?? '';