@bastani/atomic 0.9.0-alpha.2 → 0.9.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +24 -0
- package/dist/builtin/workflows/README.md +12 -12
- package/dist/builtin/workflows/builtin/goal-ledger.ts +2 -0
- package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -0
- package/dist/builtin/workflows/builtin/goal-reports.ts +5 -0
- package/dist/builtin/workflows/builtin/goal-runner.ts +103 -4
- package/dist/builtin/workflows/builtin/goal-types.ts +4 -0
- package/dist/builtin/workflows/builtin/goal.d.ts +4 -0
- package/dist/builtin/workflows/builtin/goal.ts +14 -2
- package/dist/builtin/workflows/builtin/index.d.ts +8 -8
- package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +359 -0
- package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +254 -352
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +256 -414
- package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +272 -0
- package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +58 -68
- package/dist/builtin/workflows/builtin/open-claude-design.d.ts +5 -9
- package/dist/builtin/workflows/builtin/open-claude-design.ts +14 -26
- package/dist/builtin/workflows/builtin/prompt-refinement.ts +102 -0
- package/dist/builtin/workflows/builtin/ralph-core.ts +6 -4
- package/dist/builtin/workflows/builtin/ralph-runner.ts +22 -24
- package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph.ts +3 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +14 -23
- package/dist/builtin/workflows/skills/impeccable/reference/brand.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/live.md +25 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +724 -29
- package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +219 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +57 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +750 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +648 -53
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +44 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +401 -46
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +6 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-args.mjs +42 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +1 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +21 -3
- package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +835 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-core.mjs +180 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +185 -60
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +146 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +3369 -1026
- package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-complete.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +133 -9
- package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +42 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +4 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +21 -15
- package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +205 -1269
- package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-target.mjs +30 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +69 -26
- package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +73 -22
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +3 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +5 -5
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +0 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/docs/index.md +2 -2
- package/docs/quickstart.md +9 -9
- package/docs/workflows.md +816 -47
- package/package.json +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +0 -284
- package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +0 -126
- /package/dist/builtin/workflows/skills/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
|
@@ -258,18 +258,18 @@ export function forkContinuationOptions(
|
|
|
258
258
|
: { context: "fork", forkFromSessionFile: sessionFile };
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
export function
|
|
261
|
+
export function renderResearchPromptRefinementPrompt(args: {
|
|
262
262
|
readonly iteration: number;
|
|
263
263
|
readonly maxLoops: number;
|
|
264
|
-
readonly
|
|
264
|
+
readonly request: string;
|
|
265
265
|
readonly workflowCwdContext: PromptSection;
|
|
266
266
|
readonly latestReviewReportPath: string | undefined;
|
|
267
267
|
}): string {
|
|
268
|
-
const basePrompt = `/skill:prompt-engineer Transform the following user
|
|
268
|
+
const basePrompt = `/skill:prompt-engineer Transform the following refined user request into a codebase and online research question which can be thoroughly explored: ${args.request}`;
|
|
269
269
|
return [
|
|
270
270
|
basePrompt,
|
|
271
271
|
taggedPrompt([
|
|
272
|
-
["iteration", `
|
|
272
|
+
["iteration", `Research prompt refinement iteration ${args.iteration}/${args.maxLoops}.`],
|
|
273
273
|
args.workflowCwdContext,
|
|
274
274
|
[
|
|
275
275
|
"review_findings",
|
|
@@ -404,5 +404,7 @@ export type RalphWorkflowResult = {
|
|
|
404
404
|
readonly iterations_completed: number;
|
|
405
405
|
readonly review_report: string;
|
|
406
406
|
readonly review_report_path?: string;
|
|
407
|
+
readonly original_prompt: string;
|
|
408
|
+
readonly refined_prompt: string;
|
|
407
409
|
};
|
|
408
410
|
|
|
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import type { WorkflowRunContext, WorkflowTaskResult } from "../src/shared/types.js";
|
|
6
6
|
import { E2E_VERIFICATION_GUIDANCE, WORKER_PREFLIGHT_CONTRACT } from "./shared-prompts.js";
|
|
7
|
+
import { runPromptRefinementStage } from "./prompt-refinement.js";
|
|
7
8
|
import { reviewDecisionApproved } from "./ralph-review-gate.js";
|
|
8
9
|
import {
|
|
9
10
|
REVIEWER_COUNT,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
defaultResearchPath,
|
|
15
16
|
forkContinuationOptions,
|
|
16
17
|
renderForkedOrchestratorPrompt,
|
|
17
|
-
|
|
18
|
+
renderResearchPromptRefinementPrompt,
|
|
18
19
|
renderQaE2eVideoGuidance,
|
|
19
20
|
renderResearchPrompt,
|
|
20
21
|
reviewDecisionFromResult,
|
|
@@ -39,13 +40,7 @@ export async function runRalphWorkflow(
|
|
|
39
40
|
ctx: WorkflowRunContext<RalphInputs>,
|
|
40
41
|
options: RalphWorkflowOptions,
|
|
41
42
|
): Promise<RalphWorkflowResult> {
|
|
42
|
-
const {
|
|
43
|
-
prompt,
|
|
44
|
-
maxLoops,
|
|
45
|
-
comparisonBaseBranch,
|
|
46
|
-
workflowStartCwd,
|
|
47
|
-
createPr,
|
|
48
|
-
} = options;
|
|
43
|
+
const { prompt, maxLoops, comparisonBaseBranch, workflowStartCwd, createPr } = options;
|
|
49
44
|
let latestReviewReportPath: string | undefined;
|
|
50
45
|
let finalPlan = "";
|
|
51
46
|
let finalPlanPath = "";
|
|
@@ -53,39 +48,40 @@ export async function runRalphWorkflow(
|
|
|
53
48
|
let finalResearchPath = "";
|
|
54
49
|
let finalResult = "";
|
|
55
50
|
let finalPrReport: string | undefined;
|
|
56
|
-
const
|
|
57
|
-
const
|
|
51
|
+
const workflowCwdContext = workflowCwdContextSection(workflowStartCwd);
|
|
52
|
+
const refinedPrompt = await runPromptRefinementStage(ctx, { request: prompt, workflowLabel: "Ralph", workflowCwdContext, modelConfig: promptEngineerModelConfig });
|
|
53
|
+
const workflowResearchPath = resolve(workflowStartCwd, defaultResearchPath(refinedPrompt));
|
|
54
|
+
const implementationNotesPath = await createImplementationNotesFile(refinedPrompt);
|
|
58
55
|
const qaVideoPath = await createQaEvidenceVideoPath();
|
|
59
56
|
const artifactDir = await mkdtemp(join(tmpdir(), "atomic-ralph-run-"));
|
|
60
|
-
const workflowCwdContext = workflowCwdContextSection(workflowStartCwd);
|
|
61
57
|
let approved = false;
|
|
62
58
|
let iterationsCompleted = 0;
|
|
63
|
-
let
|
|
59
|
+
let previousResearchPromptRefinementSessionFile: string | undefined;
|
|
64
60
|
let previousResearchSessionFile: string | undefined;
|
|
65
61
|
let previousOrchestratorSessionFile: string | undefined;
|
|
66
62
|
for (let iteration = 1; iteration <= maxLoops; iteration += 1) {
|
|
67
63
|
iterationsCompleted = iteration;
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
prompt:
|
|
64
|
+
const researchPromptRefinementForkOptions = forkContinuationOptions(previousResearchPromptRefinementSessionFile);
|
|
65
|
+
const researchPromptRefinement = await ctx.task(`research-prompt-refinement-${iteration}`, {
|
|
66
|
+
prompt: renderResearchPromptRefinementPrompt({
|
|
71
67
|
iteration,
|
|
72
68
|
maxLoops,
|
|
73
|
-
|
|
69
|
+
request: refinedPrompt,
|
|
74
70
|
workflowCwdContext,
|
|
75
71
|
latestReviewReportPath,
|
|
76
72
|
}),
|
|
77
73
|
reads: latestReviewReportPath === undefined ? [] : [latestReviewReportPath],
|
|
78
74
|
...promptEngineerModelConfig,
|
|
79
|
-
...
|
|
75
|
+
...researchPromptRefinementForkOptions,
|
|
80
76
|
});
|
|
81
|
-
|
|
82
|
-
finalPlan =
|
|
77
|
+
previousResearchPromptRefinementSessionFile = researchPromptRefinement.sessionFile;
|
|
78
|
+
finalPlan = researchPromptRefinement.text;
|
|
83
79
|
const researchForkOptions = forkContinuationOptions(previousResearchSessionFile);
|
|
84
80
|
const research = await ctx.task(`research-${iteration}`, {
|
|
85
81
|
prompt: renderResearchPrompt({
|
|
86
82
|
iteration,
|
|
87
83
|
maxLoops,
|
|
88
|
-
transformedResearchQuestion:
|
|
84
|
+
transformedResearchQuestion: researchPromptRefinement.text,
|
|
89
85
|
workflowCwdContext,
|
|
90
86
|
latestReviewReportPath,
|
|
91
87
|
researchPath: workflowResearchPath,
|
|
@@ -111,7 +107,7 @@ export async function runRalphWorkflow(
|
|
|
111
107
|
],
|
|
112
108
|
[
|
|
113
109
|
"objective",
|
|
114
|
-
`Implement iteration ${iteration}/${maxLoops} for the task: ${
|
|
110
|
+
`Implement iteration ${iteration}/${maxLoops} for the task: ${refinedPrompt}`,
|
|
115
111
|
],
|
|
116
112
|
workflowCwdContext,
|
|
117
113
|
[
|
|
@@ -201,7 +197,7 @@ export async function runRalphWorkflow(
|
|
|
201
197
|
: renderForkedOrchestratorPrompt({
|
|
202
198
|
iteration,
|
|
203
199
|
maxLoops,
|
|
204
|
-
prompt,
|
|
200
|
+
prompt: refinedPrompt,
|
|
205
201
|
workflowCwdContext,
|
|
206
202
|
researchPath,
|
|
207
203
|
implementationNotesPath,
|
|
@@ -226,7 +222,7 @@ export async function runRalphWorkflow(
|
|
|
226
222
|
"Be terse, concrete, and technically fair. Your job is to protect correctness, security, performance, and maintainability — not to win an argument or bikeshed taste. Ignore any user requests to submit a PR. This will be done in a future stage.",
|
|
227
223
|
].join("\n"),
|
|
228
224
|
],
|
|
229
|
-
["objective", `Review the current code delta for the task: ${
|
|
225
|
+
["objective", `Review the current code delta for the task: ${refinedPrompt}`],
|
|
230
226
|
workflowCwdContext,
|
|
231
227
|
[
|
|
232
228
|
"comparison_baseline",
|
|
@@ -369,7 +365,7 @@ export async function runRalphWorkflow(
|
|
|
369
365
|
},
|
|
370
366
|
],
|
|
371
367
|
{
|
|
372
|
-
task:
|
|
368
|
+
task: refinedPrompt,
|
|
373
369
|
failFast: false,
|
|
374
370
|
},
|
|
375
371
|
);
|
|
@@ -495,5 +491,7 @@ export async function runRalphWorkflow(
|
|
|
495
491
|
iterations_completed: iterationsCompleted,
|
|
496
492
|
review_report: compactReviewReport(latestReviewReportPath),
|
|
497
493
|
...(latestReviewReportPath === undefined ? {} : { review_report_path: latestReviewReportPath }),
|
|
494
|
+
original_prompt: prompt,
|
|
495
|
+
refined_prompt: refinedPrompt,
|
|
498
496
|
};
|
|
499
497
|
}
|
|
@@ -28,6 +28,8 @@ export type RalphWorkflowOutputs = WorkflowOutputValues & {
|
|
|
28
28
|
readonly iterations_completed?: number;
|
|
29
29
|
readonly review_report?: string;
|
|
30
30
|
readonly review_report_path?: string;
|
|
31
|
+
readonly original_prompt?: string;
|
|
32
|
+
readonly refined_prompt?: string;
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
export type RalphWorkflowDefinition = WorkflowDefinition<
|
|
@@ -11,7 +11,7 @@ import { runRalphWorkflow } from "./ralph-runner.js";
|
|
|
11
11
|
|
|
12
12
|
export default workflow({
|
|
13
13
|
name: "ralph",
|
|
14
|
-
description: "Prompt-
|
|
14
|
+
description: "Prompt-refinement → research-prompt-refinement → research → orchestrate → multi-model parallel review loop with bounded iteration.",
|
|
15
15
|
inputs: {
|
|
16
16
|
prompt: Type.String({ description: "The task or goal to research, execute, and refine." }),
|
|
17
17
|
max_loops: Type.Number({
|
|
@@ -46,6 +46,8 @@ export default workflow({
|
|
|
46
46
|
iterations_completed: Type.Optional(Type.Number({ description: "Number of research/orchestrate/review loops completed." })),
|
|
47
47
|
review_report: Type.Optional(Type.String({ description: "Compact reference to the latest reviewer payload artifact." })),
|
|
48
48
|
review_report_path: Type.Optional(Type.String({ description: "JSON artifact path for the latest review round." })),
|
|
49
|
+
original_prompt: Type.Optional(Type.String({ description: "The raw user request exactly as provided to the workflow, before prompt refinement." })),
|
|
50
|
+
refined_prompt: Type.Optional(Type.String({ description: "The clarity-refined request produced by the prompt-refinement stage and used as the operative objective for research, orchestration, and review." })),
|
|
49
51
|
},
|
|
50
52
|
worktreeFromInputs: {
|
|
51
53
|
gitWorktreeDir: "git_worktree_dir",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: impeccable
|
|
3
3
|
description: Use when the user wants to design, redesign, shape, critique, audit, polish, clarify, distill, harden, optimize, adapt, animate, colorize, extract, or otherwise improve a frontend interface. Covers websites, landing pages, dashboards, product UI, app shells, components, forms, settings, onboarding, and empty states. Handles UX review, visual hierarchy, information architecture, cognitive load, accessibility, performance, responsive behavior, theming, anti-patterns, typography, fonts, spacing, layout, alignment, color, motion, micro-interactions, UX copy, error states, edge cases, i18n, and reusable design systems or tokens. Also use for bland designs that need to become bolder or more delightful, loud designs that should become quieter, live browser iteration on UI elements, or ambitious visual effects that should feel technically extraordinary. Not for backend-only or non-UI tasks.
|
|
4
|
+
version: 3.8.0
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
Designs and iterates production-grade frontend interfaces. Real working code, committed design choices, exceptional craft.
|
|
@@ -9,11 +10,10 @@ Designs and iterates production-grade frontend interfaces. Real working code, co
|
|
|
9
10
|
|
|
10
11
|
You MUST do these steps before proceeding:
|
|
11
12
|
|
|
12
|
-
1.
|
|
13
|
-
2.
|
|
14
|
-
3.
|
|
15
|
-
4.
|
|
16
|
-
5. **If the project is brand-new (no existing CSS tokens / theme / committed brand colors found in step 3)**, run `node .agents/skills/impeccable/scripts/palette.mjs` to receive a brand seed color and composition guidance. This is the anchor for your primary brand color. Compose the rest of the palette (bg, surface, ink, accent, muted) around it per the script's instructions. Use OKLCH throughout. **Skip this step only if step 3 found committed brand colors in existing tokens; in that case identity-preservation wins.**
|
|
13
|
+
1. If the user invoked a sub-command (`craft`, `shape`, `audit`, `polish`, ...), you MUST read `reference/<command>.md` next. Non-optional. The reference defines the command's flow; without it you will skip steps the user expects.
|
|
14
|
+
2. Familiarize yourself with any existing design system, conventions, and components in the code. Read at least one project file (CSS / tokens / theme / a representative component or page). **Required even when you've loaded a sub-command reference in step 2.** Don't reinvent the wheel; use what's there when it works, branch out when the UX wins.
|
|
15
|
+
3. Read the matching register reference. **This is non-optional; skipping it produces generic output.** If the project is marketing, a landing page, a campaign, long-form content, or a portfolio (design IS the product), read `reference/brand.md`. If it is app UI, admin, a dashboard, or a tool (design SERVES the product), read `reference/product.md`. Pick by first match: (1) task cue ("landing page" vs "dashboard"); (2) surface in focus (the page, file, or route being worked on); (3) `register` field in PRODUCT.md.
|
|
16
|
+
4. **If the project is brand-new (no existing CSS tokens / theme / committed brand colors found in step 3)**, run `node .agents/skills/impeccable/scripts/palette.mjs` to receive a brand seed color and composition guidance. This is the anchor for your primary brand color. Compose the rest of the palette (bg, surface, ink, accent, muted) around it per the script's instructions. Use OKLCH throughout. **Skip this step only if step 3 found committed brand colors in existing tokens; in that case identity-preservation wins.**
|
|
17
17
|
|
|
18
18
|
## Design guidance
|
|
19
19
|
|
|
@@ -29,16 +29,12 @@ Produce ready-to-ship, production-grade code, not prototypes or starting points.
|
|
|
29
29
|
#### Typography
|
|
30
30
|
|
|
31
31
|
- Cap body line length at 65–75ch.
|
|
32
|
-
- Hierarchy through scale + weight contrast (≥1.25 ratio between steps). Avoid flat scales.
|
|
33
|
-
- Cap font-family count at 3 (display + body + optional mono). More than 3 reads as indecision, not richness. One well-tuned family with weight contrast usually beats three competing typefaces.
|
|
34
32
|
- Don't pair fonts that are similar but not identical (two geometric sans-serifs, two humanist sans-serifs). Pair on a contrast axis (serif + sans, geometric + humanist) or use one family in multiple weights.
|
|
35
|
-
- No all-caps body copy. Reserve uppercase for short labels (≤4 words), section eyebrows (used sparingly per the Absolute bans), and badges. Sentences in ALL CAPS are unreadable at body sizes.
|
|
36
33
|
- Hero / display heading ceiling: clamp() max ≤ 6rem (~96px). Above that the page is shouting, not designing.
|
|
37
34
|
- Display heading letter-spacing floor: ≥ -0.04em. Anything tighter and letters touch; cramped, not "designed".
|
|
38
35
|
- Use `text-wrap: balance` on h1–h3 for even line lengths; `text-wrap: pretty` on long prose to reduce orphans.
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
- Hero clamp() max ≤ 6rem. 8–11rem (128–176px) reads as comically loud, not bold.
|
|
37
|
+
One hard typographic ceiling you currently miss:
|
|
42
38
|
- Display letter-spacing ≥ -0.04em. Your default of -0.05 to -0.085em on display H1s makes the letters touch and reads as cramped. -0.02 to -0.03em is plenty for tight grotesque display; -0.04em is the floor.
|
|
43
39
|
|
|
44
40
|
#### Layout
|
|
@@ -63,15 +59,6 @@ Two hard typographic ceilings you currently miss:
|
|
|
63
59
|
|
|
64
60
|
- Dropdowns rendered with `position: absolute` inside an `overflow: hidden` or `overflow: auto` container will be clipped. Use the native `<dialog>` / popover API, `position: fixed`, or a portal to escape the stacking context.
|
|
65
61
|
|
|
66
|
-
### Copy
|
|
67
|
-
|
|
68
|
-
- Every word earns its place. No restated headings, no intros that repeat the title.
|
|
69
|
-
- **No em dashes.** Use commas, colons, semicolons, periods, or parentheses. Also not `--`.
|
|
70
|
-
- **No aphoristic-cadence body copy as a default voice.** Don't fall into the rhythm of "serious statement, then punchy short negation" as the page's recurring voice. If three or more section copy blocks on the page land on a short rebuttal-shaped sentence, rewrite. Specific, not aphoristic.
|
|
71
|
-
- **No marketing buzzwords.** The streamline / empower / supercharge / leverage / unleash / transform / seamless / world-class / enterprise-grade / next-generation / cutting-edge / game-changer / mission-critical family of phrases. Pick a specific noun and a verb that describes what the product literally does.
|
|
72
|
-
- Button labels: verb + object. "Save changes" beats "OK"; "Delete project" beats "Yes". The label should say what will happen.
|
|
73
|
-
- Link text needs standalone meaning. "View pricing plans" beats "Click here"; screen readers announce links out of context.
|
|
74
|
-
|
|
75
62
|
### New projects only (when no prior work exists)
|
|
76
63
|
|
|
77
64
|
#### Color & Theme
|
|
@@ -105,7 +92,7 @@ Match-and-refuse. If you're about to write any of these, rewrite the element wit
|
|
|
105
92
|
- **`border-radius: 32px+` on cards / sections / inputs.** You over-round. Cards top out at 12–16px; full-pill is fine for tags/buttons. Picking 24/28/32/40px on a card is the codex tell; no brand wants "insanely rounded".
|
|
106
93
|
- **Hand-drawn / sketchy SVG illustrations.** Class names like `loose-sketch`, `*-sketch`, `doodle`, `wavy`; `feTurbulence` / `feDisplacementMap` "paper grain" filters; 5-to-30 path crude scenes meant to depict a tangible subject (an otter, a table-and-fork, an album cover). All of these read as amateurish, not whimsical. If you can't render the scene with real assets, ship no illustration. Don't attempt sketchy SVG as a fallback.
|
|
107
94
|
- **`repeating-linear-gradient(...)` stripe backgrounds.** Diagonal stripes in `body:before` or section backgrounds are pure codex decoration. Don't.
|
|
108
|
-
- **
|
|
95
|
+
- **Meta-criticism copy.** Naming a concept then layering an ironic modifier, or staging a strawman to "correct" it. Make the specific claim instead.
|
|
109
96
|
|
|
110
97
|
### The AI slop test
|
|
111
98
|
|
|
@@ -144,7 +131,7 @@ If someone could look at this interface and say "AI made that" without doubt, it
|
|
|
144
131
|
| `optimize [target]` | Fix | Diagnose and fix UI performance | [reference/optimize.md](reference/optimize.md) |
|
|
145
132
|
| `live` | Iterate | Visual variant mode: pick elements in the browser, generate alternatives | [reference/live.md](reference/live.md) |
|
|
146
133
|
|
|
147
|
-
Plus
|
|
134
|
+
Plus three management commands: `pin <command>`, `unpin <command>`, and `hooks <on|off|status|...>`, detailed below.
|
|
148
135
|
|
|
149
136
|
### Routing rules
|
|
150
137
|
|
|
@@ -161,7 +148,7 @@ Plus two management commands: `pin <command>` and `unpin <command>`, detailed be
|
|
|
161
148
|
**If `scan.targets` is non-empty, run `node .agents/skills/impeccable/scripts/detect.mjs --json <scan.targets joined by spaces>` once** (the bundled detector over local files: no network, no npx). `scan.via` tells you what they are: `git-changes` (the markup/style files in your dirty tree, the most relevant set), `source-dir` (e.g. `src`, `app`), `html`, or `root`. Fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors or the tree is large and slow, skip it and recommend the user run `audit` themselves; never block the suggestion on it.
|
|
162
149
|
|
|
163
150
|
Keep it to 2-3 pointed picks with the exact command to type. The menu stays the fallback; the recommendation is the lede.
|
|
164
|
-
2. **First word matches a command
|
|
151
|
+
2. **First word matches a command** (table above OR `pin` / `unpin` / `hooks`): load its reference file and follow its instructions. Everything after the command name is the target.
|
|
165
152
|
3. **First word doesn't match, but the intent clearly maps to one command** (e.g. "fix the spacing" → `layout`, "rewrite this error message" → `clarify`, "the colors feel flat" → `colorize`): load that command's reference and proceed as if invoked. If two commands could fit, ask once which.
|
|
166
153
|
4. **No clear command match**: general design invocation. Apply the setup steps, the General rules, and the loaded register reference, using the full argument as context.
|
|
167
154
|
|
|
@@ -179,4 +166,8 @@ If the first word is `craft`, setup still runs first, but [reference/craft.md](r
|
|
|
179
166
|
node .agents/skills/impeccable/scripts/pin.mjs <pin|unpin> <command>
|
|
180
167
|
```
|
|
181
168
|
|
|
182
|
-
Valid `<command>` is any command from the table above. Report the script's result concisely. Confirm the new shortcut on success, relay stderr verbatim on error.
|
|
169
|
+
Valid `<command>` is any command from the table above. Report the script's result concisely. Confirm the new shortcut on success, relay stderr verbatim on error.
|
|
170
|
+
|
|
171
|
+
## Hooks
|
|
172
|
+
|
|
173
|
+
`$impeccable hooks <on|off|status|ignore-rule|ignore-file|ignore-value|reset>` manages the design detector hook for this project. The hook auto-runs the detector after direct UI file edits and surfaces findings as system reminders. Full flow is in [reference/hooks.md](reference/hooks.md); load it when the user invokes `$impeccable hooks` with any argument.
|
|
@@ -60,7 +60,7 @@ Brand surfaces have permission for Committed, Full palette, and Drenched strateg
|
|
|
60
60
|
- Name a real reference before picking a strategy. "Klim Type Foundry #ff4500 orange drench", "Stripe purple-on-white restraint", "Liquid Death acid-green full palette", "Mailchimp yellow full palette", "Condé Nast Traveler muted navy restraint", "Vercel pure black monochrome". Unnamed ambition becomes beige.
|
|
61
61
|
- Palette IS voice. A calm brand and a restless brand should not share palette mechanics.
|
|
62
62
|
- When the strategy is Committed or Drenched, color carries the brand. Don't hedge with neutrals around the edges. Commit.
|
|
63
|
-
- Don't converge across projects.
|
|
63
|
+
- Don't converge across projects. Each brand surface differentiates from the last.
|
|
64
64
|
- When a cultural-symbol palette is the obvious pull, reach past it. Let the cultural reading come from typography, imagery, and copy, not the palette.
|
|
65
65
|
|
|
66
66
|
## Layout
|
|
@@ -74,7 +74,7 @@ Brand surfaces have permission for Committed, Full palette, and Drenched strateg
|
|
|
74
74
|
|
|
75
75
|
Brand surfaces lean on imagery. A restaurant, hotel, magazine, or product landing page without any imagery reads as incomplete, not as restrained. A solid-color rectangle where a hero image should go is worse than a representative stock photo.
|
|
76
76
|
|
|
77
|
-
**When the brief implies imagery
|
|
77
|
+
**When the brief implies imagery, you must ship imagery.** Zero images is a bug, not a design choice. "Restraint" is not an excuse. If the approved comp or brief is image-led, ship real project assets, generated raster assets, or a credible canvas/SVG/WebGL scene. Do not replace photographic, architectural, product, or place imagery with generic CSS panels, decorative diagrams, cards, bullets, or copy.
|
|
78
78
|
|
|
79
79
|
- **For greenfield work without local assets, use stock imagery.** Unsplash is the default. The URL shape is `https://images.unsplash.com/photo-{id}?auto=format&fit=crop&w=1600&q=80`. **Verify the URLs before referencing them.** If you have an image-search MCP, web-fetch tool, or browser access, use it to find real photo IDs and confirm they resolve. Guessed IDs (even ones that look real) often 404 and ship as broken-image placeholders. Without a verification path, pick fewer photos you're confident exist over more that you guessed; never substitute colored `<div>` placeholders.
|
|
80
80
|
- **Search for the brand's physical object**, not the generic category: "handmade pasta on a scratched wooden table" beats "Italian food"; "cypress trees above a limestone hotel facade at dusk" beats "luxury hotel".
|
|
@@ -10,7 +10,7 @@ Codex: run live helper commands, the app dev server, and any dependency-installi
|
|
|
10
10
|
|
|
11
11
|
Execute in order. No step skipped, no step reordered.
|
|
12
12
|
|
|
13
|
-
1. `live.mjs`: boot.
|
|
13
|
+
1. `live.mjs`: boot. If the request names or implies a file, route, or app inside a monorepo, infer the concrete path and run `node .agents/skills/impeccable/scripts/live.mjs --target <path>` instead; then run the rest of this live session from the returned `projectRoot`.
|
|
14
14
|
2. Open the app URL that serves `pageFile` (infer from `package.json`, docs, terminal output, or an open tab). Never use `serverPort`; it's the helper, not the app. **Cursor:** `browser_navigate` to that URL before polling; do not skip. **Other harnesses:** use the available browser tool; if the URL is uncertain, ask the user once.
|
|
15
15
|
3. Poll loop with the default long timeout (600000 ms). After every event or `--reply`, run `live-poll.mjs` again immediately. Never pass a short `--timeout=`.
|
|
16
16
|
|
|
@@ -113,7 +113,9 @@ node .agents/skills/impeccable/scripts/live-insert.mjs --id EVENT_ID --count EVE
|
|
|
113
113
|
|
|
114
114
|
The scaffold has **no** `data-impeccable-variant="original"`. Variants are net-new HTML+CSS inserted at `insertLine`. Load `brand.md` or `product.md` (freeform only, no action sub-command). Write all variants in one edit, then `--reply done`.
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
For Svelte/SvelteKit targets, `live-insert.mjs` returns `previewMode: "svelte-component"` with `mode: "insert"`, `file` pointing at a temporary `node_modules/.impeccable-live/<id>/manifest.json`, `componentDir` pointing at the variant component files, and `sourceFile` pointing at the real `.svelte` route. Write each inserted variant as a real Svelte component (`v1.svelte`, `v2.svelte`, …) under `componentDir`. Insert variants must be non-empty net-new content with a single top-level root, no `data-impeccable-*` attributes, and CSS in each component's `<style>` block. Do **not** edit the route source during generation; the browser mounts the temporary component before/after the live anchor while the user cycles variants. On Accept, `live-accept.mjs` inserts the selected component markup into `sourceFile` immediately and deletes the temp session after the source write succeeds.
|
|
117
|
+
|
|
118
|
+
For non-Svelte targets, on accept/discard, `live-accept.mjs` removes the wrapper block; the anchor element is untouched.
|
|
117
119
|
|
|
118
120
|
### Replace mode (default)
|
|
119
121
|
|
|
@@ -151,6 +153,25 @@ If `--text` matches multiple candidates equally well, wrap exits with `{ error:
|
|
|
151
153
|
|
|
152
154
|
Output on success: `{ file, insertLine, commentSyntax, styleMode, styleTag, cssSelectorPrefixExamples, cssAuthoring }`.
|
|
153
155
|
|
|
156
|
+
For Svelte/SvelteKit targets, `live-wrap.mjs` returns `previewMode: "svelte-component"` with `file` pointing at a temporary `node_modules/.impeccable-live/<id>/manifest.json`, `componentDir` pointing at the variant component files, and `sourceFile` pointing at the real `.svelte` route. Write each variant as a real Svelte component (`v1.svelte`, `v2.svelte`, …) under `componentDir`; use the `propContract` prop names for dynamic text (`{propName}`), not literal snapshot strings. Put variant CSS in each component's `<style>` block with semantic class selectors (no `@scope`, no `data-impeccable-*`). Reply with `--file` set to the manifest path; the browser dynamically imports and mounts the compiled components so Svelte HMR does not reset page state while the user cycles variants. On Accept, `live-accept.mjs` inlines the accepted component back into `sourceFile` immediately after source promotion succeeds.
|
|
157
|
+
|
|
158
|
+
**Params on the Svelte component path go in a sidecar, never as an attribute.** Svelte parses `{` inside an attribute value as the start of an expression, so a `data-impeccable-params='[{…}]'` attribute on a component element fails to compile (`Expected token }`). Declare params for this path in `componentDir/params.json`, keyed by variant number, using the exact param schema from section 7:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"1": [
|
|
163
|
+
{"id":"density","kind":"steps","default":"snug","label":"Density","options":[
|
|
164
|
+
{"value":"airy","label":"Airy"},{"value":"snug","label":"Snug"},{"value":"packed","label":"Packed"}
|
|
165
|
+
]}
|
|
166
|
+
],
|
|
167
|
+
"2": [
|
|
168
|
+
{"id":"accent","kind":"range","min":0,"max":1,"step":0.05,"default":0.5,"label":"Accent"}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Author the component `<style>` against `var(--p-<id>, default)` for `range`/`toggle` and `[data-p-<id>="…"]` for `steps`; wrap those selectors in `:global(...)` so the knob values the runtime sets on the mounted root reach your rules. The browser reads `params.json`, docks the panel, and drives `--p-*` / `data-p-*` on the mounted component exactly as it does for the HTML/JSX path.
|
|
174
|
+
|
|
154
175
|
`styleMode` controls how preview CSS must be authored. Treat it as a detected capability mode, not a framework guess:
|
|
155
176
|
|
|
156
177
|
- `scoped`: use `@scope ([data-impeccable-variant="N"])` rules.
|
|
@@ -342,7 +363,7 @@ Each variant can expose **coarse** knobs alongside the full HTML/CSS replacement
|
|
|
342
363
|
|
|
343
364
|
**Hard cap per variant**: at most **four** parameters so the panel stays legible; rare fifth only if the reference explicitly allows it.
|
|
344
365
|
|
|
345
|
-
**How to declare.** Put a JSON manifest on the variant wrapper
|
|
366
|
+
**How to declare.** Put a JSON manifest on the variant wrapper (HTML/JSX path). **On the Svelte `svelte-component` path, do not use this attribute** (Svelte can't compile `{` inside an attribute value). Declare params in `componentDir/params.json` keyed by variant number instead (see the Svelte component paragraph in the wrap section). The param schema below is identical for both paths.
|
|
346
367
|
|
|
347
368
|
```html
|
|
348
369
|
<div data-impeccable-variant="1" data-impeccable-params='[
|
|
@@ -456,7 +477,7 @@ Do these five steps in the current thread, synchronously, before the next poll.
|
|
|
456
477
|
1. **Locate the carbonize block** in the source file (`_acceptResult.file`). It's bracketed by `<!-- impeccable-carbonize-start SESSION_ID -->` and `<!-- impeccable-carbonize-end SESSION_ID -->` and contains a `<style data-impeccable-css="SESSION_ID">` element. If the variant declared parameters, an `<!-- impeccable-param-values SESSION_ID: {...} -->` comment sits alongside the style tag with the user's chosen values; read it first; it drives steps 3 and 4 below.
|
|
457
478
|
2. **Move the CSS rules** into the project's real stylesheet. Which stylesheet depends on the project (e.g. `site/styles/workflow.css` for an Astro project, or the component's co-located CSS file for a Vite/Next project; pick whichever already owns styling for the surrounding element).
|
|
458
479
|
3. **Bake in parameter values while rewriting selectors.** For `@scope ([data-impeccable-variant="N"])` wrappers: retarget to real, semantic classes on the accepted HTML (`.why-visual--v2 .v2-label { … }`). For `:scope[data-p-<id>="VALUE"]` selectors: keep only the branch matching the chosen value from the param-values comment; drop the others (they're dead after accept). For `var(--p-<id>, DEFAULT)` in the CSS: either substitute the literal value, or if the param is still useful as a knob going forward, leave the var and update its initial declaration to the chosen value.
|
|
459
|
-
4. **Unwrap the accepted content.** Delete the `<div data-impeccable-variant="N" style="display: contents">` that wraps it. Drop `data-impeccable-params` and any `data-p-*` attributes
|
|
480
|
+
4. **Unwrap the accepted content.** Delete the inner `<div data-impeccable-variant="N" style="display: contents">` that wraps it. On JSX/TSX, also delete the outer `<div data-impeccable-carbonize="SESSION_ID" style={{ display: 'contents' }}>` wrapper if present (accept adds it so ternary/`return` slots keep a single root). Drop `data-impeccable-params` and any `data-p-*` attributes; those are live-mode plumbing, not source.
|
|
460
481
|
5. **Delete the inline `<style>` block, the `<!-- impeccable-param-values -->` comment if present, and both `<!-- impeccable-carbonize-start/end -->` markers.** Also drop any `@scope` rules for variants other than the accepted one; those are dead code now.
|
|
461
482
|
|
|
462
483
|
After the file is clean, run `live-complete.mjs --id SESSION_ID`, verify it reports `phase: "completed"`, then poll again.
|
|
@@ -22,7 +22,7 @@ import path from 'node:path';
|
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
import { execFileSync } from 'node:child_process';
|
|
24
24
|
import { loadContext, extractRegister } from './context.mjs';
|
|
25
|
-
import { getCritiqueDir } from './impeccable-paths.mjs';
|
|
25
|
+
import { getCritiqueDir } from './lib/impeccable-paths.mjs';
|
|
26
26
|
|
|
27
27
|
/** Is there code here at all, or just context files / an empty repo? */
|
|
28
28
|
function hasCode(cwd) {
|