voidforge-build 23.11.4 → 23.12.1

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 (45) hide show
  1. package/dist/.claude/agents/batman-qa.md +1 -0
  2. package/dist/.claude/agents/galadriel-frontend.md +2 -0
  3. package/dist/.claude/agents/kusanagi-devops.md +4 -0
  4. package/dist/.claude/agents/lucius-config.md +6 -0
  5. package/dist/.claude/agents/samwise-accessibility.md +4 -0
  6. package/dist/.claude/agents/silver-surfer-herald.md +13 -4
  7. package/dist/.claude/commands/architect.md +9 -0
  8. package/dist/.claude/commands/assemble.md +4 -1
  9. package/dist/.claude/commands/assess.md +13 -1
  10. package/dist/.claude/commands/audit-docs.md +106 -0
  11. package/dist/.claude/commands/deploy.md +29 -1
  12. package/dist/.claude/commands/engage.md +19 -1
  13. package/dist/.claude/commands/gauntlet.md +23 -4
  14. package/dist/.claude/commands/imagine.md +15 -0
  15. package/dist/.claude/commands/sentinel.md +15 -0
  16. package/dist/.claude/commands/ux.md +36 -0
  17. package/dist/.claude/commands/void.md +1 -0
  18. package/dist/CHANGELOG.md +65 -0
  19. package/dist/CLAUDE.md +9 -0
  20. package/dist/VERSION.md +3 -1
  21. package/dist/docs/methods/AI_INTELLIGENCE.md +33 -0
  22. package/dist/docs/methods/ASSEMBLER.md +31 -2
  23. package/dist/docs/methods/BUILD_PROTOCOL.md +2 -0
  24. package/dist/docs/methods/CAMPAIGN.md +46 -0
  25. package/dist/docs/methods/DEVOPS_ENGINEER.md +194 -0
  26. package/dist/docs/methods/DOC_AUDIT.md +92 -0
  27. package/dist/docs/methods/FORGE_KEEPER.md +16 -5
  28. package/dist/docs/methods/GAUNTLET.md +38 -0
  29. package/dist/docs/methods/PRODUCT_DESIGN_FRONTEND.md +57 -0
  30. package/dist/docs/methods/QA_ENGINEER.md +21 -0
  31. package/dist/docs/methods/RELEASE_MANAGER.md +27 -0
  32. package/dist/docs/methods/SECURITY_AUDITOR.md +12 -1
  33. package/dist/docs/methods/SUB_AGENTS.md +54 -0
  34. package/dist/docs/methods/SYSTEMS_ARCHITECT.md +13 -0
  35. package/dist/docs/methods/TESTING.md +19 -0
  36. package/dist/docs/patterns/README.md +3 -0
  37. package/dist/docs/patterns/ai-eval.ts +63 -0
  38. package/dist/docs/patterns/daemon-process.ts +90 -0
  39. package/dist/docs/patterns/database-migration.ts +65 -0
  40. package/dist/docs/patterns/deploy-preflight.ts +85 -2
  41. package/dist/docs/patterns/design-tokens.ts +338 -0
  42. package/dist/docs/patterns/error-message-categorization.tsx +376 -0
  43. package/dist/wizard/lib/patterns/daemon-process.d.ts +2 -1
  44. package/dist/wizard/lib/patterns/daemon-process.js +89 -1
  45. package/package.json +2 -2
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Pattern: Semantic Design Tokens (primitive -> semantic -> CSS custom properties)
3
+ *
4
+ * Key principles:
5
+ * - NO raw color or type values in components. Components reference SEMANTIC
6
+ * tokens only (e.g., `surface.raised`, `text.muted`, `font.size.body`) —
7
+ * never primitives (e.g., `#4f46e5`, `gray-200`, `16px`).
8
+ * - Two layers: PRIMITIVES (the raw palette/scale — every hex, every step of
9
+ * the type scale) and SEMANTIC roles (what a thing MEANS — `accent`,
10
+ * `danger`, `border.subtle`). Semantic roles map TO primitives.
11
+ * - Tokens are emitted as CSS custom properties (`--vf-color-accent`) so a
12
+ * theme override is a `:root[data-theme=...]` block — not a recompile.
13
+ * - A palette or type pivot becomes a TOKEN edit (re-point semantic roles at
14
+ * different primitives), not a component-by-component rewrite.
15
+ *
16
+ * The trap (field report #351, #3): a rebrand asked for a new accent color and
17
+ * a tighter type scale. Components had hardcoded `#4f46e5` and `text-[15px]`
18
+ * inline in 60+ files. The "one color change" turned into a 60-file grep-and-
19
+ * replace that missed five spots (shipped a two-tone accent for a week) and
20
+ * couldn't support a dark theme at all because the values had no indirection.
21
+ * Scoping all color/type to semantic tokens makes the pivot a single edit.
22
+ *
23
+ * Agents: Galadriel (design system), Legolas (code), Samwise (a11y contrast)
24
+ *
25
+ * Framework adaptations:
26
+ * React/Next.js: This file — emit CSS vars into a <style> at the root, read
27
+ * them via Tailwind theme extension or plain `var(--vf-...)`.
28
+ * Tailwind: map semantic tokens into `theme.extend.colors` /
29
+ * `theme.extend.fontSize` as `var(--vf-...)` so `bg-accent` resolves to the
30
+ * token (see the Tailwind block at the bottom).
31
+ * Vue/Svelte: same CSS-var output; bind `data-theme` on a root element.
32
+ * Django/Rails templates: emit the `:root` block from `tokensToCss()` into a
33
+ * server-rendered <style> tag in the base layout; components use `var(...)`.
34
+ *
35
+ * Pairs with /docs/patterns/component.tsx (components consume tokens, never
36
+ * raw values) and /docs/patterns/combobox.tsx (a11y-critical surfaces).
37
+ */
38
+
39
+ // ── Layer 1: Primitives (the raw palette + scales) ───────────────────────────
40
+ // These are the ONLY place raw values live. Name them by what they ARE
41
+ // (a swatch index, a scale step), never by what they're FOR. A primitive
42
+ // never appears directly in a component.
43
+
44
+ export const primitives = {
45
+ color: {
46
+ // Neutral ramp
47
+ white: '#ffffff',
48
+ black: '#0a0a0a',
49
+ gray50: '#f9fafb',
50
+ gray100: '#f3f4f6',
51
+ gray200: '#e5e7eb',
52
+ gray500: '#6b7280',
53
+ gray700: '#374151',
54
+ gray900: '#111827',
55
+ // Brand ramp
56
+ indigo500: '#6366f1',
57
+ indigo600: '#4f46e5',
58
+ indigo700: '#4338ca',
59
+ // Status ramps
60
+ red500: '#ef4444',
61
+ red600: '#dc2626',
62
+ green500: '#22c55e',
63
+ amber500: '#f59e0b',
64
+ },
65
+ // Type scale — a modular scale, not arbitrary pixels.
66
+ fontSize: {
67
+ xs: '0.75rem',
68
+ sm: '0.875rem',
69
+ base: '1rem',
70
+ lg: '1.125rem',
71
+ xl: '1.5rem',
72
+ '2xl': '2rem',
73
+ },
74
+ fontWeight: {
75
+ regular: '400',
76
+ medium: '500',
77
+ semibold: '600',
78
+ bold: '700',
79
+ },
80
+ lineHeight: {
81
+ tight: '1.2',
82
+ normal: '1.5',
83
+ relaxed: '1.7',
84
+ },
85
+ fontFamily: {
86
+ sans: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
87
+ mono: 'ui-monospace, "SF Mono", "Cascadia Code", monospace',
88
+ },
89
+ } as const;
90
+
91
+ type PrimitiveColor = keyof typeof primitives.color;
92
+ type PrimitiveFontSize = keyof typeof primitives.fontSize;
93
+ type PrimitiveFontWeight = keyof typeof primitives.fontWeight;
94
+ type PrimitiveLineHeight = keyof typeof primitives.lineHeight;
95
+ type PrimitiveFontFamily = keyof typeof primitives.fontFamily;
96
+
97
+ // ── Layer 2: Semantic tokens (what a thing MEANS) ────────────────────────────
98
+ // A semantic token is a NAME FOR A ROLE that points at a primitive. Components
99
+ // reference these. Re-point them to pivot the whole product. The shape is the
100
+ // contract — a theme override must provide every role, so the type system
101
+ // guarantees no role is left un-themed.
102
+
103
+ export type SemanticTokens = {
104
+ color: {
105
+ 'bg.canvas': PrimitiveColor; // page background
106
+ 'bg.raised': PrimitiveColor; // cards, popovers
107
+ 'text.default': PrimitiveColor;
108
+ 'text.muted': PrimitiveColor;
109
+ 'text.on-accent': PrimitiveColor; // text placed on top of `accent`
110
+ 'border.subtle': PrimitiveColor;
111
+ accent: PrimitiveColor; // primary action / brand
112
+ 'accent.hover': PrimitiveColor;
113
+ danger: PrimitiveColor;
114
+ success: PrimitiveColor;
115
+ warning: PrimitiveColor;
116
+ };
117
+ type: {
118
+ 'size.caption': PrimitiveFontSize;
119
+ 'size.body': PrimitiveFontSize;
120
+ 'size.heading': PrimitiveFontSize;
121
+ 'size.display': PrimitiveFontSize;
122
+ 'weight.body': PrimitiveFontWeight;
123
+ 'weight.emphasis': PrimitiveFontWeight;
124
+ 'leading.body': PrimitiveLineHeight;
125
+ 'leading.heading': PrimitiveLineHeight;
126
+ 'family.ui': PrimitiveFontFamily;
127
+ 'family.code': PrimitiveFontFamily;
128
+ };
129
+ };
130
+
131
+ // ── Worked example, step 1: define the default (light) theme ─────────────────
132
+ // Every semantic role points at a primitive. This is the whole brand surface —
133
+ // change these mappings and the product re-skins. Note: no hex here, only
134
+ // references into `primitives`.
135
+
136
+ export const lightTheme: SemanticTokens = {
137
+ color: {
138
+ 'bg.canvas': 'white',
139
+ 'bg.raised': 'gray50',
140
+ 'text.default': 'gray900',
141
+ 'text.muted': 'gray500',
142
+ 'text.on-accent': 'white',
143
+ 'border.subtle': 'gray200',
144
+ accent: 'indigo600',
145
+ 'accent.hover': 'indigo700',
146
+ danger: 'red600',
147
+ success: 'green500',
148
+ warning: 'amber500',
149
+ },
150
+ type: {
151
+ 'size.caption': 'sm',
152
+ 'size.body': 'base',
153
+ 'size.heading': 'xl',
154
+ 'size.display': '2xl',
155
+ 'weight.body': 'regular',
156
+ 'weight.emphasis': 'semibold',
157
+ 'leading.body': 'normal',
158
+ 'leading.heading': 'tight',
159
+ 'family.ui': 'sans',
160
+ 'family.code': 'mono',
161
+ },
162
+ };
163
+
164
+ // ── Worked example, step 2: a theme override (dark) ──────────────────────────
165
+ // Because color roles are indirection, a dark theme is just a different
166
+ // primitive mapping. The type scale is unchanged here — override only what
167
+ // differs. A full rebrand would supply a new `primitives.color` ramp AND a new
168
+ // mapping; both still live in tokens, never in components.
169
+
170
+ export const darkTheme: SemanticTokens = {
171
+ color: {
172
+ 'bg.canvas': 'black',
173
+ 'bg.raised': 'gray900',
174
+ 'text.default': 'gray50',
175
+ 'text.muted': 'gray500',
176
+ 'text.on-accent': 'white',
177
+ 'border.subtle': 'gray700',
178
+ accent: 'indigo500', // lighter accent reads better on dark canvas
179
+ 'accent.hover': 'indigo600',
180
+ danger: 'red500',
181
+ success: 'green500',
182
+ warning: 'amber500',
183
+ },
184
+ type: lightTheme.type, // type scale is theme-invariant in this example
185
+ };
186
+
187
+ // ── Emit: semantic tokens -> CSS custom properties ───────────────────────────
188
+ // `--vf-color-<role>` and `--vf-type-<role>`, with `.` and braces flattened to
189
+ // `-`. The variable VALUE is the resolved primitive, so consumers never touch
190
+ // primitives directly. Emit one block per theme keyed by `data-theme`.
191
+
192
+ const CSS_VAR_PREFIX = '--vf';
193
+
194
+ function cssVarName(group: 'color' | 'type', role: string): string {
195
+ // 'accent.hover' -> '--vf-color-accent-hover'
196
+ const safeRole = role.replace(/[.\s]+/g, '-');
197
+ return `${CSS_VAR_PREFIX}-${group}-${safeRole}`;
198
+ }
199
+
200
+ function resolveColor(role: PrimitiveColor): string {
201
+ return primitives.color[role];
202
+ }
203
+
204
+ function resolveType(
205
+ tokenKey: keyof SemanticTokens['type'],
206
+ ref: string,
207
+ ): string {
208
+ if (tokenKey.startsWith('size.')) return primitives.fontSize[ref as PrimitiveFontSize];
209
+ if (tokenKey.startsWith('weight.')) return primitives.fontWeight[ref as PrimitiveFontWeight];
210
+ if (tokenKey.startsWith('leading.')) return primitives.lineHeight[ref as PrimitiveLineHeight];
211
+ if (tokenKey.startsWith('family.')) return primitives.fontFamily[ref as PrimitiveFontFamily];
212
+ throw new Error(`Unknown type token group: ${tokenKey}`);
213
+ }
214
+
215
+ /** Build the `--vf-*` declarations for one theme as `key: value` lines. */
216
+ export function themeToDeclarations(tokens: SemanticTokens): Record<string, string> {
217
+ const out: Record<string, string> = {};
218
+ for (const [role, ref] of Object.entries(tokens.color)) {
219
+ out[cssVarName('color', role)] = resolveColor(ref as PrimitiveColor);
220
+ }
221
+ for (const [role, ref] of Object.entries(tokens.type)) {
222
+ out[cssVarName('type', role)] = resolveType(
223
+ role as keyof SemanticTokens['type'],
224
+ ref as string,
225
+ );
226
+ }
227
+ return out;
228
+ }
229
+
230
+ /**
231
+ * Render every theme into a single CSS string: `:root` carries the default
232
+ * theme, `[data-theme="<name>"]` carries each override. Drop this into a
233
+ * <style> tag (React: dangerouslySetInnerHTML; templates: server-render).
234
+ */
235
+ export function tokensToCss(themes: {
236
+ default: SemanticTokens;
237
+ overrides?: Record<string, SemanticTokens>;
238
+ }): string {
239
+ const block = (selector: string, decls: Record<string, string>): string => {
240
+ const body = Object.entries(decls)
241
+ .map(([k, v]) => ` ${k}: ${v};`)
242
+ .join('\n');
243
+ return `${selector} {\n${body}\n}`;
244
+ };
245
+
246
+ const parts = [block(':root', themeToDeclarations(themes.default))];
247
+ for (const [name, theme] of Object.entries(themes.overrides ?? {})) {
248
+ parts.push(block(`[data-theme="${name}"]`, themeToDeclarations(theme)));
249
+ }
250
+ return parts.join('\n\n');
251
+ }
252
+
253
+ // ── Worked example, step 3: consume tokens in a component ─────────────────────
254
+ // The component references SEMANTIC tokens via `var(--vf-...)` — never a hex,
255
+ // never a pixel literal. Swapping `data-theme` on any ancestor re-themes it
256
+ // with zero component changes. This is the payoff: the pivot lives in tokens.
257
+
258
+ import type { CSSProperties, PropsWithChildren } from 'react';
259
+
260
+ const tokenColor = (role: keyof SemanticTokens['color']): string =>
261
+ `var(${cssVarName('color', role)})`;
262
+ const tokenType = (role: keyof SemanticTokens['type']): string =>
263
+ `var(${cssVarName('type', role)})`;
264
+
265
+ export function CalloutCard({ children }: PropsWithChildren): JSX.Element {
266
+ const style: CSSProperties = {
267
+ background: tokenColor('bg.raised'),
268
+ color: tokenColor('text.default'),
269
+ border: `1px solid ${tokenColor('border.subtle')}`,
270
+ borderRadius: 8,
271
+ padding: '1rem 1.25rem',
272
+ fontFamily: tokenType('family.ui'),
273
+ fontSize: tokenType('size.body'),
274
+ lineHeight: tokenType('leading.body'),
275
+ };
276
+ const ctaStyle: CSSProperties = {
277
+ background: tokenColor('accent'),
278
+ color: tokenColor('text.on-accent'),
279
+ fontWeight: tokenType('weight.emphasis'),
280
+ border: 'none',
281
+ borderRadius: 6,
282
+ padding: '0.5rem 0.875rem',
283
+ marginTop: '0.75rem',
284
+ cursor: 'pointer',
285
+ };
286
+ return (
287
+ <section style={style}>
288
+ {children}
289
+ {/* No #hex, no 15px — only token references. A rebrand never touches this file. */}
290
+ <button type="button" style={ctaStyle}>
291
+ Continue
292
+ </button>
293
+ </section>
294
+ );
295
+ }
296
+
297
+ // To mount the themes once at the app root (Next.js: in a Server Component
298
+ // layout, or a root <style> in app/layout.tsx):
299
+ //
300
+ // const css = tokensToCss({ default: lightTheme, overrides: { dark: darkTheme } });
301
+ // // <style dangerouslySetInnerHTML={{ __html: css }} />
302
+ // // then: <html data-theme={prefersDark ? 'dark' : undefined}> ... </html>
303
+ //
304
+ // produces, e.g.:
305
+ // :root { --vf-color-accent: #4f46e5; --vf-type-size-body: 1rem; ... }
306
+ // [data-theme="dark"] { --vf-color-accent: #6366f1; ... }
307
+
308
+ // ── Tailwind adaptation ──────────────────────────────────────────────────────
309
+ // Map semantic tokens into the Tailwind theme as `var(--vf-...)`, so utility
310
+ // classes (`bg-accent`, `text-muted`, `text-body`) resolve to tokens and a
311
+ // theme swap still flows through `data-theme`. Components keep using semantic
312
+ // utilities — never `bg-[#4f46e5]`.
313
+ //
314
+ // // tailwind.config.ts
315
+ // export default {
316
+ // theme: {
317
+ // extend: {
318
+ // colors: {
319
+ // canvas: 'var(--vf-color-bg-canvas)',
320
+ // raised: 'var(--vf-color-bg-raised)',
321
+ // accent: { DEFAULT: 'var(--vf-color-accent)', hover: 'var(--vf-color-accent-hover)' },
322
+ // muted: 'var(--vf-color-text-muted)',
323
+ // },
324
+ // fontSize: {
325
+ // caption: 'var(--vf-type-size-caption)',
326
+ // body: 'var(--vf-type-size-body)',
327
+ // heading: 'var(--vf-type-size-heading)',
328
+ // },
329
+ // },
330
+ // },
331
+ // };
332
+ //
333
+ // Lint guardrail (field report #351): forbid raw hex/px in component source so
334
+ // the indirection can't be bypassed. Stylelint: `color-no-hex` + a custom
335
+ // `declaration-property-value-disallowed-list` for `font-size: /\d+px/`; for
336
+ // className strings, an ESLint `no-restricted-syntax` rule banning Tailwind
337
+ // arbitrary-value brackets in color/size utilities (e.g. `bg-[#...]`,
338
+ // `text-[15px]`). The token layer only pays off if primitives can't leak past it.