@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.
Files changed (51) hide show
  1. package/.agents/skills/ado-commit/SKILL.md +92 -0
  2. package/.agents/skills/ado-create-pr/SKILL.md +209 -0
  3. package/.claude/settings.json +1 -0
  4. package/.mcp.json +5 -0
  5. package/.opencode/opencode.json +7 -1
  6. package/README.md +150 -116
  7. package/assets/settings.schema.json +2 -2
  8. package/dist/sdk/runtime/executor.d.ts.map +1 -1
  9. package/dist/sdk/workflows/builtin/open-claude-design/claude/index.d.ts +46 -0
  10. package/dist/sdk/workflows/builtin/open-claude-design/claude/index.d.ts.map +1 -0
  11. package/dist/sdk/workflows/builtin/open-claude-design/copilot/index.d.ts +34 -0
  12. package/dist/sdk/workflows/builtin/open-claude-design/copilot/index.d.ts.map +1 -0
  13. package/dist/sdk/workflows/builtin/open-claude-design/helpers/constants.d.ts +72 -0
  14. package/dist/sdk/workflows/builtin/open-claude-design/helpers/constants.d.ts.map +1 -0
  15. package/dist/sdk/workflows/builtin/open-claude-design/helpers/design-system.d.ts +46 -0
  16. package/dist/sdk/workflows/builtin/open-claude-design/helpers/design-system.d.ts.map +1 -0
  17. package/dist/sdk/workflows/builtin/open-claude-design/helpers/export.d.ts +32 -0
  18. package/dist/sdk/workflows/builtin/open-claude-design/helpers/export.d.ts.map +1 -0
  19. package/dist/sdk/workflows/builtin/open-claude-design/helpers/import.d.ts +33 -0
  20. package/dist/sdk/workflows/builtin/open-claude-design/helpers/import.d.ts.map +1 -0
  21. package/dist/sdk/workflows/builtin/open-claude-design/helpers/prompts.d.ts +106 -0
  22. package/dist/sdk/workflows/builtin/open-claude-design/helpers/prompts.d.ts.map +1 -0
  23. package/dist/sdk/workflows/builtin/open-claude-design/helpers/scan.d.ts +50 -0
  24. package/dist/sdk/workflows/builtin/open-claude-design/helpers/scan.d.ts.map +1 -0
  25. package/dist/sdk/workflows/builtin/open-claude-design/helpers/validation.d.ts +12 -0
  26. package/dist/sdk/workflows/builtin/open-claude-design/helpers/validation.d.ts.map +1 -0
  27. package/dist/sdk/workflows/builtin/open-claude-design/opencode/index.d.ts +36 -0
  28. package/dist/sdk/workflows/builtin/open-claude-design/opencode/index.d.ts.map +1 -0
  29. package/dist/services/config/atomic-config.d.ts +6 -0
  30. package/dist/services/config/atomic-config.d.ts.map +1 -1
  31. package/dist/services/config/scm-sync.d.ts +37 -0
  32. package/dist/services/config/scm-sync.d.ts.map +1 -0
  33. package/package.json +7 -7
  34. package/src/cli.ts +2 -2
  35. package/src/commands/cli/chat/index.ts +8 -1
  36. package/src/commands/cli/config.ts +34 -10
  37. package/src/commands/cli/init/index.ts +9 -0
  38. package/src/sdk/runtime/executor.ts +13 -0
  39. package/src/sdk/workflows/builtin/open-claude-design/claude/index.ts +499 -0
  40. package/src/sdk/workflows/builtin/open-claude-design/copilot/index.ts +507 -0
  41. package/src/sdk/workflows/builtin/open-claude-design/helpers/constants.ts +159 -0
  42. package/src/sdk/workflows/builtin/open-claude-design/helpers/design-system.ts +88 -0
  43. package/src/sdk/workflows/builtin/open-claude-design/helpers/export.ts +193 -0
  44. package/src/sdk/workflows/builtin/open-claude-design/helpers/import.ts +52 -0
  45. package/src/sdk/workflows/builtin/open-claude-design/helpers/prompts.ts +1110 -0
  46. package/src/sdk/workflows/builtin/open-claude-design/helpers/scan.ts +117 -0
  47. package/src/sdk/workflows/builtin/open-claude-design/helpers/validation.ts +38 -0
  48. package/src/sdk/workflows/builtin/open-claude-design/opencode/index.ts +570 -0
  49. package/src/services/config/atomic-config.ts +12 -0
  50. package/src/services/config/scm-sync.ts +174 -0
  51. 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();