get-shit-pretty 0.7.4 → 0.8.3

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 (79) hide show
  1. package/README.md +21 -14
  2. package/bin/install.js +10 -10
  3. package/bin/theme-css.js +331 -0
  4. package/gsp/agents/gsp-brand-coherence.md +7 -0
  5. package/gsp/skills/get-shit-pretty/SKILL.md +3 -1
  6. package/gsp/skills/gsp-brand-brief/SKILL.md +50 -5
  7. package/gsp/skills/gsp-brand-guidelines/SKILL.md +51 -31
  8. package/gsp/skills/gsp-brand-guidelines/design-tokens.md +2 -0
  9. package/gsp/skills/gsp-brand-guidelines/guidelines-structure.md +167 -0
  10. package/gsp/skills/gsp-brand-guidelines/methodology/gsp-brand-coherence.md +86 -0
  11. package/gsp/skills/gsp-brand-guidelines/methodology/gsp-brand-engineer.md +13 -5
  12. package/gsp/skills/gsp-brand-guidelines/token-mapping.md +16 -319
  13. package/gsp/skills/gsp-brand-identity/SKILL.md +1 -1
  14. package/gsp/skills/gsp-brand-refine/SKILL.md +5 -6
  15. package/gsp/skills/gsp-brand-research/SKILL.md +1 -1
  16. package/gsp/skills/gsp-brand-strategy/SKILL.md +1 -1
  17. package/gsp/skills/gsp-design-system/SKILL.md +1 -1
  18. package/gsp/skills/gsp-doctor/SKILL.md +54 -6
  19. package/gsp/skills/gsp-progress/SKILL.md +1 -1
  20. package/gsp/skills/gsp-project-brief/SKILL.md +40 -6
  21. package/gsp/skills/gsp-project-build/SKILL.md +22 -29
  22. package/gsp/skills/gsp-project-build/flows/figma.md +50 -0
  23. package/gsp/skills/gsp-project-build/flows/revision.md +64 -0
  24. package/gsp/skills/gsp-project-build/methodology/gsp-project-builder.md +57 -4
  25. package/gsp/skills/gsp-project-build/shadcn-composition.md +217 -0
  26. package/gsp/skills/gsp-project-critique/SKILL.md +3 -1
  27. package/gsp/skills/gsp-project-design/SKILL.md +3 -1
  28. package/gsp/skills/gsp-project-research/SKILL.md +3 -1
  29. package/gsp/skills/gsp-project-review/SKILL.md +10 -1
  30. package/gsp/skills/gsp-scaffold/SKILL.md +49 -12
  31. package/gsp/skills/gsp-scaffold/shadcn-rules.md +433 -0
  32. package/gsp/skills/gsp-scaffold/shadcn-theming.md +310 -0
  33. package/gsp/skills/gsp-start/SKILL.md +18 -2
  34. package/gsp/skills/gsp-style/SKILL.md +1 -1
  35. package/gsp/skills/gsp-style/style-preset-schema.md +59 -14
  36. package/gsp/skills/gsp-style/styles/academia.yml +58 -8
  37. package/gsp/skills/gsp-style/styles/art-deco.yml +39 -7
  38. package/gsp/skills/gsp-style/styles/bauhaus.yml +39 -8
  39. package/gsp/skills/gsp-style/styles/bold-typography.yml +39 -8
  40. package/gsp/skills/gsp-style/styles/botanical.yml +39 -9
  41. package/gsp/skills/gsp-style/styles/claymorphism.yml +39 -9
  42. package/gsp/skills/gsp-style/styles/cyberpunk.yml +39 -7
  43. package/gsp/skills/gsp-style/styles/enterprise.yml +39 -10
  44. package/gsp/skills/gsp-style/styles/flat-design.yml +39 -9
  45. package/gsp/skills/gsp-style/styles/fluent.yml +39 -10
  46. package/gsp/skills/gsp-style/styles/glassmorphism.yml +39 -8
  47. package/gsp/skills/gsp-style/styles/humanist-literary.yml +39 -9
  48. package/gsp/skills/gsp-style/styles/industrial.yml +59 -9
  49. package/gsp/skills/gsp-style/styles/kinetic.yml +32 -7
  50. package/gsp/skills/gsp-style/styles/liquid-glass.yml +59 -9
  51. package/gsp/skills/gsp-style/styles/luxury.yml +59 -9
  52. package/gsp/skills/gsp-style/styles/material.yml +59 -9
  53. package/gsp/skills/gsp-style/styles/maximalism.yml +32 -6
  54. package/gsp/skills/gsp-style/styles/minimal-dark.yml +32 -7
  55. package/gsp/skills/gsp-style/styles/modern-dark.yml +32 -7
  56. package/gsp/skills/gsp-style/styles/monochrome.yml +59 -10
  57. package/gsp/skills/gsp-style/styles/neubrutalism.yml +59 -9
  58. package/gsp/skills/gsp-style/styles/neumorphism.yml +32 -7
  59. package/gsp/skills/gsp-style/styles/newsprint.yml +32 -7
  60. package/gsp/skills/gsp-style/styles/nothing.yml +44 -13
  61. package/gsp/skills/gsp-style/styles/organic.yml +42 -9
  62. package/gsp/skills/gsp-style/styles/playful-geometric.yml +43 -9
  63. package/gsp/skills/gsp-style/styles/professional.yml +41 -10
  64. package/gsp/skills/gsp-style/styles/retro.yml +42 -8
  65. package/gsp/skills/gsp-style/styles/saas.yml +42 -9
  66. package/gsp/skills/gsp-style/styles/sketch.yml +42 -9
  67. package/gsp/skills/gsp-style/styles/swiss-minimalist.yml +41 -10
  68. package/gsp/skills/gsp-style/styles/terminal.yml +42 -8
  69. package/gsp/skills/gsp-style/styles/vaporwave.yml +42 -7
  70. package/gsp/skills/gsp-style/styles/web3.yml +42 -9
  71. package/gsp/skills/gsp-update/SKILL.md +9 -6
  72. package/gsp/templates/branding/brief.md +9 -0
  73. package/gsp/templates/branding/config.json +1 -1
  74. package/gsp/templates/design-claude.md +6 -0
  75. package/gsp/templates/phases/patterns.md +2 -2
  76. package/gsp/templates/projects/config.json +6 -3
  77. package/gsp/templates/projects/state.md +1 -1
  78. package/gsp/templates/system/STACK.md +1 -0
  79. package/package.json +1 -1
package/README.md CHANGED
@@ -12,7 +12,9 @@
12
12
  <br>
13
13
 
14
14
  ```bash
15
- npx get-shit-pretty
15
+ pnpm dlx get-shit-pretty
16
+ # or with bun
17
+ bunx get-shit-pretty
16
18
  ```
17
19
 
18
20
  **Works on Mac, Windows, and Linux.**
@@ -50,8 +52,9 @@ Both disciplines. Same pipeline. Same environment. The missing half of the bridg
50
52
  ## Quick Start
51
53
 
52
54
  ```bash
53
- # 1. Install
54
- npx get-shit-pretty
55
+ # 1. Install (pnpm or bun)
56
+ pnpm dlx get-shit-pretty
57
+ # bunx get-shit-pretty
55
58
 
56
59
  # 2. Define your brand — or skip with a style preset
57
60
  /gsp-brand-brief # guided brand definition
@@ -418,7 +421,9 @@ GSP works across all major AI coding tools. The installer converts Claude Code's
418
421
  ## Install
419
422
 
420
423
  ```bash
421
- npx get-shit-pretty
424
+ pnpm dlx get-shit-pretty
425
+ # or with bun
426
+ bunx get-shit-pretty
422
427
  ```
423
428
 
424
429
  The installer prompts you to choose:
@@ -430,32 +435,34 @@ The installer prompts you to choose:
430
435
 
431
436
  ```bash
432
437
  # Claude Code
433
- npx get-shit-pretty --claude --global
434
- npx get-shit-pretty --claude --local
438
+ pnpm dlx get-shit-pretty --claude --global
439
+ pnpm dlx get-shit-pretty --claude --local
435
440
 
436
441
  # OpenCode
437
- npx get-shit-pretty --opencode --global
442
+ pnpm dlx get-shit-pretty --opencode --global
438
443
 
439
444
  # Gemini CLI
440
- npx get-shit-pretty --gemini --global
445
+ pnpm dlx get-shit-pretty --gemini --global
441
446
 
442
447
  # Codex CLI
443
- npx get-shit-pretty --codex --global
448
+ pnpm dlx get-shit-pretty --codex --global
444
449
 
445
450
  # All runtimes
446
- npx get-shit-pretty --all --global
451
+ pnpm dlx get-shit-pretty --all --global
447
452
  ```
448
453
 
454
+ > Substitute `bunx` for `pnpm dlx` if you prefer bun.
455
+
449
456
  </details>
450
457
 
451
458
  <details>
452
459
  <summary><strong>Uninstall</strong></summary>
453
460
 
454
461
  ```bash
455
- npx get-shit-pretty --claude --global --uninstall
456
- npx get-shit-pretty --opencode --global --uninstall
457
- npx get-shit-pretty --gemini --global --uninstall
458
- npx get-shit-pretty --codex --global --uninstall
462
+ pnpm dlx get-shit-pretty --claude --global --uninstall
463
+ pnpm dlx get-shit-pretty --opencode --global --uninstall
464
+ pnpm dlx get-shit-pretty --gemini --global --uninstall
465
+ pnpm dlx get-shit-pretty --codex --global --uninstall
459
466
  ```
460
467
 
461
468
  </details>
package/bin/install.js CHANGED
@@ -204,7 +204,7 @@ console.log(banner);
204
204
 
205
205
  // Help
206
206
  if (hasHelp) {
207
- console.log(` ${yellow}Usage:${reset} npx get-shit-pretty [options]\n
207
+ console.log(` ${yellow}Usage:${reset} pnpm dlx get-shit-pretty [options] ${dim}# or: bunx get-shit-pretty [options]${reset}\n
208
208
  ${yellow}Options:${reset}
209
209
  ${cyan}-g, --global${reset} Install globally (to config directory)
210
210
  ${cyan}-l, --local${reset} Install locally (to current directory)
@@ -221,19 +221,19 @@ if (hasHelp) {
221
221
 
222
222
  ${yellow}Examples:${reset}
223
223
  ${dim}# Interactive install (prompts for runtime and location)${reset}
224
- npx get-shit-pretty
224
+ pnpm dlx get-shit-pretty
225
225
 
226
226
  ${dim}# Install for Claude Code globally${reset}
227
- npx get-shit-pretty --claude --global
227
+ pnpm dlx get-shit-pretty --claude --global
228
228
 
229
229
  ${dim}# Install for all runtimes globally${reset}
230
- npx get-shit-pretty --all --global
230
+ pnpm dlx get-shit-pretty --all --global
231
231
 
232
232
  ${dim}# Install to current project only${reset}
233
- npx get-shit-pretty --claude --local
233
+ pnpm dlx get-shit-pretty --claude --local
234
234
 
235
235
  ${dim}# Uninstall GSP from Claude Code globally${reset}
236
- npx get-shit-pretty --claude --global --uninstall
236
+ pnpm dlx get-shit-pretty --claude --global --uninstall
237
237
  `);
238
238
  process.exit(0);
239
239
  }
@@ -1817,8 +1817,8 @@ function promptRuntime(callback) {
1817
1817
 
1818
1818
  function promptLocation(runtimes) {
1819
1819
  if (!process.stdin.isTTY) {
1820
- console.log(` ${yellow}Non-interactive terminal detected, defaulting to global install${reset}\n`);
1821
- installAllRuntimes(runtimes, true, false);
1820
+ console.log(` ${yellow}Non-interactive terminal detected, defaulting to local (project) install${reset}\n`);
1821
+ installAllRuntimes(runtimes, false, false);
1822
1822
  return;
1823
1823
  }
1824
1824
 
@@ -1898,8 +1898,8 @@ if (require.main === module) {
1898
1898
  installAllRuntimes(['claude'], hasGlobal, false);
1899
1899
  } else {
1900
1900
  if (!process.stdin.isTTY) {
1901
- console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code global install${reset}\n`);
1902
- installAllRuntimes(['claude'], true, false);
1901
+ console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code local (project) install${reset}\n`);
1902
+ installAllRuntimes(['claude'], false, false);
1903
1903
  } else {
1904
1904
  promptRuntime((runtimes) => {
1905
1905
  promptLocation(runtimes);
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * theme-css.js — GSP deterministic token-to-CSS generator
4
+ *
5
+ * Reads a GSP style preset `.yml` file and outputs a shadcn/ui-compatible
6
+ * CSS variables block for `:root` and `.dark`.
7
+ *
8
+ * Usage:
9
+ * node bin/theme-css.js <path-to-preset.yml>
10
+ * node bin/theme-css.js <path-to-preset.yml> --output globals.css
11
+ * node bin/theme-css.js <path-to-preset.yml> --stdout
12
+ *
13
+ * Token → CSS var mapping is 1:1. No derivation, no LLM guesswork.
14
+ * Hex values are converted to OKLCH. Alpha values (oklch with /) pass through.
15
+ * Sidebar vars are output verbatim. Extras (success/warning/info) become custom props.
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // YAML parser (zero-dependency, handles the subset GSP uses)
25
+ // ---------------------------------------------------------------------------
26
+
27
+ function parseYaml(text) {
28
+ const lines = text.split('\n');
29
+ const root = {};
30
+ const stack = [{ indent: -1, obj: root }];
31
+
32
+ for (let i = 0; i < lines.length; i++) {
33
+ const raw = lines[i];
34
+ const trimmed = raw.trimEnd();
35
+ if (!trimmed || trimmed.trimStart().startsWith('#')) continue;
36
+
37
+ const indent = raw.length - raw.trimStart().length;
38
+ const content = trimmed.trimStart();
39
+
40
+ // Handle inline arrays: key: [a, b, c]
41
+ const colonIdx = content.indexOf(':');
42
+ if (colonIdx === -1) continue;
43
+
44
+ const key = content.slice(0, colonIdx).trim();
45
+ const rest = content.slice(colonIdx + 1).trim();
46
+
47
+ // Pop stack to correct parent
48
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
49
+ stack.pop();
50
+ }
51
+ const parent = stack[stack.length - 1].obj;
52
+
53
+ if (rest === '' || rest.startsWith('#')) {
54
+ // Nested object
55
+ parent[key] = {};
56
+ stack.push({ indent, obj: parent[key] });
57
+ } else if (rest.startsWith('[')) {
58
+ // Inline array — parse as string, not needed for color extraction
59
+ parent[key] = rest;
60
+ } else {
61
+ // Scalar — strip inline comments and quotes
62
+ let val = rest.split(' #')[0].trim();
63
+ if ((val.startsWith('"') && val.endsWith('"')) ||
64
+ (val.startsWith("'") && val.endsWith("'"))) {
65
+ val = val.slice(1, -1);
66
+ }
67
+ // Numbers
68
+ if (/^-?\d+(\.\d+)?$/.test(val)) {
69
+ parent[key] = parseFloat(val);
70
+ } else if (val === 'true') {
71
+ parent[key] = true;
72
+ } else if (val === 'false') {
73
+ parent[key] = false;
74
+ } else {
75
+ parent[key] = val;
76
+ }
77
+ }
78
+ }
79
+
80
+ return root;
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Color conversion: hex → OKLCH
85
+ // ---------------------------------------------------------------------------
86
+
87
+ function hexToRgb(hex) {
88
+ const h = hex.replace('#', '');
89
+ const len = h.length;
90
+ if (len === 3) {
91
+ return [
92
+ parseInt(h[0] + h[0], 16),
93
+ parseInt(h[1] + h[1], 16),
94
+ parseInt(h[2] + h[2], 16),
95
+ ];
96
+ }
97
+ return [
98
+ parseInt(h.slice(0, 2), 16),
99
+ parseInt(h.slice(2, 4), 16),
100
+ parseInt(h.slice(4, 6), 16),
101
+ ];
102
+ }
103
+
104
+ // sRGB → linear
105
+ function toLinear(c) {
106
+ c = c / 255;
107
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
108
+ }
109
+
110
+ // Linear RGB → XYZ (D65)
111
+ function linearToXyz(r, g, b) {
112
+ return [
113
+ r * 0.4124564 + g * 0.3575761 + b * 0.1804375,
114
+ r * 0.2126729 + g * 0.7151522 + b * 0.0721750,
115
+ r * 0.0193339 + g * 0.1191920 + b * 0.9503041,
116
+ ];
117
+ }
118
+
119
+ // XYZ → OKLab
120
+ function xyzToOklab(x, y, z) {
121
+ const l_ = Math.cbrt(0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z);
122
+ const m_ = Math.cbrt(0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z);
123
+ const s_ = Math.cbrt(0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z);
124
+ return [
125
+ 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
126
+ 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
127
+ 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
128
+ ];
129
+ }
130
+
131
+ // OKLab → OKLCH
132
+ function oklabToOklch(L, a, b) {
133
+ const C = Math.sqrt(a * a + b * b);
134
+ let H = Math.atan2(b, a) * (180 / Math.PI);
135
+ if (H < 0) H += 360;
136
+ return [L, C, H];
137
+ }
138
+
139
+ function hexToOklch(hex) {
140
+ const [r, g, b] = hexToRgb(hex);
141
+ const lr = toLinear(r);
142
+ const lg = toLinear(g);
143
+ const lb = toLinear(b);
144
+ const [x, y, z] = linearToXyz(lr, lg, lb);
145
+ const [L, a, bk] = xyzToOklab(x, y, z);
146
+ const [Lch, C, H] = oklabToOklch(L, a, bk);
147
+ // Format: oklch(L% C H)
148
+ const Lpct = (Lch * 100).toFixed(2);
149
+ const Cfmt = C.toFixed(4);
150
+ const Hfmt = H.toFixed(2);
151
+ return `oklch(${Lpct}% ${Cfmt} ${Hfmt})`;
152
+ }
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // Value formatter: hex → oklch, alpha values pass through
156
+ // ---------------------------------------------------------------------------
157
+
158
+ function formatValue(val) {
159
+ if (typeof val !== 'string') return String(val);
160
+ const v = val.trim();
161
+
162
+ // Already oklch (alpha or not) — pass through
163
+ if (v.startsWith('oklch(')) return v;
164
+
165
+ // Hex color
166
+ if (/^#[0-9a-fA-F]{3,6}$/.test(v)) {
167
+ return hexToOklch(v);
168
+ }
169
+
170
+ // Everything else (rgba, named colors, etc.) — pass through
171
+ return v;
172
+ }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // CSS var name mapping (1:1 to shadcn/ui)
176
+ // ---------------------------------------------------------------------------
177
+
178
+ // Core color tokens → CSS var names
179
+ const CORE_VARS = [
180
+ 'background', 'foreground',
181
+ 'card', 'card-foreground',
182
+ 'popover', 'popover-foreground',
183
+ 'primary', 'primary-foreground',
184
+ 'secondary', 'secondary-foreground',
185
+ 'accent', 'accent-foreground',
186
+ 'muted', 'muted-foreground',
187
+ 'destructive',
188
+ 'border', 'input', 'ring',
189
+ ];
190
+
191
+ // NOTE: 'sidebar' (not 'sidebar-background') matches shadcn's CSS var --sidebar
192
+ const SIDEBAR_VARS = [
193
+ 'sidebar', 'sidebar-foreground',
194
+ 'sidebar-primary', 'sidebar-primary-foreground',
195
+ 'sidebar-accent', 'sidebar-accent-foreground',
196
+ 'sidebar-border', 'sidebar-ring',
197
+ ];
198
+
199
+ const EXTRA_VARS = ['success', 'warning', 'info'];
200
+
201
+ // Shape tokens → CSS vars
202
+ const SHAPE_VARS = {
203
+ 'border-radius-lg': '--radius',
204
+ };
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // CSS block generator
208
+ // ---------------------------------------------------------------------------
209
+
210
+ function generateBlock(colorObj, shapeObj, typographyObj, selector) {
211
+ if (!colorObj) return '';
212
+ const lines = [];
213
+
214
+ // Core vars
215
+ for (const key of CORE_VARS) {
216
+ if (colorObj[key] !== undefined) {
217
+ lines.push(` --${key}: ${formatValue(colorObj[key])};`);
218
+ }
219
+ }
220
+
221
+ // Sidebar vars
222
+ for (const key of SIDEBAR_VARS) {
223
+ if (colorObj[key] !== undefined) {
224
+ lines.push(` --${key}: ${formatValue(colorObj[key])};`);
225
+ }
226
+ }
227
+
228
+ // Chart vars — emit in both :root and .dark (dark falls back to light values if not set)
229
+ {
230
+ // For :root use light palette; for .dark use dark overrides falling back to light
231
+ const chartColors = [
232
+ colorObj['chart-1'] || colorObj.primary,
233
+ colorObj['chart-2'] || colorObj.secondary,
234
+ colorObj['chart-3'] || colorObj.accent,
235
+ colorObj['chart-4'] || colorObj.info || colorObj.primary,
236
+ colorObj['chart-5'] || colorObj.success || colorObj.secondary || colorObj.primary,
237
+ ];
238
+ chartColors.forEach((c, i) => {
239
+ if (c) lines.push(` --chart-${i + 1}: ${formatValue(c)};`);
240
+ });
241
+ }
242
+
243
+ // Shape → radius (only in :root)
244
+ if (selector === ':root' && shapeObj) {
245
+ const lg = shapeObj['border-radius-lg'];
246
+ if (lg !== undefined) {
247
+ lines.push(` --radius: ${lg};`);
248
+ }
249
+ }
250
+
251
+ // Typography → font CSS vars (only in :root)
252
+ if (selector === ':root' && typographyObj) {
253
+ const fontMappings = [
254
+ ['font-family-primary', '--font-sans'],
255
+ ['font-family-mono', '--font-mono'],
256
+ ['font-family-display', '--font-display'],
257
+ ['font-family-secondary', '--font-secondary'],
258
+ ];
259
+ for (const [ymlKey, cssVar] of fontMappings) {
260
+ if (typographyObj[ymlKey] !== undefined) {
261
+ lines.push(` ${cssVar}: ${typographyObj[ymlKey]};`);
262
+ }
263
+ }
264
+ }
265
+
266
+ // Extras as custom properties
267
+ for (const key of EXTRA_VARS) {
268
+ if (colorObj[key] !== undefined) {
269
+ lines.push(` --${key}: ${formatValue(colorObj[key])};`);
270
+ }
271
+ }
272
+
273
+ if (!lines.length) return '';
274
+ return `${selector} {\n${lines.join('\n')}\n}`;
275
+ }
276
+
277
+ // ---------------------------------------------------------------------------
278
+ // Main
279
+ // ---------------------------------------------------------------------------
280
+
281
+ function main() {
282
+ const args = process.argv.slice(2);
283
+ if (!args.length || args.includes('--help') || args.includes('-h')) {
284
+ console.log(`Usage: node bin/theme-css.js <preset.yml> [--output <file>] [--stdout]`);
285
+ console.log(` node bin/theme-css.js gsp/skills/gsp-style/styles/saas.yml`);
286
+ process.exit(0);
287
+ }
288
+
289
+ const inputPath = path.resolve(args[0]);
290
+ if (!fs.existsSync(inputPath)) {
291
+ console.error(`Error: File not found: ${inputPath}`);
292
+ process.exit(1);
293
+ }
294
+
295
+ const outputIdx = args.indexOf('--output');
296
+ const outputPath = outputIdx !== -1 ? path.resolve(args[outputIdx + 1]) : null;
297
+ const toStdout = args.includes('--stdout') || !outputPath;
298
+
299
+ const raw = fs.readFileSync(inputPath, 'utf8');
300
+ const preset = parseYaml(raw);
301
+
302
+ const colorLight = (preset.tokens && preset.tokens.color) || {};
303
+ const colorDark = (preset.dark_mode && preset.dark_mode.color) || {};
304
+ const shape = (preset.tokens && preset.tokens.shape) || {};
305
+ const typography = (preset.tokens && preset.tokens.typography) || null;
306
+
307
+ const rootBlock = generateBlock(colorLight, shape, typography, ':root');
308
+ const darkBlock = generateBlock(colorDark, null, null, '.dark');
309
+
310
+ const presetName = preset.name || path.basename(inputPath, '.yml');
311
+ const presetDesc = preset.description || '';
312
+
313
+ const header = [
314
+ `/* GSP theme: ${presetName} */`,
315
+ presetDesc ? `/* ${presetDesc} */` : null,
316
+ `/* Generated by bin/theme-css.js from ${path.basename(inputPath)} */`,
317
+ `/* Edit the .yml file, not this output */`,
318
+ '',
319
+ ].filter(Boolean).join('\n');
320
+
321
+ const output = [header, rootBlock, darkBlock ? '' : null, darkBlock].filter(s => s !== null).join('\n');
322
+
323
+ if (toStdout) {
324
+ process.stdout.write(output + '\n');
325
+ } else {
326
+ fs.writeFileSync(outputPath, output + '\n', 'utf8');
327
+ console.log(`Written to ${outputPath}`);
328
+ }
329
+ }
330
+
331
+ main();
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: gsp-brand-coherence
3
+ description: Brand coherence auditor — scores intensity dials against archetype and surfaces tensions between declared values and expressed output
4
+ tools:
5
+ - Read
6
+ ---
7
+ Coherence auditor for gsp-brand-guidelines. Spawned by the skill after Pass 1 to evaluate the generated artifacts before user review.
@@ -11,7 +11,9 @@ Design engineering system for AI coding tools. Brand identity + design projects,
11
11
  ## Install
12
12
 
13
13
  ```bash
14
- npx get-shit-pretty
14
+ pnpm dlx get-shit-pretty
15
+ # or with bun
16
+ bunx get-shit-pretty
15
17
  ```
16
18
 
17
19
  Pick your runtime (Claude Code, OpenCode, Gemini CLI, or Codex CLI), choose global or local install, and you're set.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: gsp-brand-brief
3
- description: Define your brand — who, why, and what it should feel like
3
+ description: Define your brand — who, why, and what it should feel like — use when: create a brand, define our brand identity, who are we, what's our brand
4
4
  user-invocable: true
5
5
  allowed-tools:
6
6
  - Read
@@ -86,23 +86,35 @@ Before presenting personality options, **internally synthesize** promise (what s
86
86
  - Note: this is a high-level direction only. Brand strategy phase will deepen this into archetype + voice — don't over-refine here.
87
87
  8. What should the brand NEVER feel like? (use `AskUserQuestion` with 2-3 anti-directions inferred from their personality pick, plus open-ended option)
88
88
  9. Brands admired or styles to avoid? (open-ended `AskUserQuestion`)
89
+ 10. Visual direction — raw aesthetic feeling. Use `AskUserQuestion` (open-ended):
90
+ > "What should it look and feel like visually? You can share image or website links, describe a mood ('editorial and dark', 'warm brutalist', 'cinematographic with beautiful stills'), drop adjective clusters ('rounded, clean, airy'), or even describe a scene or texture. The weirder and more specific the better — this is what prevents a bland brand."
91
+ - Synthesize the answer into a `visual_direction` block in the brief: mood words, reference aesthetics, texture/atmosphere descriptors, any specific anti-patterns (e.g., "never stock-photo corporate"). This block directly informs color, typography, and imagery choices downstream.
89
92
 
90
93
  ## Step 4: Constraints & confirmation
91
94
 
92
- 10. Any non-negotiables or constraints? (timeline, budget, must-haves) — open-ended `AskUserQuestion`
93
- 11. State your understanding back: "Here's what I'm hearing: [summary]." Use `AskUserQuestion`:
95
+ 11. Any non-negotiables or constraints? (timeline, budget, must-haves) — open-ended `AskUserQuestion`
96
+ 12. State your understanding back but lead with *feeling*, not facts. Format:
97
+
98
+ > "Here's what I'm hearing: [2-sentence factual summary].
99
+ > The feeling this brand should leave: **[emotional compass — one evocative sentence capturing the brand's energy, not its category]**."
100
+
101
+ The emotional compass is the hardest line to write and the most important. It should make the user feel something when they read it. Not "a fintech tool that simplifies investing" but "the brand that makes financial confidence feel earned, not given." Synthesize it from the personality direction, the persona aspiration, the brand POV, and the visual direction. It should be specific enough to be wrong — vague sentences aren't compasses.
102
+
103
+ Use `AskUserQuestion`:
94
104
  - **Looks good** — "That's accurate, let's go"
95
- - **Adjust something** — "I want to change or add something"
105
+ - **Adjust the feeling** — "The compass is off let me reframe it"
106
+ - **Adjust something else** — "Facts are right but I want to change something"
96
107
 
97
108
  If "Adjust" — ask what to change, update your understanding, re-confirm. Don't re-ask everything.
98
109
 
99
- ## Step 5: Write artifacts
110
+ ## Step 5: Write artifacts and register brand
100
111
 
101
112
  Read templates at write time from `${CLAUDE_SKILL_DIR}/../../templates/branding/` and write:
102
113
 
103
114
  1. `.design/branding/{name}/BRIEF.md` from `brief.md` template
104
115
  - Populate all sections from conversation answers
105
116
  - Synthesize brand promise, POV, and personality (these are inferred, not asked directly)
117
+ - Write the confirmed emotional compass as `brand_heartbeat` in the Emotional Compass section
106
118
  - Set `brand_mode` to `new`
107
119
  - Set evolve-only sections (Existing Brand State, Evolution Scope) to "N/A — new brand"
108
120
 
@@ -117,6 +129,14 @@ Read templates at write time from `${CLAUDE_SKILL_DIR}/../../templates/branding/
117
129
 
118
130
  4. `.design/branding/{name}/ROADMAP.md` from `roadmap.md` template
119
131
 
132
+ 5. Write/update `.design/CLAUDE.md` — register the brand as started. If the file doesn't exist, read `${CLAUDE_SKILL_DIR}/../../templates/design-claude.md` first. Append under `## Brands`:
133
+
134
+ ```markdown
135
+ ### {brand-name} · in progress · {DATE}
136
+ "{brand_heartbeat}"
137
+ next: gsp-brand-research · .design/branding/{brand-name}/
138
+ ```
139
+
120
140
  ## Step 6: Route
121
141
 
122
142
  Use `AskUserQuestion` — always offer Continue / Stop here / What happens next:
@@ -126,4 +146,29 @@ Use `AskUserQuestion` — always offer Continue / Stop here / What happens next:
126
146
  - **What happens next?** — "Explain the research phase" → explain what brand-research does (market landscape, competitive audit, trend analysis, mood board direction) and how it uses the brief
127
147
 
128
148
  If `e2e: true`, mention that after the full branding diamond completes, it will auto-transition to project setup.
149
+
150
+ ## Step 7: e2e transition (only when `e2e: true` and branding diamond is complete)
151
+
152
+ After all four brand phases complete (brand-research → brand-strategy → brand-identity → brand-guidelines), scaffold the project directory before invoking `/gsp-project-brief`:
153
+
154
+ 1. Derive `{project-slug}` from the brand name: lowercase, spaces and underscores replaced with hyphens.
155
+
156
+ 2. Create the project directory:
157
+ ```bash
158
+ mkdir -p .design/projects/{project-slug}/
159
+ ```
160
+
161
+ 3. Read templates at write time from `${CLAUDE_SKILL_DIR}/../../templates/projects/` and write:
162
+ - `.design/projects/{project-slug}/config.json` from `config.json` template — set `project.name` (title-cased from project-slug) and `project.created` (ISO date)
163
+ - `.design/projects/{project-slug}/STATE.md` from `state.md` template — fill in project name and brand name
164
+
165
+ 4. Write `.design/projects/{project-slug}/brand.ref` containing the brand directory name (e.g. `{brand-name}`), so the project knows which brand it belongs to.
166
+
167
+ 5. Display:
168
+ ```
169
+ brand complete — {brand-name}
170
+ now let's scope your project.
171
+ ```
172
+
173
+ 6. Invoke `/gsp-project-brief` via Skill tool, passing `{project-slug}` so Step 0 resolves the existing directory rather than prompting for one.
129
174
  </process>