@bastani/atomic 0.5.25-0 → 0.5.26-0
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/.agents/skills/ado-commit/SKILL.md +92 -0
- package/.agents/skills/ado-create-pr/SKILL.md +209 -0
- package/.claude/settings.json +1 -0
- package/.mcp.json +5 -0
- package/.opencode/opencode.json +7 -1
- package/README.md +150 -116
- package/assets/settings.schema.json +2 -2
- package/dist/sdk/runtime/executor.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/open-claude-design/claude/index.d.ts +46 -0
- package/dist/sdk/workflows/builtin/open-claude-design/claude/index.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/open-claude-design/copilot/index.d.ts +34 -0
- package/dist/sdk/workflows/builtin/open-claude-design/copilot/index.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/constants.d.ts +72 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/constants.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/design-system.d.ts +46 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/design-system.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/export.d.ts +32 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/export.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/import.d.ts +33 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/import.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/prompts.d.ts +106 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/prompts.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/scan.d.ts +50 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/scan.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/validation.d.ts +12 -0
- package/dist/sdk/workflows/builtin/open-claude-design/helpers/validation.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/open-claude-design/opencode/index.d.ts +36 -0
- package/dist/sdk/workflows/builtin/open-claude-design/opencode/index.d.ts.map +1 -0
- package/dist/services/config/atomic-config.d.ts +6 -0
- package/dist/services/config/atomic-config.d.ts.map +1 -1
- package/dist/services/config/scm-sync.d.ts +37 -0
- package/dist/services/config/scm-sync.d.ts.map +1 -0
- package/package.json +7 -7
- package/src/cli.ts +2 -2
- package/src/commands/cli/chat/index.ts +8 -1
- package/src/commands/cli/config.ts +34 -10
- package/src/commands/cli/init/index.ts +9 -0
- package/src/sdk/runtime/executor.ts +13 -0
- package/src/sdk/workflows/builtin/open-claude-design/claude/index.ts +499 -0
- package/src/sdk/workflows/builtin/open-claude-design/copilot/index.ts +507 -0
- package/src/sdk/workflows/builtin/open-claude-design/helpers/constants.ts +159 -0
- package/src/sdk/workflows/builtin/open-claude-design/helpers/design-system.ts +88 -0
- package/src/sdk/workflows/builtin/open-claude-design/helpers/export.ts +193 -0
- package/src/sdk/workflows/builtin/open-claude-design/helpers/import.ts +52 -0
- package/src/sdk/workflows/builtin/open-claude-design/helpers/prompts.ts +1110 -0
- package/src/sdk/workflows/builtin/open-claude-design/helpers/scan.ts +117 -0
- package/src/sdk/workflows/builtin/open-claude-design/helpers/validation.ts +38 -0
- package/src/sdk/workflows/builtin/open-claude-design/opencode/index.ts +570 -0
- package/src/services/config/atomic-config.ts +12 -0
- package/src/services/config/scm-sync.ts +174 -0
- package/src/services/config/settings.ts +19 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* open-claude-design / claude
|
|
3
|
+
*
|
|
4
|
+
* An open-source replica of Anthropic's Claude Design product, implemented
|
|
5
|
+
* as an Atomic CLI workflow. Orchestrates the design skill ecosystem
|
|
6
|
+
* (impeccable, critique, shape, polish, audit, etc.) into a deterministic
|
|
7
|
+
* 5-phase pipeline:
|
|
8
|
+
*
|
|
9
|
+
* Phase 1: Design System Onboarding — extract tokens, build Design.md (HIL)
|
|
10
|
+
* Phase 2: Import — aggregate text/URL/file references
|
|
11
|
+
* Phase 3: Generation — produce first design version
|
|
12
|
+
* Phase 4: Refinement Loop — bounded iterate with critique + user feedback
|
|
13
|
+
* Phase 5: Export/Handoff — HTML export + Claude Code handoff bundle
|
|
14
|
+
*
|
|
15
|
+
* Topology:
|
|
16
|
+
*
|
|
17
|
+
* ┌─→ ds-locator (headless) ∥ ds-analyzer (headless) ∥ ds-patterns (headless)
|
|
18
|
+
* │ │
|
|
19
|
+
* │ ▼
|
|
20
|
+
* │ design-system-builder (visible, HIL)
|
|
21
|
+
* │ │
|
|
22
|
+
* │ ▼
|
|
23
|
+
* │ web-capture (headless, if URL) ∥ file-parser (headless, if file)
|
|
24
|
+
* │ │
|
|
25
|
+
* │ ▼
|
|
26
|
+
* │ generator (visible)
|
|
27
|
+
* │ │
|
|
28
|
+
* │ ▼
|
|
29
|
+
* │ ┌─→ user-feedback-i (visible, HIL)
|
|
30
|
+
* │ │ │
|
|
31
|
+
* │ │ ├─→ critique-i (headless) ∥ screenshot-i (headless)
|
|
32
|
+
* │ │ │
|
|
33
|
+
* │ │ ▼
|
|
34
|
+
* │ │ apply-changes-i (visible)
|
|
35
|
+
* │ │ │
|
|
36
|
+
* │ └─────────┘ (loop until approved or MAX_REFINEMENTS)
|
|
37
|
+
* │ │
|
|
38
|
+
* │ ▼
|
|
39
|
+
* │ exporter (visible)
|
|
40
|
+
* └──────────────────────────────────────
|
|
41
|
+
*
|
|
42
|
+
* Run: atomic workflow -n open-claude-design -a claude "design a dashboard"
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import { defineWorkflow, extractAssistantText } from "../../../index.ts";
|
|
46
|
+
import { mkdir } from "node:fs/promises";
|
|
47
|
+
import path from "node:path";
|
|
48
|
+
|
|
49
|
+
import {
|
|
50
|
+
MAX_REFINEMENTS,
|
|
51
|
+
HEADLESS_OPTS,
|
|
52
|
+
DESIGNS_DIR,
|
|
53
|
+
} from "../helpers/constants.ts";
|
|
54
|
+
import {
|
|
55
|
+
loadDesignSystem,
|
|
56
|
+
persistDesignSystem,
|
|
57
|
+
readImpeccableMd,
|
|
58
|
+
slugifyPrompt,
|
|
59
|
+
ensureScratchDir,
|
|
60
|
+
} from "../helpers/design-system.ts";
|
|
61
|
+
import {
|
|
62
|
+
isUrl,
|
|
63
|
+
isFilePath,
|
|
64
|
+
aggregateImportResults,
|
|
65
|
+
} from "../helpers/import.ts";
|
|
66
|
+
import { isRefinementComplete } from "../helpers/validation.ts";
|
|
67
|
+
import { writeHandoffBundle } from "../helpers/export.ts";
|
|
68
|
+
import {
|
|
69
|
+
hasBlockingFindings,
|
|
70
|
+
renderScanFindings,
|
|
71
|
+
runImpeccableScan,
|
|
72
|
+
} from "../helpers/scan.ts";
|
|
73
|
+
import {
|
|
74
|
+
buildDesignLocatorPrompt,
|
|
75
|
+
buildDesignAnalyzerPrompt,
|
|
76
|
+
buildDesignPatternPrompt,
|
|
77
|
+
buildDesignSystemBuilderPrompt,
|
|
78
|
+
buildWebCapturePrompt,
|
|
79
|
+
buildFileParserPrompt,
|
|
80
|
+
buildGeneratorPrompt,
|
|
81
|
+
buildRefineFeedbackPrompt,
|
|
82
|
+
buildCritiquePrompt,
|
|
83
|
+
buildScreenshotValidationPrompt,
|
|
84
|
+
buildApplyChangesPrompt,
|
|
85
|
+
buildForcedFixPrompt,
|
|
86
|
+
buildExportPrompt,
|
|
87
|
+
} from "../helpers/prompts.ts";
|
|
88
|
+
|
|
89
|
+
export default defineWorkflow({
|
|
90
|
+
name: "open-claude-design",
|
|
91
|
+
description:
|
|
92
|
+
"AI-powered design workflow: design system onboarding → import → generate → refine → export/handoff",
|
|
93
|
+
inputs: [
|
|
94
|
+
{
|
|
95
|
+
name: "prompt",
|
|
96
|
+
type: "text",
|
|
97
|
+
required: true,
|
|
98
|
+
description:
|
|
99
|
+
"What to design (e.g., 'a dashboard for monitoring API latency')",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "reference",
|
|
103
|
+
type: "text",
|
|
104
|
+
required: false,
|
|
105
|
+
description:
|
|
106
|
+
"URL, file path, or codebase path to import as design reference",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "output-type",
|
|
110
|
+
type: "enum",
|
|
111
|
+
required: false,
|
|
112
|
+
values: ["prototype", "wireframe", "page", "component"],
|
|
113
|
+
default: "prototype",
|
|
114
|
+
description: "Type of design output to generate",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "design-system",
|
|
118
|
+
type: "text",
|
|
119
|
+
required: false,
|
|
120
|
+
description: "Path to existing Design.md (skips onboarding if provided)",
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
})
|
|
124
|
+
.for<"claude">()
|
|
125
|
+
.run(async (ctx) => {
|
|
126
|
+
const prompt = ctx.inputs.prompt ?? "";
|
|
127
|
+
const reference = ctx.inputs.reference ?? "";
|
|
128
|
+
const outputType = ctx.inputs["output-type"] ?? "prototype";
|
|
129
|
+
const designSystemPath = ctx.inputs["design-system"] ?? "";
|
|
130
|
+
|
|
131
|
+
const root = process.cwd();
|
|
132
|
+
const slug = slugifyPrompt(prompt);
|
|
133
|
+
const isoDate = new Date().toISOString().slice(0, 10);
|
|
134
|
+
const scratchDir = await ensureScratchDir(root);
|
|
135
|
+
const designDir = path.join(scratchDir, slug);
|
|
136
|
+
await mkdir(designDir, { recursive: true });
|
|
137
|
+
|
|
138
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
139
|
+
// PHASE 1: Design System Onboarding
|
|
140
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
141
|
+
|
|
142
|
+
let designSystem;
|
|
143
|
+
|
|
144
|
+
if (designSystemPath.trim()) {
|
|
145
|
+
// Skip onboarding — user provided an existing Design.md
|
|
146
|
+
designSystem = await loadDesignSystem(designSystemPath);
|
|
147
|
+
} else {
|
|
148
|
+
// Layer 1: Parallel headless codebase analysis
|
|
149
|
+
const [locator, analyzer, patterns] = await Promise.all([
|
|
150
|
+
ctx.stage(
|
|
151
|
+
{
|
|
152
|
+
name: "ds-locator",
|
|
153
|
+
headless: true,
|
|
154
|
+
description: "Locate design files and tokens",
|
|
155
|
+
},
|
|
156
|
+
{},
|
|
157
|
+
{},
|
|
158
|
+
async (s) => {
|
|
159
|
+
const result = await s.session.query(
|
|
160
|
+
buildDesignLocatorPrompt({ root }),
|
|
161
|
+
{ agent: "codebase-locator", ...HEADLESS_OPTS },
|
|
162
|
+
);
|
|
163
|
+
s.save(s.sessionId);
|
|
164
|
+
return extractAssistantText(result, 0);
|
|
165
|
+
},
|
|
166
|
+
),
|
|
167
|
+
ctx.stage(
|
|
168
|
+
{
|
|
169
|
+
name: "ds-analyzer",
|
|
170
|
+
headless: true,
|
|
171
|
+
description: "Analyze design tokens and patterns",
|
|
172
|
+
},
|
|
173
|
+
{},
|
|
174
|
+
{},
|
|
175
|
+
async (s) => {
|
|
176
|
+
const result = await s.session.query(
|
|
177
|
+
buildDesignAnalyzerPrompt({ root }),
|
|
178
|
+
{ agent: "codebase-analyzer", ...HEADLESS_OPTS },
|
|
179
|
+
);
|
|
180
|
+
s.save(s.sessionId);
|
|
181
|
+
return extractAssistantText(result, 0);
|
|
182
|
+
},
|
|
183
|
+
),
|
|
184
|
+
ctx.stage(
|
|
185
|
+
{
|
|
186
|
+
name: "ds-patterns",
|
|
187
|
+
headless: true,
|
|
188
|
+
description: "Find existing design patterns",
|
|
189
|
+
},
|
|
190
|
+
{},
|
|
191
|
+
{},
|
|
192
|
+
async (s) => {
|
|
193
|
+
const result = await s.session.query(
|
|
194
|
+
buildDesignPatternPrompt({ root }),
|
|
195
|
+
{ agent: "codebase-pattern-finder", ...HEADLESS_OPTS },
|
|
196
|
+
);
|
|
197
|
+
s.save(s.sessionId);
|
|
198
|
+
return extractAssistantText(result, 0);
|
|
199
|
+
},
|
|
200
|
+
),
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
// Layer 2: Visible stage with HIL — presents findings, asks user to
|
|
204
|
+
// approve/modify each design element category
|
|
205
|
+
await ctx.stage(
|
|
206
|
+
{
|
|
207
|
+
name: "design-system-builder",
|
|
208
|
+
description: "Build design system with user approval (HIL)",
|
|
209
|
+
},
|
|
210
|
+
{},
|
|
211
|
+
{},
|
|
212
|
+
async (s) => {
|
|
213
|
+
await s.session.query(
|
|
214
|
+
buildDesignSystemBuilderPrompt({
|
|
215
|
+
root,
|
|
216
|
+
locatorOutput: locator.result,
|
|
217
|
+
analyzerOutput: analyzer.result,
|
|
218
|
+
patternsOutput: patterns.result,
|
|
219
|
+
existingImpeccable: await readImpeccableMd(root),
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
s.save(s.sessionId);
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Deterministic: read back the Design.md the agent wrote
|
|
227
|
+
designSystem = await persistDesignSystem(root);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
231
|
+
// PHASE 2: Import
|
|
232
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
233
|
+
|
|
234
|
+
const importResults = await Promise.all([
|
|
235
|
+
// Web capture (only if reference is a URL)
|
|
236
|
+
isUrl(reference)
|
|
237
|
+
? ctx.stage(
|
|
238
|
+
{
|
|
239
|
+
name: "web-capture",
|
|
240
|
+
headless: true,
|
|
241
|
+
description: "Capture web reference via playwright",
|
|
242
|
+
},
|
|
243
|
+
{},
|
|
244
|
+
{},
|
|
245
|
+
async (s) => {
|
|
246
|
+
const result = await s.session.query(
|
|
247
|
+
buildWebCapturePrompt({ url: reference, screenshotDir: scratchDir }),
|
|
248
|
+
{ agent: "codebase-online-researcher", ...HEADLESS_OPTS },
|
|
249
|
+
);
|
|
250
|
+
s.save(s.sessionId);
|
|
251
|
+
return extractAssistantText(result, 0);
|
|
252
|
+
},
|
|
253
|
+
)
|
|
254
|
+
: null,
|
|
255
|
+
|
|
256
|
+
// File parser (only if reference is a file path)
|
|
257
|
+
isFilePath(reference)
|
|
258
|
+
? ctx.stage(
|
|
259
|
+
{
|
|
260
|
+
name: "file-parser",
|
|
261
|
+
headless: true,
|
|
262
|
+
description: "Parse reference document",
|
|
263
|
+
},
|
|
264
|
+
{},
|
|
265
|
+
{},
|
|
266
|
+
async (s) => {
|
|
267
|
+
const result = await s.session.query(
|
|
268
|
+
buildFileParserPrompt({ filePath: reference }),
|
|
269
|
+
{ agent: "codebase-analyzer", ...HEADLESS_OPTS },
|
|
270
|
+
);
|
|
271
|
+
s.save(s.sessionId);
|
|
272
|
+
return extractAssistantText(result, 0);
|
|
273
|
+
},
|
|
274
|
+
)
|
|
275
|
+
: null,
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
// Deterministic aggregation
|
|
279
|
+
const importContext = aggregateImportResults({
|
|
280
|
+
prompt,
|
|
281
|
+
reference,
|
|
282
|
+
webCapture: importResults[0]?.result ?? null,
|
|
283
|
+
fileParse: importResults[1]?.result ?? null,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
287
|
+
// PHASE 3: Generation
|
|
288
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
289
|
+
|
|
290
|
+
await ctx.stage(
|
|
291
|
+
{ name: "generator", description: "Generate first design version" },
|
|
292
|
+
{},
|
|
293
|
+
{},
|
|
294
|
+
async (s) => {
|
|
295
|
+
await s.session.query(
|
|
296
|
+
buildGeneratorPrompt({
|
|
297
|
+
prompt,
|
|
298
|
+
outputType,
|
|
299
|
+
designSystem,
|
|
300
|
+
importContext,
|
|
301
|
+
root,
|
|
302
|
+
outputDir: designDir,
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
s.save(s.sessionId);
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
310
|
+
// PHASE 4: Refinement Loop
|
|
311
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
312
|
+
|
|
313
|
+
for (let iteration = 1; iteration <= MAX_REFINEMENTS; iteration++) {
|
|
314
|
+
// Step 1: Collect user feedback via HIL
|
|
315
|
+
const feedback = await ctx.stage(
|
|
316
|
+
{
|
|
317
|
+
name: `user-feedback-${iteration}`,
|
|
318
|
+
description: `Collect refinement feedback (iteration ${iteration})`,
|
|
319
|
+
},
|
|
320
|
+
{},
|
|
321
|
+
{},
|
|
322
|
+
async (s) => {
|
|
323
|
+
const result = await s.session.query(
|
|
324
|
+
buildRefineFeedbackPrompt({
|
|
325
|
+
prompt,
|
|
326
|
+
designDir,
|
|
327
|
+
iteration,
|
|
328
|
+
maxIterations: MAX_REFINEMENTS,
|
|
329
|
+
}),
|
|
330
|
+
{ ...HEADLESS_OPTS },
|
|
331
|
+
);
|
|
332
|
+
s.save(s.sessionId);
|
|
333
|
+
return extractAssistantText(result, 0);
|
|
334
|
+
},
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// Check if user signaled "done" via AskUserQuestion response
|
|
338
|
+
if (isRefinementComplete(feedback.result)) break;
|
|
339
|
+
|
|
340
|
+
// Step 2: Parallel validation — critique + screenshot
|
|
341
|
+
const [critiqueResult, screenshotResult] = await Promise.all([
|
|
342
|
+
ctx.stage(
|
|
343
|
+
{
|
|
344
|
+
name: `critique-${iteration}`,
|
|
345
|
+
headless: true,
|
|
346
|
+
description: `Design critique (iteration ${iteration})`,
|
|
347
|
+
},
|
|
348
|
+
{},
|
|
349
|
+
{},
|
|
350
|
+
async (s) => {
|
|
351
|
+
const result = await s.session.query(
|
|
352
|
+
buildCritiquePrompt({
|
|
353
|
+
designDir,
|
|
354
|
+
designSystem,
|
|
355
|
+
userFeedback: feedback.result,
|
|
356
|
+
}),
|
|
357
|
+
{ agent: "reviewer", ...HEADLESS_OPTS },
|
|
358
|
+
);
|
|
359
|
+
s.save(s.sessionId);
|
|
360
|
+
return extractAssistantText(result, 0);
|
|
361
|
+
},
|
|
362
|
+
),
|
|
363
|
+
ctx.stage(
|
|
364
|
+
{
|
|
365
|
+
name: `screenshot-${iteration}`,
|
|
366
|
+
headless: true,
|
|
367
|
+
description: `Visual validation (iteration ${iteration})`,
|
|
368
|
+
},
|
|
369
|
+
{},
|
|
370
|
+
{},
|
|
371
|
+
async (s) => {
|
|
372
|
+
const result = await s.session.query(
|
|
373
|
+
buildScreenshotValidationPrompt({ designDir, scratchDir }),
|
|
374
|
+
{ agent: "codebase-analyzer", ...HEADLESS_OPTS },
|
|
375
|
+
);
|
|
376
|
+
s.save(s.sessionId);
|
|
377
|
+
return extractAssistantText(result, 0);
|
|
378
|
+
},
|
|
379
|
+
),
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
// Step 3: Deterministic scan — surface banned anti-patterns to the
|
|
383
|
+
// agent so apply-changes can fix them alongside user feedback. No LLM
|
|
384
|
+
// call; runs the `impeccable detect` CLI directly.
|
|
385
|
+
const scan = await runImpeccableScan(designDir);
|
|
386
|
+
const scanFindings =
|
|
387
|
+
scan.available && scan.findings.length > 0
|
|
388
|
+
? renderScanFindings(scan.findings)
|
|
389
|
+
: scan.available
|
|
390
|
+
? ""
|
|
391
|
+
: `(scanner unavailable: ${scan.reason} — proceed without scan input)`;
|
|
392
|
+
|
|
393
|
+
// Step 4: Apply changes based on feedback + critique + scanner findings
|
|
394
|
+
await ctx.stage(
|
|
395
|
+
{
|
|
396
|
+
name: `apply-changes-${iteration}`,
|
|
397
|
+
description: `Apply refinements (iteration ${iteration})`,
|
|
398
|
+
},
|
|
399
|
+
{},
|
|
400
|
+
{},
|
|
401
|
+
async (s) => {
|
|
402
|
+
await s.session.query(
|
|
403
|
+
buildApplyChangesPrompt({
|
|
404
|
+
prompt,
|
|
405
|
+
designDir,
|
|
406
|
+
designSystem,
|
|
407
|
+
userFeedback: feedback.result,
|
|
408
|
+
critiqueOutput: critiqueResult.result,
|
|
409
|
+
screenshotOutput: screenshotResult.result,
|
|
410
|
+
scanFindings,
|
|
411
|
+
iteration,
|
|
412
|
+
}),
|
|
413
|
+
);
|
|
414
|
+
s.save(s.sessionId);
|
|
415
|
+
},
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
420
|
+
// Hard enforcement gate — runs before export, independent of the
|
|
421
|
+
// refinement loop's exit condition. Guarantees no design ships with
|
|
422
|
+
// scanner findings even if the user approved early or MAX_REFINEMENTS
|
|
423
|
+
// was reached with the agent still introducing banned patterns.
|
|
424
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
425
|
+
|
|
426
|
+
const preExportScan = await runImpeccableScan(designDir);
|
|
427
|
+
if (hasBlockingFindings(preExportScan)) {
|
|
428
|
+
// TS narrowing: hasBlockingFindings guarantees available === true
|
|
429
|
+
const findings = (
|
|
430
|
+
preExportScan as Extract<typeof preExportScan, { available: true }>
|
|
431
|
+
).findings;
|
|
432
|
+
const findingsText = renderScanFindings(findings);
|
|
433
|
+
|
|
434
|
+
await ctx.stage(
|
|
435
|
+
{
|
|
436
|
+
name: "forced-fix",
|
|
437
|
+
description: "Remove banned anti-patterns before export",
|
|
438
|
+
},
|
|
439
|
+
{},
|
|
440
|
+
{},
|
|
441
|
+
async (s) => {
|
|
442
|
+
await s.session.query(
|
|
443
|
+
buildForcedFixPrompt({
|
|
444
|
+
designDir,
|
|
445
|
+
designSystem,
|
|
446
|
+
scanFindings: findingsText,
|
|
447
|
+
}),
|
|
448
|
+
);
|
|
449
|
+
s.save(s.sessionId);
|
|
450
|
+
},
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
const rescan = await runImpeccableScan(designDir);
|
|
454
|
+
if (hasBlockingFindings(rescan)) {
|
|
455
|
+
const remaining = (
|
|
456
|
+
rescan as Extract<typeof rescan, { available: true }>
|
|
457
|
+
).findings;
|
|
458
|
+
throw new Error(
|
|
459
|
+
`open-claude-design: export blocked — ${remaining.length} ` +
|
|
460
|
+
`banned anti-pattern(s) remain after forced fix:\n` +
|
|
461
|
+
renderScanFindings(remaining),
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
467
|
+
// PHASE 5: Export / Handoff
|
|
468
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
469
|
+
|
|
470
|
+
const finalDesignDir = path.join(root, DESIGNS_DIR, `${isoDate}-${slug}`);
|
|
471
|
+
|
|
472
|
+
await ctx.stage(
|
|
473
|
+
{ name: "exporter", description: "Export design and create handoff bundle" },
|
|
474
|
+
{},
|
|
475
|
+
{},
|
|
476
|
+
async (s) => {
|
|
477
|
+
await s.session.query(
|
|
478
|
+
buildExportPrompt({
|
|
479
|
+
prompt,
|
|
480
|
+
designDir,
|
|
481
|
+
finalDesignDir,
|
|
482
|
+
designSystem,
|
|
483
|
+
outputType,
|
|
484
|
+
}),
|
|
485
|
+
{ ...HEADLESS_OPTS },
|
|
486
|
+
);
|
|
487
|
+
s.save(s.sessionId);
|
|
488
|
+
},
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
// Deterministic: package handoff bundle (copies Design.md, writes
|
|
492
|
+
// handoff-prompt.md and README.md — no LLM call)
|
|
493
|
+
await writeHandoffBundle(finalDesignDir, {
|
|
494
|
+
designSystem,
|
|
495
|
+
prompt,
|
|
496
|
+
outputType,
|
|
497
|
+
});
|
|
498
|
+
})
|
|
499
|
+
.compile();
|