arggon-harness 0.1.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 (170) hide show
  1. package/README.md +229 -0
  2. package/config/default-opencode.json +21 -0
  3. package/dist/init.d.ts +3 -0
  4. package/dist/init.d.ts.map +1 -0
  5. package/dist/init.js +406 -0
  6. package/dist/init.js.map +1 -0
  7. package/dist/plugin/engine/artifact-graph.d.ts +38 -0
  8. package/dist/plugin/engine/artifact-graph.d.ts.map +1 -0
  9. package/dist/plugin/engine/artifact-graph.js +137 -0
  10. package/dist/plugin/engine/artifact-graph.js.map +1 -0
  11. package/dist/plugin/engine/config.d.ts +21 -0
  12. package/dist/plugin/engine/config.d.ts.map +1 -0
  13. package/dist/plugin/engine/config.js +146 -0
  14. package/dist/plugin/engine/config.js.map +1 -0
  15. package/dist/plugin/engine/delta-apply.d.ts +23 -0
  16. package/dist/plugin/engine/delta-apply.d.ts.map +1 -0
  17. package/dist/plugin/engine/delta-apply.js +153 -0
  18. package/dist/plugin/engine/delta-apply.js.map +1 -0
  19. package/dist/plugin/engine/git.d.ts +32 -0
  20. package/dist/plugin/engine/git.d.ts.map +1 -0
  21. package/dist/plugin/engine/git.js +61 -0
  22. package/dist/plugin/engine/git.js.map +1 -0
  23. package/dist/plugin/engine/integrity.d.ts +45 -0
  24. package/dist/plugin/engine/integrity.d.ts.map +1 -0
  25. package/dist/plugin/engine/integrity.js +98 -0
  26. package/dist/plugin/engine/integrity.js.map +1 -0
  27. package/dist/plugin/engine/registry.d.ts +39 -0
  28. package/dist/plugin/engine/registry.d.ts.map +1 -0
  29. package/dist/plugin/engine/registry.js +191 -0
  30. package/dist/plugin/engine/registry.js.map +1 -0
  31. package/dist/plugin/engine/state.d.ts +31 -0
  32. package/dist/plugin/engine/state.d.ts.map +1 -0
  33. package/dist/plugin/engine/state.js +116 -0
  34. package/dist/plugin/engine/state.js.map +1 -0
  35. package/dist/plugin/engine/task-tracker.d.ts +66 -0
  36. package/dist/plugin/engine/task-tracker.d.ts.map +1 -0
  37. package/dist/plugin/engine/task-tracker.js +124 -0
  38. package/dist/plugin/engine/task-tracker.js.map +1 -0
  39. package/dist/plugin/engine/types.d.ts +349 -0
  40. package/dist/plugin/engine/types.d.ts.map +1 -0
  41. package/dist/plugin/engine/types.js +3 -0
  42. package/dist/plugin/engine/types.js.map +1 -0
  43. package/dist/plugin/engine/validator.d.ts +52 -0
  44. package/dist/plugin/engine/validator.d.ts.map +1 -0
  45. package/dist/plugin/engine/validator.js +457 -0
  46. package/dist/plugin/engine/validator.js.map +1 -0
  47. package/dist/plugin/engine/verifier.d.ts +61 -0
  48. package/dist/plugin/engine/verifier.d.ts.map +1 -0
  49. package/dist/plugin/engine/verifier.js +441 -0
  50. package/dist/plugin/engine/verifier.js.map +1 -0
  51. package/dist/plugin/hooks/context-injection.d.ts +11 -0
  52. package/dist/plugin/hooks/context-injection.d.ts.map +1 -0
  53. package/dist/plugin/hooks/context-injection.js +88 -0
  54. package/dist/plugin/hooks/context-injection.js.map +1 -0
  55. package/dist/plugin/hooks/event-handlers.d.ts +9 -0
  56. package/dist/plugin/hooks/event-handlers.d.ts.map +1 -0
  57. package/dist/plugin/hooks/event-handlers.js +10 -0
  58. package/dist/plugin/hooks/event-handlers.js.map +1 -0
  59. package/dist/plugin/hooks/workflow-gate.d.ts +24 -0
  60. package/dist/plugin/hooks/workflow-gate.d.ts.map +1 -0
  61. package/dist/plugin/hooks/workflow-gate.js +78 -0
  62. package/dist/plugin/hooks/workflow-gate.js.map +1 -0
  63. package/dist/plugin/index.d.ts +423 -0
  64. package/dist/plugin/index.d.ts.map +1 -0
  65. package/dist/plugin/index.js +253 -0
  66. package/dist/plugin/index.js.map +1 -0
  67. package/dist/plugin/tools/spec-artifact-instr.d.ts +7 -0
  68. package/dist/plugin/tools/spec-artifact-instr.d.ts.map +1 -0
  69. package/dist/plugin/tools/spec-artifact-instr.js +73 -0
  70. package/dist/plugin/tools/spec-artifact-instr.js.map +1 -0
  71. package/dist/plugin/tools/spec-change-archive.d.ts +6 -0
  72. package/dist/plugin/tools/spec-change-archive.d.ts.map +1 -0
  73. package/dist/plugin/tools/spec-change-archive.js +62 -0
  74. package/dist/plugin/tools/spec-change-archive.js.map +1 -0
  75. package/dist/plugin/tools/spec-change-list.d.ts +3 -0
  76. package/dist/plugin/tools/spec-change-list.d.ts.map +1 -0
  77. package/dist/plugin/tools/spec-change-list.js +38 -0
  78. package/dist/plugin/tools/spec-change-list.js.map +1 -0
  79. package/dist/plugin/tools/spec-change-new.d.ts +7 -0
  80. package/dist/plugin/tools/spec-change-new.d.ts.map +1 -0
  81. package/dist/plugin/tools/spec-change-new.js +47 -0
  82. package/dist/plugin/tools/spec-change-new.js.map +1 -0
  83. package/dist/plugin/tools/spec-change-status.d.ts +6 -0
  84. package/dist/plugin/tools/spec-change-status.d.ts.map +1 -0
  85. package/dist/plugin/tools/spec-change-status.js +43 -0
  86. package/dist/plugin/tools/spec-change-status.js.map +1 -0
  87. package/dist/plugin/tools/spec-design-critique.d.ts +20 -0
  88. package/dist/plugin/tools/spec-design-critique.d.ts.map +1 -0
  89. package/dist/plugin/tools/spec-design-critique.js +412 -0
  90. package/dist/plugin/tools/spec-design-critique.js.map +1 -0
  91. package/dist/plugin/tools/spec-design-hifi.d.ts +119 -0
  92. package/dist/plugin/tools/spec-design-hifi.d.ts.map +1 -0
  93. package/dist/plugin/tools/spec-design-hifi.js +653 -0
  94. package/dist/plugin/tools/spec-design-hifi.js.map +1 -0
  95. package/dist/plugin/tools/spec-design-wireframe.d.ts +91 -0
  96. package/dist/plugin/tools/spec-design-wireframe.d.ts.map +1 -0
  97. package/dist/plugin/tools/spec-design-wireframe.js +357 -0
  98. package/dist/plugin/tools/spec-design-wireframe.js.map +1 -0
  99. package/dist/plugin/tools/spec-init.d.ts +9 -0
  100. package/dist/plugin/tools/spec-init.d.ts.map +1 -0
  101. package/dist/plugin/tools/spec-init.js +58 -0
  102. package/dist/plugin/tools/spec-init.js.map +1 -0
  103. package/dist/plugin/tools/spec-integrity-check.d.ts +6 -0
  104. package/dist/plugin/tools/spec-integrity-check.d.ts.map +1 -0
  105. package/dist/plugin/tools/spec-integrity-check.js +19 -0
  106. package/dist/plugin/tools/spec-integrity-check.js.map +1 -0
  107. package/dist/plugin/tools/spec-registry-update.d.ts +3 -0
  108. package/dist/plugin/tools/spec-registry-update.d.ts.map +1 -0
  109. package/dist/plugin/tools/spec-registry-update.js +34 -0
  110. package/dist/plugin/tools/spec-registry-update.js.map +1 -0
  111. package/dist/plugin/tools/spec-schema-list.d.ts +3 -0
  112. package/dist/plugin/tools/spec-schema-list.d.ts.map +1 -0
  113. package/dist/plugin/tools/spec-schema-list.js +28 -0
  114. package/dist/plugin/tools/spec-schema-list.js.map +1 -0
  115. package/dist/plugin/tools/spec-specs-apply.d.ts +7 -0
  116. package/dist/plugin/tools/spec-specs-apply.d.ts.map +1 -0
  117. package/dist/plugin/tools/spec-specs-apply.js +49 -0
  118. package/dist/plugin/tools/spec-specs-apply.js.map +1 -0
  119. package/dist/plugin/tools/spec-task-progress.d.ts +8 -0
  120. package/dist/plugin/tools/spec-task-progress.d.ts.map +1 -0
  121. package/dist/plugin/tools/spec-task-progress.js +96 -0
  122. package/dist/plugin/tools/spec-task-progress.js.map +1 -0
  123. package/dist/plugin/tools/spec-validate.d.ts +21 -0
  124. package/dist/plugin/tools/spec-validate.d.ts.map +1 -0
  125. package/dist/plugin/tools/spec-validate.js +182 -0
  126. package/dist/plugin/tools/spec-validate.js.map +1 -0
  127. package/dist/plugin/tools/spec-verify.d.ts +7 -0
  128. package/dist/plugin/tools/spec-verify.d.ts.map +1 -0
  129. package/dist/plugin/tools/spec-verify.js +50 -0
  130. package/dist/plugin/tools/spec-verify.js.map +1 -0
  131. package/dist/plugin/tools/util.d.ts +25 -0
  132. package/dist/plugin/tools/util.d.ts.map +1 -0
  133. package/dist/plugin/tools/util.js +33 -0
  134. package/dist/plugin/tools/util.js.map +1 -0
  135. package/package.json +61 -0
  136. package/src/agents/orchestrator.md +158 -0
  137. package/src/agents/spec-apply.md +114 -0
  138. package/src/agents/spec-archive.md +103 -0
  139. package/src/agents/spec-propose.md +120 -0
  140. package/src/agents/spec-verify.md +103 -0
  141. package/src/commands/spec-init.md +6 -0
  142. package/src/commands/spec-onboard.md +6 -0
  143. package/src/commands/spec-status.md +6 -0
  144. package/src/commands/spec-sync.md +6 -0
  145. package/src/schemas/hybrid.yaml +144 -0
  146. package/src/schemas/spec-driven.yaml +155 -0
  147. package/src/schemas/tdd.yaml +203 -0
  148. package/src/skills/playwright-cli/SKILL.md +388 -0
  149. package/src/skills/playwright-cli/references/element-attributes.md +23 -0
  150. package/src/skills/playwright-cli/references/playwright-tests.md +39 -0
  151. package/src/skills/playwright-cli/references/request-mocking.md +87 -0
  152. package/src/skills/playwright-cli/references/running-code.md +241 -0
  153. package/src/skills/playwright-cli/references/session-management.md +225 -0
  154. package/src/skills/playwright-cli/references/spec-driven-testing.md +305 -0
  155. package/src/skills/playwright-cli/references/storage-state.md +275 -0
  156. package/src/skills/playwright-cli/references/test-generation.md +134 -0
  157. package/src/skills/playwright-cli/references/tracing.md +139 -0
  158. package/src/skills/playwright-cli/references/video-recording.md +143 -0
  159. package/src/skills/spec-init/SKILL.md +61 -0
  160. package/src/skills/spec-onboard/SKILL.md +178 -0
  161. package/src/skills/spec-status/SKILL.md +72 -0
  162. package/src/skills/spec-sync/SKILL.md +63 -0
  163. package/src/templates/config.yaml +14 -0
  164. package/src/templates/design-hifi.yaml +580 -0
  165. package/src/templates/design-tech.yaml +42 -0
  166. package/src/templates/design-wireframe.yaml +114 -0
  167. package/src/templates/proposal.yaml +43 -0
  168. package/src/templates/registry.yaml +3 -0
  169. package/src/templates/spec.yaml +56 -0
  170. package/src/templates/tasks.yaml +58 -0
@@ -0,0 +1,653 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import { z } from "zod";
4
+ // ── Helpers ──────────────────────────────────────────────────────────
5
+ const hexColorRegex = /^#[0-9a-fA-F]{3,8}$/;
6
+ function isValidHex(val) {
7
+ return hexColorRegex.test(val);
8
+ }
9
+ // ── Zod schemas ──────────────────────────────────────────────────────
10
+ const ColorGroupSchema = z.object({
11
+ base: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
12
+ hover: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }).optional(),
13
+ active: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }).optional(),
14
+ disabled: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }).optional(),
15
+ contrast: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }).optional(),
16
+ });
17
+ const SemanticColorsSchema = z.object({
18
+ success: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
19
+ warning: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
20
+ error: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
21
+ info: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
22
+ });
23
+ const SurfaceColorsSchema = z.object({
24
+ background: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
25
+ foreground: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
26
+ card: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
27
+ border: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
28
+ muted: z.string().refine(isValidHex, { message: "Must be a valid hex color (#RRGGBB)" }),
29
+ });
30
+ const ColorPaletteSchema = z.object({
31
+ primary: ColorGroupSchema,
32
+ secondary: ColorGroupSchema,
33
+ accent: ColorGroupSchema,
34
+ neutral: ColorGroupSchema,
35
+ semantic: SemanticColorsSchema,
36
+ surfaces: SurfaceColorsSchema,
37
+ });
38
+ const FontFamilySchema = z.object({
39
+ name: z.string().min(1),
40
+ stack: z.string().min(1),
41
+ role: z.enum(["heading", "body", "monospace"]),
42
+ });
43
+ const TypeScaleSchema = z.object({
44
+ name: z.string().min(1),
45
+ size: z.string().min(1),
46
+ weight: z.number().min(100).max(900),
47
+ line_height: z.number().min(0.5).max(3),
48
+ letter_spacing: z.string().optional(),
49
+ role: z.string().min(1),
50
+ });
51
+ const TypographySchema = z.object({
52
+ families: z.array(FontFamilySchema).min(1),
53
+ scale: z.array(TypeScaleSchema).min(1),
54
+ });
55
+ const SpacingSchema = z.object({
56
+ base_unit: z.number().min(1).max(32),
57
+ scale: z.array(z.number().min(0)),
58
+ });
59
+ const ComponentStateSchema = z.object({
60
+ name: z.string().min(1),
61
+ properties: z.record(z.string(), z.any()),
62
+ });
63
+ const ComponentVariantSchema = z.object({
64
+ name: z.string().min(1),
65
+ description: z.string().optional(),
66
+ properties: z.record(z.string(), z.any()).optional(),
67
+ states: z.array(ComponentStateSchema).optional(),
68
+ });
69
+ const ComponentLibrarySchema = z.object({
70
+ button: z.array(ComponentVariantSchema).optional(),
71
+ card: z.array(ComponentVariantSchema).optional(),
72
+ input: z.array(ComponentVariantSchema).optional(),
73
+ navigation: z.array(ComponentVariantSchema).optional(),
74
+ });
75
+ const BreakpointSchema = z.object({
76
+ name: z.string().min(1),
77
+ min_width: z.number().min(0),
78
+ columns: z.number().min(1).max(24),
79
+ rules: z.array(z.string()).optional(),
80
+ });
81
+ const DesignHiFiInputSchema = z.object({
82
+ changeName: z.string().min(1),
83
+ color_palette: ColorPaletteSchema,
84
+ typography: TypographySchema,
85
+ spacing: SpacingSchema,
86
+ component_library: ComponentLibrarySchema.optional(),
87
+ breakpoints: z.array(BreakpointSchema).optional(),
88
+ html_preview: z.boolean().optional().default(true),
89
+ });
90
+ // ── Exported handler ─────────────────────────────────────────────────
91
+ export async function specDesignHiFi(args, ctx) {
92
+ try {
93
+ // Validate input via Zod
94
+ const parsed = DesignHiFiInputSchema.parse(args);
95
+ const { changeName, color_palette, typography, spacing, component_library, breakpoints, html_preview } = parsed;
96
+ const changeDir = path.join(ctx.projectRoot, "spec", "changes", changeName);
97
+ // Verify change directory exists
98
+ try {
99
+ await fs.access(changeDir);
100
+ }
101
+ catch {
102
+ return {
103
+ output: `Error: Change "${changeName}" does not exist. Use spec_change_new to create it first.`,
104
+ metadata: { error: true, changeName },
105
+ };
106
+ }
107
+ // Build artifact content matching the template structure
108
+ const artifact = {
109
+ id: changeName,
110
+ schema: "spec-driven",
111
+ agent: {
112
+ context_files: [],
113
+ affected_paths: [`spec/changes/${changeName}/design-hifi.yaml`],
114
+ },
115
+ body: {
116
+ color_palette: {
117
+ primary: {
118
+ base: color_palette.primary.base,
119
+ ...(color_palette.primary.hover ? { hover: color_palette.primary.hover } : {}),
120
+ ...(color_palette.primary.active ? { active: color_palette.primary.active } : {}),
121
+ ...(color_palette.primary.disabled ? { disabled: color_palette.primary.disabled } : {}),
122
+ ...(color_palette.primary.contrast ? { contrast: color_palette.primary.contrast } : {}),
123
+ },
124
+ secondary: {
125
+ base: color_palette.secondary.base,
126
+ ...(color_palette.secondary.hover ? { hover: color_palette.secondary.hover } : {}),
127
+ ...(color_palette.secondary.active ? { active: color_palette.secondary.active } : {}),
128
+ ...(color_palette.secondary.disabled ? { disabled: color_palette.secondary.disabled } : {}),
129
+ ...(color_palette.secondary.contrast ? { contrast: color_palette.secondary.contrast } : {}),
130
+ },
131
+ accent: {
132
+ base: color_palette.accent.base,
133
+ ...(color_palette.accent.hover ? { hover: color_palette.accent.hover } : {}),
134
+ ...(color_palette.accent.active ? { active: color_palette.accent.active } : {}),
135
+ ...(color_palette.accent.disabled ? { disabled: color_palette.accent.disabled } : {}),
136
+ ...(color_palette.accent.contrast ? { contrast: color_palette.accent.contrast } : {}),
137
+ },
138
+ neutral: {
139
+ base: color_palette.neutral.base,
140
+ ...(color_palette.neutral.hover ? { hover: color_palette.neutral.hover } : {}),
141
+ ...(color_palette.neutral.active ? { active: color_palette.neutral.active } : {}),
142
+ ...(color_palette.neutral.disabled ? { disabled: color_palette.neutral.disabled } : {}),
143
+ ...(color_palette.neutral.contrast ? { contrast: color_palette.neutral.contrast } : {}),
144
+ },
145
+ semantic: {
146
+ success: color_palette.semantic.success,
147
+ warning: color_palette.semantic.warning,
148
+ error: color_palette.semantic.error,
149
+ info: color_palette.semantic.info,
150
+ },
151
+ surfaces: {
152
+ background: color_palette.surfaces.background,
153
+ foreground: color_palette.surfaces.foreground,
154
+ card: color_palette.surfaces.card,
155
+ border: color_palette.surfaces.border,
156
+ muted: color_palette.surfaces.muted,
157
+ },
158
+ },
159
+ typography: {
160
+ families: typography.families.map((f) => ({
161
+ name: f.name,
162
+ stack: f.stack,
163
+ role: f.role,
164
+ })),
165
+ scale: typography.scale.map((t) => ({
166
+ name: t.name,
167
+ size: t.size,
168
+ weight: t.weight,
169
+ line_height: t.line_height,
170
+ ...(t.letter_spacing ? { letter_spacing: t.letter_spacing } : {}),
171
+ role: t.role,
172
+ })),
173
+ },
174
+ spacing: {
175
+ base_unit: spacing.base_unit,
176
+ scale: spacing.scale,
177
+ },
178
+ ...(component_library
179
+ ? {
180
+ component_library: {
181
+ ...(component_library.button
182
+ ? {
183
+ button: component_library.button.map((v) => ({
184
+ name: v.name,
185
+ ...(v.description ? { description: v.description } : {}),
186
+ ...(v.properties && Object.keys(v.properties).length > 0
187
+ ? { properties: v.properties }
188
+ : {}),
189
+ ...(v.states && v.states.length > 0
190
+ ? {
191
+ states: v.states.map((s) => ({
192
+ name: s.name,
193
+ properties: s.properties,
194
+ })),
195
+ }
196
+ : {}),
197
+ })),
198
+ }
199
+ : {}),
200
+ ...(component_library.card
201
+ ? {
202
+ card: component_library.card.map((v) => ({
203
+ name: v.name,
204
+ ...(v.description ? { description: v.description } : {}),
205
+ ...(v.properties && Object.keys(v.properties).length > 0
206
+ ? { properties: v.properties }
207
+ : {}),
208
+ ...(v.states && v.states.length > 0
209
+ ? {
210
+ states: v.states.map((s) => ({
211
+ name: s.name,
212
+ properties: s.properties,
213
+ })),
214
+ }
215
+ : {}),
216
+ })),
217
+ }
218
+ : {}),
219
+ ...(component_library.input
220
+ ? {
221
+ input: component_library.input.map((v) => ({
222
+ name: v.name,
223
+ ...(v.description ? { description: v.description } : {}),
224
+ ...(v.properties && Object.keys(v.properties).length > 0
225
+ ? { properties: v.properties }
226
+ : {}),
227
+ ...(v.states && v.states.length > 0
228
+ ? {
229
+ states: v.states.map((s) => ({
230
+ name: s.name,
231
+ properties: s.properties,
232
+ })),
233
+ }
234
+ : {}),
235
+ })),
236
+ }
237
+ : {}),
238
+ ...(component_library.navigation
239
+ ? {
240
+ navigation: component_library.navigation.map((v) => ({
241
+ name: v.name,
242
+ ...(v.description ? { description: v.description } : {}),
243
+ ...(v.properties && Object.keys(v.properties).length > 0
244
+ ? { properties: v.properties }
245
+ : {}),
246
+ ...(v.states && v.states.length > 0
247
+ ? {
248
+ states: v.states.map((s) => ({
249
+ name: s.name,
250
+ properties: s.properties,
251
+ })),
252
+ }
253
+ : {}),
254
+ })),
255
+ }
256
+ : {}),
257
+ },
258
+ }
259
+ : {}),
260
+ ...(breakpoints
261
+ ? {
262
+ breakpoints: breakpoints.map((b) => ({
263
+ name: b.name,
264
+ min_width: b.min_width,
265
+ columns: b.columns,
266
+ ...(b.rules && b.rules.length > 0 ? { rules: b.rules } : {}),
267
+ })),
268
+ }
269
+ : {}),
270
+ },
271
+ };
272
+ // Write YAML file
273
+ const yamlContent = buildYaml(artifact);
274
+ const outputPath = path.join(changeDir, "design-hifi.yaml");
275
+ await fs.writeFile(outputPath, yamlContent, "utf-8");
276
+ // Generate HTML preview if requested
277
+ let htmlPreview;
278
+ if (html_preview) {
279
+ htmlPreview = generateStyleGuideHtml(artifact);
280
+ const htmlPath = path.join(changeDir, "design-hifi-preview.html");
281
+ await fs.writeFile(htmlPath, htmlPreview, "utf-8");
282
+ }
283
+ return {
284
+ output: `Created hi-fi design artifact at spec/changes/${changeName}/design-hifi.yaml` +
285
+ (htmlPreview
286
+ ? `\nHTML style guide preview: spec/changes/${changeName}/design-hifi-preview.html`
287
+ : ""),
288
+ metadata: {
289
+ changeName,
290
+ artifactPath: `spec/changes/${changeName}/design-hifi.yaml`,
291
+ colorsCount: 6, // primary, secondary, accent, neutral, semantic, surfaces
292
+ typographyFamilies: typography.families.length,
293
+ typeScaleItems: typography.scale.length,
294
+ spacingScale: spacing.scale.length,
295
+ componentVariants: component_library
296
+ ? Object.values(component_library).reduce((sum, arr) => sum + (arr?.length || 0), 0)
297
+ : 0,
298
+ breakpointsCount: breakpoints?.length || 0,
299
+ htmlPreview: !!html_preview,
300
+ },
301
+ };
302
+ }
303
+ catch (error) {
304
+ if (error instanceof z.ZodError) {
305
+ const issues = error.issues
306
+ .map((i) => `${i.path.join(".")}: ${i.message}`)
307
+ .join("; ");
308
+ return {
309
+ output: `Validation error: ${issues}`,
310
+ metadata: { error: true, validationErrors: error.issues },
311
+ };
312
+ }
313
+ return {
314
+ output: `Error: ${error.message}`,
315
+ metadata: { error: true },
316
+ };
317
+ }
318
+ }
319
+ // ── YAML builder (pure string builder, no external deps) ────────────
320
+ function buildYaml(obj, indent = 0) {
321
+ const pad = " ".repeat(indent);
322
+ let result = "";
323
+ for (const [key, value] of Object.entries(obj)) {
324
+ if (value === undefined || value === null)
325
+ continue;
326
+ if (Array.isArray(value)) {
327
+ if (value.length === 0) {
328
+ result += `${pad}${key}: []\n`;
329
+ }
330
+ else {
331
+ result += `${pad}${key}:\n`;
332
+ for (const item of value) {
333
+ if (typeof item === "object" && item !== null) {
334
+ result += `${pad}- ${buildInlineYaml(item, indent + 1).trimStart()}\n`;
335
+ }
336
+ else {
337
+ result += `${pad}- ${formatYamlValue(item)}\n`;
338
+ }
339
+ }
340
+ }
341
+ }
342
+ else if (typeof value === "object" && value !== null) {
343
+ result += `${pad}${key}:\n`;
344
+ result += buildYaml(value, indent + 1);
345
+ }
346
+ else {
347
+ result += `${pad}${key}: ${formatYamlValue(value)}\n`;
348
+ }
349
+ }
350
+ return result;
351
+ }
352
+ function buildInlineYaml(obj, indent) {
353
+ const pad = " ".repeat(indent);
354
+ let result = "";
355
+ const entries = Object.entries(obj).filter(([_, v]) => v !== undefined && v !== null);
356
+ for (let i = 0; i < entries.length; i++) {
357
+ const [key, value] = entries[i];
358
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
359
+ result += `\n${pad}${key}:\n`;
360
+ result += buildYaml(value, indent + 1);
361
+ }
362
+ else if (Array.isArray(value)) {
363
+ if (value.length === 0) {
364
+ result += `${i > 0 ? pad : ""}${key}: []\n`;
365
+ }
366
+ else {
367
+ result += `${i > 0 ? pad : ""}${key}:\n`;
368
+ for (const item of value) {
369
+ if (typeof item === "object" && item !== null) {
370
+ result += `${pad}- ${buildInlineYaml(item, indent + 1).trimStart()}\n`;
371
+ }
372
+ else {
373
+ result += `${pad}- ${formatYamlValue(item)}\n`;
374
+ }
375
+ }
376
+ }
377
+ }
378
+ else {
379
+ const prefix = i > 0 && typeof entries[i - 1][1] === "object" ? pad : "";
380
+ result += `${prefix}${key}: ${formatYamlValue(value)}\n`;
381
+ }
382
+ }
383
+ return result;
384
+ }
385
+ function formatYamlValue(value) {
386
+ if (typeof value === "string") {
387
+ if (value.includes("\n")) {
388
+ return `|\n ${value.replace(/\n/g, "\n ")}`;
389
+ }
390
+ if (value.includes(":") || value.includes("#") || value === "" || value.startsWith(" ")) {
391
+ return `"${value}"`;
392
+ }
393
+ return value;
394
+ }
395
+ return String(value);
396
+ }
397
+ // ── HTML style guide preview generator ──────────────────────────────
398
+ function generateStyleGuideHtml(artifact) {
399
+ const body = artifact.body;
400
+ const palette = body.color_palette || {};
401
+ const typography = body.typography || {};
402
+ const spacing = body.spacing || {};
403
+ const components = body.component_library || {};
404
+ const breakpoints = body.breakpoints || [];
405
+ // ── Color swatches ─────────────────────────────────────────────────
406
+ function renderColorGroup(name, group) {
407
+ if (!group)
408
+ return "";
409
+ const keys = ["base", "hover", "active", "disabled", "contrast"];
410
+ const swatches = keys
411
+ .filter((k) => group[k])
412
+ .map((k) => `
413
+ <div class="swatch" style="background:${group[k]}; color: ${k === "contrast" ? group.base : "#fff"};">
414
+ <span class="swatch-label">${k}</span>
415
+ <span class="swatch-hex">${group[k]}</span>
416
+ </div>`)
417
+ .join("");
418
+ return `
419
+ <div class="section">
420
+ <h3>${escapeHtml(name)}</h3>
421
+ <div class="swatch-group">${swatches}</div>
422
+ </div>`;
423
+ }
424
+ function renderSemanticColors(sem) {
425
+ if (!sem)
426
+ return "";
427
+ const swatches = Object.entries(sem)
428
+ .map(([k, v]) => `
429
+ <div class="swatch" style="background:${v};">
430
+ <span class="swatch-label">${k}</span>
431
+ <span class="swatch-hex">${v}</span>
432
+ </div>`)
433
+ .join("");
434
+ return `
435
+ <div class="section">
436
+ <h3>semantic</h3>
437
+ <div class="swatch-group">${swatches}</div>
438
+ </div>`;
439
+ }
440
+ function renderSurfaceColors(surfaces) {
441
+ if (!surfaces)
442
+ return "";
443
+ const swatches = Object.entries(surfaces)
444
+ .map(([k, v]) => `
445
+ <div class="swatch" style="background:${v}; ${k === "foreground" || k === "border" ? "color:#fff" : ""}; ${k === "background" || k === "card" || k === "muted" ? "border:1px solid #ddd;" : ""}">
446
+ <span class="swatch-label">${k}</span>
447
+ <span class="swatch-hex">${v}</span>
448
+ </div>`)
449
+ .join("");
450
+ return `
451
+ <div class="section">
452
+ <h3>surfaces</h3>
453
+ <div class="swatch-group">${swatches}</div>
454
+ </div>`;
455
+ }
456
+ // ── Typography samples ─────────────────────────────────────────────
457
+ function renderTypography() {
458
+ const families = typography.families || [];
459
+ const scale = typography.scale || [];
460
+ const familyHtml = families
461
+ .map((f) => `
462
+ <div style="margin-bottom:8px;">
463
+ <span style="font-size:12px;color:#999;font-family:monospace;">${escapeHtml(f.name)}</span>
464
+ <span style="font-size:11px;color:#aaa;font-family:monospace;margin-left:8px;">${escapeHtml(f.role)}</span>
465
+ <div style="font-size:12px;color:#bbb;font-family:monospace;word-break:break-all;">${escapeHtml(f.stack)}</div>
466
+ </div>`)
467
+ .join("");
468
+ const scaleHtml = scale
469
+ .map((t) => {
470
+ const style = `font-size:${t.size};font-weight:${t.weight};line-height:${t.line_height};${t.letter_spacing ? `letter-spacing:${t.letter_spacing};` : ""}`;
471
+ return `
472
+ <div class="type-sample" style="margin-bottom:16px;padding-bottom:8px;border-bottom:1px solid #eee;">
473
+ <div style="${style}margin:0;">
474
+ ${escapeHtml(t.name || t.role)} — The quick brown fox jumps over the lazy dog.
475
+ </div>
476
+ <div style="font-size:11px;color:#999;margin-top:4px;font-family:monospace;">
477
+ ${escapeHtml(t.name)} / ${t.size} / ${t.weight}w / ${t.line_height}lh${t.letter_spacing ? ` / ${t.letter_spacing}` : ""} / ${t.role}
478
+ </div>
479
+ </div>`;
480
+ })
481
+ .join("");
482
+ return `
483
+ <div class="section">
484
+ <h3>Font Families</h3>
485
+ ${familyHtml}
486
+ <h3 style="margin-top:24px;">Type Scale</h3>
487
+ ${scaleHtml}
488
+ </div>`;
489
+ }
490
+ // ── Spacing blocks ─────────────────────────────────────────────────
491
+ function renderSpacing() {
492
+ const base = spacing.base_unit || 4;
493
+ const scale = spacing.scale || [];
494
+ const blocks = scale
495
+ .map((multiplier) => {
496
+ const px = base * multiplier;
497
+ return `
498
+ <div class="spacing-row">
499
+ <span class="spacing-label">${multiplier}×</span>
500
+ <span class="spacing-value">${px}px</span>
501
+ <div class="spacing-block" style="width:${Math.min(px, 640)}px;height:16px;background:#3B82F6;border-radius:2px;${px === 0 ? "width:8px;background:transparent;border-left:1px dashed #999;" : ""}"></div>
502
+ </div>`;
503
+ })
504
+ .join("");
505
+ return `
506
+ <div class="section">
507
+ <h3>Spacing Scale (base unit: ${base}px)</h3>
508
+ ${blocks}
509
+ </div>`;
510
+ }
511
+ // ── Component library ──────────────────────────────────────────────
512
+ function renderComponentGroup(type, variants) {
513
+ if (!variants || variants.length === 0)
514
+ return "";
515
+ const variantHtml = variants
516
+ .map((v) => {
517
+ const states = v.states || [];
518
+ const stateHtml = states
519
+ .map((s) => `
520
+ <div class="state-card">
521
+ <div class="state-name">${escapeHtml(s.name)}</div>
522
+ <div class="state-props">
523
+ ${Object.entries(s.properties || {})
524
+ .map(([pk, pv]) => `<span class="prop">${escapeHtml(pk)}: <strong>${escapeHtml(String(pv))}</strong></span>`)
525
+ .join("\n ")}
526
+ </div>
527
+ </div>`)
528
+ .join("");
529
+ return `
530
+ <div class="variant-card">
531
+ <div class="variant-header">
532
+ <strong>${escapeHtml(v.name)}</strong>
533
+ ${v.description ? `<p class="variant-desc">${escapeHtml(v.description)}</p>` : ""}
534
+ </div>
535
+ <div class="variant-props">
536
+ ${Object.entries(v.properties || {})
537
+ .slice(0, 8)
538
+ .map(([pk, pv]) => `<span class="prop">${escapeHtml(pk)}: <strong>${escapeHtml(String(pv)).substring(0, 60)}</strong></span>`)
539
+ .join("\n ")}
540
+ ${Object.keys(v.properties || {}).length > 8 ? `<span class="prop" style="color:#999;">… and ${Object.keys(v.properties).length - 8} more</span>` : ""}
541
+ </div>
542
+ ${stateHtml ? `<div class="states-section"><strong>States:</strong>${stateHtml}</div>` : ""}
543
+ </div>`;
544
+ })
545
+ .join("");
546
+ return `
547
+ <div class="section">
548
+ <h3>${escapeHtml(type)}</h3>
549
+ <div class="variants-grid">${variantHtml}</div>
550
+ </div>`;
551
+ }
552
+ // ── Breakpoints ────────────────────────────────────────────────────
553
+ function renderBreakpoints() {
554
+ if (!breakpoints.length)
555
+ return "";
556
+ const bpHtml = breakpoints
557
+ .map((bp) => `
558
+ <div class="bp-card">
559
+ <div class="bp-header">
560
+ <strong>${escapeHtml(bp.name)}</strong>
561
+ <span class="bp-meta">≥${bp.min_width}px / ${bp.columns} cols</span>
562
+ </div>
563
+ ${bp.rules && bp.rules.length > 0
564
+ ? `<ul class="bp-rules">
565
+ ${bp.rules.map((r) => `<li>${escapeHtml(r)}</li>`).join("")}
566
+ </ul>`
567
+ : ""}
568
+ </div>`)
569
+ .join("");
570
+ return `
571
+ <div class="section">
572
+ <h3>Breakpoints</h3>
573
+ <div class="bps-grid">${bpHtml}</div>
574
+ </div>`;
575
+ }
576
+ // ── Assemble ───────────────────────────────────────────────────────
577
+ const colorSection = `
578
+ <div class="section">
579
+ <h2>Color Palette</h2>
580
+ ${renderColorGroup("primary", palette.primary)}
581
+ ${renderColorGroup("secondary", palette.secondary)}
582
+ ${renderColorGroup("accent", palette.accent)}
583
+ ${renderColorGroup("neutral", palette.neutral)}
584
+ ${renderSemanticColors(palette.semantic)}
585
+ ${renderSurfaceColors(palette.surfaces)}
586
+ </div>`;
587
+ const typographySection = renderTypography();
588
+ const spacingSection = renderSpacing();
589
+ const componentsSection = `
590
+ <div class="section">
591
+ <h2>Component Library</h2>
592
+ ${renderComponentGroup("button", components.button)}
593
+ ${renderComponentGroup("card", components.card)}
594
+ ${renderComponentGroup("input", components.input)}
595
+ ${renderComponentGroup("navigation", components.navigation)}
596
+ </div>`;
597
+ const breakpointsSection = renderBreakpoints();
598
+ return `<!DOCTYPE html>
599
+ <html lang="en">
600
+ <head><meta charset="UTF-8"><title>Style Guide Preview</title>
601
+ <style>
602
+ * { margin: 0; padding: 0; box-sizing: border-box; }
603
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 24px; background: #f0f2f5; color: #1a1a2e; }
604
+ .container { max-width: 1100px; margin: 0 auto; background: #fff; padding: 32px; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
605
+ h1 { font-size: 28px; font-weight: 700; margin-bottom: 8px; color: #111; }
606
+ h2 { font-size: 20px; font-weight: 600; margin: 32px 0 16px; padding-bottom: 8px; border-bottom: 2px solid #e5e7eb; color: #1f2937; }
607
+ h3 { font-size: 14px; font-weight: 600; margin: 16px 0 8px; text-transform: uppercase; letter-spacing: 0.05em; color: #6b7280; }
608
+ .section { margin-bottom: 32px; }
609
+ .swatch-group { display: flex; flex-wrap: wrap; gap: 8px; }
610
+ .swatch { display: flex; flex-direction: column; justify-content: flex-end; width: 120px; height: 80px; padding: 6px 8px; border-radius: 6px; font-size: 11px; font-family: monospace; position: relative; }
611
+ .swatch-label { font-weight: 600; text-transform: uppercase; opacity: 0.85; }
612
+ .swatch-hex { opacity: 0.7; font-size: 10px; }
613
+ .type-sample { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
614
+ .spacing-row { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; }
615
+ .spacing-label { width: 40px; font-size: 12px; font-family: monospace; color: #6b7280; text-align: right; }
616
+ .spacing-value { width: 60px; font-size: 11px; font-family: monospace; color: #9ca3af; }
617
+ .spacing-block { flex-shrink: 0; transition: width 0.1s; }
618
+ .variants-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
619
+ .variant-card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; background: #fafafa; }
620
+ .variant-header { margin-bottom: 8px; }
621
+ .variant-desc { font-size: 12px; color: #6b7280; margin-top: 4px; line-height: 1.4; }
622
+ .variant-props { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }
623
+ .prop { font-size: 10px; font-family: monospace; background: #f3f4f6; padding: 2px 6px; border-radius: 4px; color: #374151; }
624
+ .states-section { border-top: 1px solid #e5e7eb; padding-top: 8px; margin-top: 8px; font-size: 12px; color: #374151; }
625
+ .state-card { margin-top: 6px; padding: 6px; background: #fff; border: 1px solid #f3f4f6; border-radius: 4px; }
626
+ .state-name { font-size: 11px; font-weight: 600; text-transform: uppercase; color: #6b7280; margin-bottom: 4px; }
627
+ .state-props { display: flex; flex-wrap: wrap; gap: 3px; }
628
+ .bps-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
629
+ .bp-card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; background: #fafafa; }
630
+ .bp-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
631
+ .bp-meta { font-size: 11px; font-family: monospace; color: #6b7280; }
632
+ .bp-rules { list-style: none; font-size: 12px; color: #4b5563; }
633
+ .bp-rules li::before { content: "• "; color: #3B82F6; }
634
+ .subtitle { font-size: 14px; color: #6b7280; margin-bottom: 24px; }
635
+ </style></head>
636
+ <body><div class="container">
637
+ <h1>Style Guide: ${escapeHtml(artifact.id)}</h1>
638
+ <div class="subtitle">High-fidelity design system specification</div>
639
+ ${colorSection}
640
+ ${typographySection}
641
+ ${spacingSection}
642
+ ${componentsSection}
643
+ ${breakpointsSection}
644
+ </div></body></html>`;
645
+ }
646
+ function escapeHtml(text) {
647
+ return text
648
+ .replace(/&/g, "&amp;")
649
+ .replace(/</g, "&lt;")
650
+ .replace(/>/g, "&gt;")
651
+ .replace(/"/g, "&quot;");
652
+ }
653
+ //# sourceMappingURL=spec-design-hifi.js.map