@polymorphism-tech/morph-spec 4.11.1 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +16 -6
- package/framework/hooks/claude-code/post-tool-use/agent-complete.js +122 -0
- package/framework/hooks/shared/dispatch-helpers.js +17 -0
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +248 -14
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +31 -0
- package/framework/standards/frontend/design-system/aesthetic-direction.md +60 -7
- package/framework/standards/frontend/design-system/design-trends.md +171 -0
- package/framework/templates/context/CONTEXT.md +2 -2
- package/framework/templates/context/design-system.md +346 -211
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/package.json +1 -1
- package/src/commands/mcp/mcp-setup.js +28 -6
- package/src/commands/project/init.js +20 -1
- package/src/lib/generators/design-system-generator.js +340 -51
- package/src/lib/validators/design-system/design-system-validator.js +11 -5
- package/src/utils/hooks-installer.js +11 -1
|
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'url';
|
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import ora from 'ora';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
6
7
|
import { logger } from '../../utils/logger.js';
|
|
7
8
|
import {
|
|
8
9
|
pathExists,
|
|
@@ -21,7 +22,8 @@ import {
|
|
|
21
22
|
installAutoMcps,
|
|
22
23
|
installMcpWithCredentials,
|
|
23
24
|
generateSetupInstructions,
|
|
24
|
-
formatMcpStatusTable
|
|
25
|
+
formatMcpStatusTable,
|
|
26
|
+
isRemoteMcp
|
|
25
27
|
} from '../../lib/installers/mcp-installer.js';
|
|
26
28
|
import { setupInfra, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
27
29
|
import { installSkills } from '../../utils/skills-installer.js';
|
|
@@ -277,6 +279,23 @@ export async function initCommand(options) {
|
|
|
277
279
|
}]);
|
|
278
280
|
|
|
279
281
|
if (setupNow) {
|
|
282
|
+
// Remote MCPs — launch OAuth via claude mcp add
|
|
283
|
+
if (isRemoteMcp(entry.install.config)) {
|
|
284
|
+
const url = entry.install.config.url;
|
|
285
|
+
logger.info(` Launching ${capitalizedName} OAuth authentication...`);
|
|
286
|
+
try {
|
|
287
|
+
execSync(`claude mcp add --transport http ${name} ${url}`, {
|
|
288
|
+
stdio: 'inherit',
|
|
289
|
+
timeout: 120000
|
|
290
|
+
});
|
|
291
|
+
logger.success(` ${capitalizedName} MCP configured`);
|
|
292
|
+
manualInstalled[name] = entry;
|
|
293
|
+
} catch {
|
|
294
|
+
logger.warn(` ${capitalizedName} setup cancelled. Run later: claude mcp add --transport http ${name} ${url}`);
|
|
295
|
+
}
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
280
299
|
// Collect credentials
|
|
281
300
|
const credentialValues = {};
|
|
282
301
|
for (const cred of entry.install.credentials) {
|
|
@@ -1,35 +1,133 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MORPH-SPEC Design System Generator Library
|
|
3
3
|
*
|
|
4
|
-
* Reads ui-design-system.md and generates:
|
|
4
|
+
* Reads design-system.md (or ui-design-system.md) and generates:
|
|
5
5
|
* - wwwroot/css/design-system.css (CSS variables)
|
|
6
6
|
* - Themes/FluentDesignTheme.cs (Fluent UI theme)
|
|
7
7
|
* - Themes/MudTheme.cs (MudBlazor theme)
|
|
8
8
|
*
|
|
9
|
+
* Supports BOTH:
|
|
10
|
+
* - New CSS :root format (design-system.md template v2)
|
|
11
|
+
* - Old markdown list format (backward compat)
|
|
12
|
+
*
|
|
9
13
|
* Used both by CLI commands and internal automation.
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
import { readFileSync, existsSync } from 'fs';
|
|
13
17
|
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// CSS :root Helpers
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract all CSS :root blocks from markdown (inside ```css fences)
|
|
24
|
+
* @param {string} markdown
|
|
25
|
+
* @returns {string} Combined CSS content from all :root blocks
|
|
26
|
+
*/
|
|
27
|
+
function extractCSSRootBlocks(markdown) {
|
|
28
|
+
const cssBlocks = [];
|
|
29
|
+
const fenceRegex = /```css\s*\n([\s\S]*?)```/g;
|
|
30
|
+
let match;
|
|
31
|
+
while ((match = fenceRegex.exec(markdown)) !== null) {
|
|
32
|
+
cssBlocks.push(match[1]);
|
|
33
|
+
}
|
|
34
|
+
return cssBlocks.join('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Extract CSS custom properties from :root blocks
|
|
39
|
+
* Returns array of { name, value } for all --xxx: yyy; declarations
|
|
40
|
+
*/
|
|
41
|
+
function extractCSSProperties(cssContent) {
|
|
42
|
+
const props = [];
|
|
43
|
+
const propRegex = /(--[\w-]+)\s*:\s*([^;]+);/g;
|
|
44
|
+
let match;
|
|
45
|
+
while ((match = propRegex.exec(cssContent)) !== null) {
|
|
46
|
+
props.push({ name: match[1].trim(), value: match[2].trim() });
|
|
47
|
+
}
|
|
48
|
+
return props;
|
|
49
|
+
}
|
|
50
|
+
|
|
14
51
|
// ============================================================================
|
|
15
52
|
// Parsing Functions
|
|
16
53
|
// ============================================================================
|
|
17
54
|
|
|
18
55
|
/**
|
|
19
|
-
* Parse colors from markdown
|
|
56
|
+
* Parse colors from markdown — supports CSS :root format and old markdown format
|
|
20
57
|
* @param {string} markdown - Markdown content
|
|
21
|
-
* @returns {Object} Colors object with primary, secondary, neutral, semantic
|
|
58
|
+
* @returns {Object} Colors object with primary, accent, secondary, neutral, semantic, surface, text, other
|
|
22
59
|
*/
|
|
23
60
|
export function parseColors(markdown) {
|
|
24
|
-
const colors = {
|
|
61
|
+
const colors = {
|
|
62
|
+
primary: {},
|
|
63
|
+
accent: {},
|
|
64
|
+
secondary: {},
|
|
65
|
+
neutral: {},
|
|
66
|
+
semantic: {},
|
|
67
|
+
surface: {},
|
|
68
|
+
text: {},
|
|
69
|
+
other: {}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Try CSS :root format first
|
|
73
|
+
const cssContent = extractCSSRootBlocks(markdown);
|
|
74
|
+
const props = extractCSSProperties(cssContent);
|
|
75
|
+
|
|
76
|
+
let cssColorCount = 0;
|
|
77
|
+
|
|
78
|
+
for (const { name, value } of props) {
|
|
79
|
+
if (!name.startsWith('--color-')) continue;
|
|
80
|
+
|
|
81
|
+
const suffix = name.slice('--color-'.length); // e.g. "primary", "primary-light", "neutral-500"
|
|
82
|
+
|
|
83
|
+
if (suffix.startsWith('primary-rgb')) {
|
|
84
|
+
// Store RGB metadata as-is
|
|
85
|
+
colors.primary['rgb'] = value;
|
|
86
|
+
cssColorCount++;
|
|
87
|
+
} else if (suffix.startsWith('primary')) {
|
|
88
|
+
const variant = suffix.slice('primary'.length).replace(/^-/, '') || 'default';
|
|
89
|
+
colors.primary[variant] = value;
|
|
90
|
+
cssColorCount++;
|
|
91
|
+
} else if (suffix.startsWith('accent')) {
|
|
92
|
+
const variant = suffix.slice('accent'.length).replace(/^-/, '') || 'default';
|
|
93
|
+
colors.accent[variant] = value;
|
|
94
|
+
cssColorCount++;
|
|
95
|
+
} else if (suffix.startsWith('neutral-')) {
|
|
96
|
+
const variant = suffix.slice('neutral-'.length);
|
|
97
|
+
colors.neutral[variant] = value;
|
|
98
|
+
cssColorCount++;
|
|
99
|
+
} else if (suffix.startsWith('surface-')) {
|
|
100
|
+
const variant = suffix.slice('surface-'.length);
|
|
101
|
+
colors.surface[variant] = value;
|
|
102
|
+
cssColorCount++;
|
|
103
|
+
} else if (suffix.startsWith('text-')) {
|
|
104
|
+
const variant = suffix.slice('text-'.length);
|
|
105
|
+
colors.text[variant] = value;
|
|
106
|
+
cssColorCount++;
|
|
107
|
+
} else if (suffix === 'success' || suffix === 'warning' || suffix === 'error' || suffix === 'info') {
|
|
108
|
+
colors.semantic[suffix] = value;
|
|
109
|
+
cssColorCount++;
|
|
110
|
+
} else if (suffix === 'border') {
|
|
111
|
+
colors.other['border'] = value;
|
|
112
|
+
cssColorCount++;
|
|
113
|
+
} else {
|
|
114
|
+
// Any other --color-xxx token
|
|
115
|
+
colors.other[suffix] = value;
|
|
116
|
+
cssColorCount++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
25
119
|
|
|
26
|
-
//
|
|
120
|
+
// If CSS format found colors, return them
|
|
121
|
+
if (cssColorCount > 0) {
|
|
122
|
+
return colors;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// FALL BACK to old markdown regex format
|
|
27
126
|
const colorRegex = /(?:^|\n)[-*]\s+\*\*([^*]+)\*\*[^\(]*\(`(#[0-9A-Fa-f]{6})`\)/gm;
|
|
28
127
|
|
|
29
128
|
let match;
|
|
30
129
|
while ((match = colorRegex.exec(markdown)) !== null) {
|
|
31
130
|
const [, name, hex] = match;
|
|
32
|
-
const cleanName = name.trim().toLowerCase().replace(/\s+/g, '-');
|
|
33
131
|
|
|
34
132
|
if (name.includes('Primary')) {
|
|
35
133
|
const variant = name.match(/\d+/)?.[0] || 'default';
|
|
@@ -50,13 +148,56 @@ export function parseColors(markdown) {
|
|
|
50
148
|
}
|
|
51
149
|
|
|
52
150
|
/**
|
|
53
|
-
* Parse typography from markdown
|
|
151
|
+
* Parse typography from markdown — supports CSS :root format and old markdown format
|
|
54
152
|
* @param {string} markdown - Markdown content
|
|
55
|
-
* @returns {Object} Typography
|
|
153
|
+
* @returns {Object} Typography with fontFamily, fontSize, fontWeight, lineHeight
|
|
56
154
|
*/
|
|
57
155
|
export function parseTypography(markdown) {
|
|
58
|
-
const typography = {
|
|
156
|
+
const typography = {
|
|
157
|
+
fontFamily: {},
|
|
158
|
+
fontSize: {},
|
|
159
|
+
fontWeight: {},
|
|
160
|
+
lineHeight: {}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Try CSS :root format first
|
|
164
|
+
const cssContent = extractCSSRootBlocks(markdown);
|
|
165
|
+
const props = extractCSSProperties(cssContent);
|
|
166
|
+
|
|
167
|
+
let cssTypoCount = 0;
|
|
168
|
+
|
|
169
|
+
for (const { name, value } of props) {
|
|
170
|
+
if (name === '--font-display') {
|
|
171
|
+
typography.fontFamily.display = value;
|
|
172
|
+
cssTypoCount++;
|
|
173
|
+
} else if (name === '--font-body') {
|
|
174
|
+
typography.fontFamily.body = value;
|
|
175
|
+
cssTypoCount++;
|
|
176
|
+
} else if (name === '--font-mono') {
|
|
177
|
+
typography.fontFamily.mono = value;
|
|
178
|
+
cssTypoCount++;
|
|
179
|
+
} else if (name.startsWith('--text-')) {
|
|
180
|
+
// Font sizes: --text-xs, --text-sm, --text-base, etc.
|
|
181
|
+
const sizeName = name.slice('--text-'.length);
|
|
182
|
+
typography.fontSize[sizeName] = value;
|
|
183
|
+
cssTypoCount++;
|
|
184
|
+
} else if (name.startsWith('--font-weight-')) {
|
|
185
|
+
const weightName = name.slice('--font-weight-'.length);
|
|
186
|
+
typography.fontWeight[weightName] = value;
|
|
187
|
+
cssTypoCount++;
|
|
188
|
+
} else if (name.startsWith('--leading-')) {
|
|
189
|
+
const leadingName = name.slice('--leading-'.length);
|
|
190
|
+
typography.lineHeight[leadingName] = value;
|
|
191
|
+
cssTypoCount++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
59
194
|
|
|
195
|
+
// If CSS format found typography, return it
|
|
196
|
+
if (cssTypoCount > 0) {
|
|
197
|
+
return typography;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// FALL BACK to old markdown format
|
|
60
201
|
// Match font family
|
|
61
202
|
const fontFamilyRegex = /(?:font[- ]?family|typeface)[:\s]+([^\n]+)/gi;
|
|
62
203
|
const fontMatch = fontFamilyRegex.exec(markdown);
|
|
@@ -82,14 +223,33 @@ export function parseTypography(markdown) {
|
|
|
82
223
|
}
|
|
83
224
|
|
|
84
225
|
/**
|
|
85
|
-
* Parse spacing from markdown
|
|
226
|
+
* Parse spacing from markdown — supports CSS :root format and old markdown format
|
|
86
227
|
* @param {string} markdown - Markdown content
|
|
87
|
-
* @returns {Object} Spacing
|
|
228
|
+
* @returns {Object} Spacing values
|
|
88
229
|
*/
|
|
89
230
|
export function parseSpacing(markdown) {
|
|
90
231
|
const spacing = {};
|
|
91
232
|
|
|
92
|
-
//
|
|
233
|
+
// Try CSS :root format first
|
|
234
|
+
const cssContent = extractCSSRootBlocks(markdown);
|
|
235
|
+
const props = extractCSSProperties(cssContent);
|
|
236
|
+
|
|
237
|
+
let cssSpaceCount = 0;
|
|
238
|
+
|
|
239
|
+
for (const { name, value } of props) {
|
|
240
|
+
if (name.startsWith('--space-')) {
|
|
241
|
+
const spaceName = name.slice('--space-'.length);
|
|
242
|
+
spacing[spaceName] = value;
|
|
243
|
+
cssSpaceCount++;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// If CSS format found spacing, return it
|
|
248
|
+
if (cssSpaceCount > 0) {
|
|
249
|
+
return spacing;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// FALL BACK to old markdown format
|
|
93
253
|
const spacingRegex = /(?:^|\n)[-*]\s+\*\*([^*]+)\*\*[^:]*:\s*(\d+(?:\.\d+)?(?:rem|px|em))/gm;
|
|
94
254
|
let match;
|
|
95
255
|
while ((match = spacingRegex.exec(markdown)) !== null) {
|
|
@@ -103,10 +263,84 @@ export function parseSpacing(markdown) {
|
|
|
103
263
|
return spacing;
|
|
104
264
|
}
|
|
105
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Parse motion tokens from markdown (CSS :root format only)
|
|
268
|
+
* @param {string} markdown - Markdown content
|
|
269
|
+
* @returns {Object} { durations, easings, scroll }
|
|
270
|
+
*/
|
|
271
|
+
export function parseMotion(markdown) {
|
|
272
|
+
const motion = {
|
|
273
|
+
durations: {},
|
|
274
|
+
easings: {},
|
|
275
|
+
scroll: {}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const cssContent = extractCSSRootBlocks(markdown);
|
|
279
|
+
const props = extractCSSProperties(cssContent);
|
|
280
|
+
|
|
281
|
+
for (const { name, value } of props) {
|
|
282
|
+
if (name.startsWith('--duration-')) {
|
|
283
|
+
const key = name.slice('--duration-'.length);
|
|
284
|
+
motion.durations[key] = value;
|
|
285
|
+
} else if (name.startsWith('--easing-')) {
|
|
286
|
+
const key = name.slice('--easing-'.length);
|
|
287
|
+
motion.easings[key] = value;
|
|
288
|
+
} else if (name.startsWith('--scroll-')) {
|
|
289
|
+
const key = name.slice('--scroll-'.length);
|
|
290
|
+
motion.scroll[key] = value;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return motion;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Parse advanced effects tokens from markdown (CSS :root format only)
|
|
299
|
+
* @param {string} markdown - Markdown content
|
|
300
|
+
* @returns {Object} { glass, aurora, perspective, container, viewTransition, noise }
|
|
301
|
+
*/
|
|
302
|
+
export function parseAdvancedEffects(markdown) {
|
|
303
|
+
const effects = {
|
|
304
|
+
glass: {},
|
|
305
|
+
aurora: {},
|
|
306
|
+
perspective: {},
|
|
307
|
+
container: {},
|
|
308
|
+
viewTransition: {},
|
|
309
|
+
noise: {}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const cssContent = extractCSSRootBlocks(markdown);
|
|
313
|
+
const props = extractCSSProperties(cssContent);
|
|
314
|
+
|
|
315
|
+
for (const { name, value } of props) {
|
|
316
|
+
if (name.startsWith('--glass-')) {
|
|
317
|
+
const key = name.slice('--glass-'.length);
|
|
318
|
+
effects.glass[key] = value;
|
|
319
|
+
} else if (name.startsWith('--aurora-')) {
|
|
320
|
+
const key = name.slice('--aurora-'.length);
|
|
321
|
+
effects.aurora[key] = value;
|
|
322
|
+
} else if (name.startsWith('--noise-')) {
|
|
323
|
+
const key = name.slice('--noise-'.length);
|
|
324
|
+
effects.noise[key] = value;
|
|
325
|
+
} else if (name.startsWith('--perspective-')) {
|
|
326
|
+
const key = name.slice('--perspective-'.length);
|
|
327
|
+
effects.perspective[key] = value;
|
|
328
|
+
} else if (name.startsWith('--container-')) {
|
|
329
|
+
const key = name.slice('--container-'.length);
|
|
330
|
+
effects.container[key] = value;
|
|
331
|
+
} else if (name.startsWith('--view-transition-')) {
|
|
332
|
+
const key = name.slice('--view-transition-'.length);
|
|
333
|
+
effects.viewTransition[key] = value;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return effects;
|
|
338
|
+
}
|
|
339
|
+
|
|
106
340
|
/**
|
|
107
341
|
* Parse all design system properties from markdown
|
|
108
|
-
* @param {string} markdownPath - Path to
|
|
109
|
-
* @returns {Object} Parsed design system with colors, typography, spacing
|
|
342
|
+
* @param {string} markdownPath - Path to design-system.md file
|
|
343
|
+
* @returns {Object} Parsed design system with colors, typography, spacing, motion, effects
|
|
110
344
|
*/
|
|
111
345
|
export function parseDesignSystem(markdownPath) {
|
|
112
346
|
if (!existsSync(markdownPath)) {
|
|
@@ -118,7 +352,9 @@ export function parseDesignSystem(markdownPath) {
|
|
|
118
352
|
return {
|
|
119
353
|
colors: parseColors(markdown),
|
|
120
354
|
typography: parseTypography(markdown),
|
|
121
|
-
spacing: parseSpacing(markdown)
|
|
355
|
+
spacing: parseSpacing(markdown),
|
|
356
|
+
motion: parseMotion(markdown),
|
|
357
|
+
effects: parseAdvancedEffects(markdown)
|
|
122
358
|
};
|
|
123
359
|
}
|
|
124
360
|
|
|
@@ -126,6 +362,25 @@ export function parseDesignSystem(markdownPath) {
|
|
|
126
362
|
// Generation Functions
|
|
127
363
|
// ============================================================================
|
|
128
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Helper to emit CSS variable entries for a color category
|
|
367
|
+
*/
|
|
368
|
+
function emitColorCategory(colors, prefix, label) {
|
|
369
|
+
if (Object.keys(colors).length === 0) return '';
|
|
370
|
+
let css = ` /* ${label} */\n`;
|
|
371
|
+
Object.entries(colors).forEach(([variant, value]) => {
|
|
372
|
+
if (variant === 'rgb') {
|
|
373
|
+
css += ` --color-${prefix}-rgb: ${value};\n`;
|
|
374
|
+
} else if (variant === 'default') {
|
|
375
|
+
css += ` --color-${prefix}: ${value};\n`;
|
|
376
|
+
} else {
|
|
377
|
+
css += ` --color-${prefix}-${variant}: ${value};\n`;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
css += `\n`;
|
|
381
|
+
return css;
|
|
382
|
+
}
|
|
383
|
+
|
|
129
384
|
/**
|
|
130
385
|
* Generate CSS variables file
|
|
131
386
|
* @param {Object} colors - Colors object
|
|
@@ -138,32 +393,28 @@ export function generateCSS(colors, typography, spacing) {
|
|
|
138
393
|
css += `/* Do not edit manually - regenerate from ui-design-system.md */\n\n`;
|
|
139
394
|
css += `:root {\n`;
|
|
140
395
|
|
|
141
|
-
// Colors
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
396
|
+
// Colors — handle expanded structure
|
|
397
|
+
css += emitColorCategory(colors.primary, 'primary', 'Primary Colors');
|
|
398
|
+
|
|
399
|
+
// Support both accent (new) and secondary (old)
|
|
400
|
+
if (Object.keys(colors.accent || {}).length > 0) {
|
|
401
|
+
css += emitColorCategory(colors.accent, 'accent', 'Accent Colors');
|
|
402
|
+
}
|
|
403
|
+
if (Object.keys(colors.secondary || {}).length > 0) {
|
|
404
|
+
css += emitColorCategory(colors.secondary, 'secondary', 'Secondary Colors');
|
|
148
405
|
}
|
|
149
406
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
155
|
-
css += `\n`;
|
|
407
|
+
css += emitColorCategory(colors.neutral || {}, 'neutral', 'Neutral Colors');
|
|
408
|
+
|
|
409
|
+
if (Object.keys(colors.surface || {}).length > 0) {
|
|
410
|
+
css += emitColorCategory(colors.surface, 'surface', 'Surface Colors');
|
|
156
411
|
}
|
|
157
412
|
|
|
158
|
-
if (Object.keys(colors.
|
|
159
|
-
css +=
|
|
160
|
-
Object.entries(colors.neutral).forEach(([variant, hex]) => {
|
|
161
|
-
css += ` --color-neutral-${variant}: ${hex};\n`;
|
|
162
|
-
});
|
|
163
|
-
css += `\n`;
|
|
413
|
+
if (Object.keys(colors.text || {}).length > 0) {
|
|
414
|
+
css += emitColorCategory(colors.text, 'text', 'Text Colors');
|
|
164
415
|
}
|
|
165
416
|
|
|
166
|
-
if (Object.keys(colors.semantic).length > 0) {
|
|
417
|
+
if (Object.keys(colors.semantic || {}).length > 0) {
|
|
167
418
|
css += ` /* Semantic Colors */\n`;
|
|
168
419
|
Object.entries(colors.semantic).forEach(([type, hex]) => {
|
|
169
420
|
css += ` --color-${type}: ${hex};\n`;
|
|
@@ -171,10 +422,22 @@ export function generateCSS(colors, typography, spacing) {
|
|
|
171
422
|
css += `\n`;
|
|
172
423
|
}
|
|
173
424
|
|
|
425
|
+
if (Object.keys(colors.other || {}).length > 0) {
|
|
426
|
+
css += ` /* Other Colors */\n`;
|
|
427
|
+
Object.entries(colors.other).forEach(([key, value]) => {
|
|
428
|
+
css += ` --color-${key}: ${value};\n`;
|
|
429
|
+
});
|
|
430
|
+
css += `\n`;
|
|
431
|
+
}
|
|
432
|
+
|
|
174
433
|
// Typography
|
|
175
|
-
|
|
434
|
+
const fontFamilies = typography.fontFamily || {};
|
|
435
|
+
if (fontFamilies.display || fontFamilies.body || fontFamilies.mono || fontFamilies.primary) {
|
|
176
436
|
css += ` /* Typography */\n`;
|
|
177
|
-
css += ` --font-
|
|
437
|
+
if (fontFamilies.display) css += ` --font-display: ${fontFamilies.display};\n`;
|
|
438
|
+
if (fontFamilies.body) css += ` --font-body: ${fontFamilies.body};\n`;
|
|
439
|
+
if (fontFamilies.mono) css += ` --font-mono: ${fontFamilies.mono};\n`;
|
|
440
|
+
if (fontFamilies.primary) css += ` --font-family-primary: ${fontFamilies.primary};\n`;
|
|
178
441
|
css += `\n`;
|
|
179
442
|
}
|
|
180
443
|
|
|
@@ -190,7 +453,12 @@ export function generateCSS(colors, typography, spacing) {
|
|
|
190
453
|
if (Object.keys(spacing).length > 0) {
|
|
191
454
|
css += ` /* Spacing */\n`;
|
|
192
455
|
Object.entries(spacing).forEach(([name, value]) => {
|
|
193
|
-
|
|
456
|
+
// If key is numeric (from --space-N), use --space-N format
|
|
457
|
+
if (/^\d+$/.test(name)) {
|
|
458
|
+
css += ` --space-${name}: ${value};\n`;
|
|
459
|
+
} else {
|
|
460
|
+
css += ` --${name}: ${value};\n`;
|
|
461
|
+
}
|
|
194
462
|
});
|
|
195
463
|
}
|
|
196
464
|
|
|
@@ -207,7 +475,8 @@ export function generateCSS(colors, typography, spacing) {
|
|
|
207
475
|
* @returns {string} C# code
|
|
208
476
|
*/
|
|
209
477
|
export function generateFluentTheme(colors, typography, namespace = 'YourProject.Themes') {
|
|
210
|
-
|
|
478
|
+
// Support both new format (primary.default) and old format (primary['500'])
|
|
479
|
+
const primary = colors.primary.default || colors.primary['500'] || '#0078d4';
|
|
211
480
|
|
|
212
481
|
let csharp = `// Auto-generated by MORPH-SPEC Design System Generator\n`;
|
|
213
482
|
csharp += `// Do not edit manually - regenerate from ui-design-system.md\n\n`;
|
|
@@ -218,7 +487,7 @@ export function generateFluentTheme(colors, typography, namespace = 'YourProject
|
|
|
218
487
|
csharp += ` {\n`;
|
|
219
488
|
csharp += ` return new DesignThemePalette\n`;
|
|
220
489
|
csharp += ` {\n`;
|
|
221
|
-
csharp += ` Accent = "${
|
|
490
|
+
csharp += ` Accent = "${primary}",\n`;
|
|
222
491
|
csharp += ` // Add more theme properties as needed\n`;
|
|
223
492
|
csharp += ` };\n`;
|
|
224
493
|
csharp += ` }\n`;
|
|
@@ -235,8 +504,10 @@ export function generateFluentTheme(colors, typography, namespace = 'YourProject
|
|
|
235
504
|
* @returns {string} C# code
|
|
236
505
|
*/
|
|
237
506
|
export function generateMudTheme(colors, typography, namespace = 'YourProject.Themes') {
|
|
238
|
-
|
|
239
|
-
const
|
|
507
|
+
// Support both new format (primary.default) and old format (primary['500'])
|
|
508
|
+
const primary = colors.primary.default || colors.primary['500'] || '#594ae2';
|
|
509
|
+
// Support accent (new) or secondary (old)
|
|
510
|
+
const secondary = (colors.accent || {}).default || (colors.secondary || {})['500'] || '#ff4081';
|
|
240
511
|
|
|
241
512
|
let csharp = `// Auto-generated by MORPH-SPEC Design System Generator\n`;
|
|
242
513
|
csharp += `// Do not edit manually - regenerate from ui-design-system.md\n\n`;
|
|
@@ -249,8 +520,8 @@ export function generateMudTheme(colors, typography, namespace = 'YourProject.Th
|
|
|
249
520
|
csharp += ` {\n`;
|
|
250
521
|
csharp += ` Palette = new PaletteLight\n`;
|
|
251
522
|
csharp += ` {\n`;
|
|
252
|
-
csharp += ` Primary = "${
|
|
253
|
-
csharp += ` Secondary = "${
|
|
523
|
+
csharp += ` Primary = "${primary}",\n`;
|
|
524
|
+
csharp += ` Secondary = "${secondary}",\n`;
|
|
254
525
|
csharp += ` // Add more palette colors as needed\n`;
|
|
255
526
|
csharp += ` }\n`;
|
|
256
527
|
csharp += ` };\n`;
|
|
@@ -260,9 +531,16 @@ export function generateMudTheme(colors, typography, namespace = 'YourProject.Th
|
|
|
260
531
|
return csharp;
|
|
261
532
|
}
|
|
262
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Count non-empty keys in an object
|
|
536
|
+
*/
|
|
537
|
+
function countTokens(obj) {
|
|
538
|
+
return Object.keys(obj || {}).length;
|
|
539
|
+
}
|
|
540
|
+
|
|
263
541
|
/**
|
|
264
542
|
* Generate all design system files
|
|
265
|
-
* @param {string} designSystemPath - Path to
|
|
543
|
+
* @param {string} designSystemPath - Path to design-system.md
|
|
266
544
|
* @param {Object} options - Generation options
|
|
267
545
|
* @param {string} options.mode - Generation mode: 'fluent', 'mud', or 'both'
|
|
268
546
|
* @param {string} options.namespace - C# namespace
|
|
@@ -272,16 +550,27 @@ export function generateDesignSystem(designSystemPath, options = {}) {
|
|
|
272
550
|
const { mode = 'both', namespace = 'YourProject.Themes' } = options;
|
|
273
551
|
|
|
274
552
|
// Parse design system
|
|
275
|
-
const { colors, typography, spacing } = parseDesignSystem(designSystemPath);
|
|
553
|
+
const { colors, typography, spacing, motion, effects } = parseDesignSystem(designSystemPath);
|
|
276
554
|
|
|
277
555
|
const generated = {
|
|
278
556
|
css: generateCSS(colors, typography, spacing),
|
|
279
557
|
stats: {
|
|
280
|
-
primaryColors:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
558
|
+
primaryColors: countTokens(colors.primary),
|
|
559
|
+
accentColors: countTokens(colors.accent),
|
|
560
|
+
secondaryColors: countTokens(colors.secondary),
|
|
561
|
+
neutralColors: countTokens(colors.neutral),
|
|
562
|
+
semanticColors: countTokens(colors.semantic),
|
|
563
|
+
surfaceColors: countTokens(colors.surface),
|
|
564
|
+
textColors: countTokens(colors.text),
|
|
565
|
+
fontSizes: countTokens(typography.fontSize),
|
|
566
|
+
fontWeights: countTokens(typography.fontWeight),
|
|
567
|
+
lineHeights: countTokens(typography.lineHeight),
|
|
568
|
+
spacingValues: countTokens(spacing),
|
|
569
|
+
motionDurations: countTokens(motion.durations),
|
|
570
|
+
motionEasings: countTokens(motion.easings),
|
|
571
|
+
effectsGlass: countTokens(effects.glass),
|
|
572
|
+
effectsAurora: countTokens(effects.aurora),
|
|
573
|
+
effectsPerspective: countTokens(effects.perspective)
|
|
285
574
|
}
|
|
286
575
|
};
|
|
287
576
|
|
|
@@ -58,12 +58,18 @@ export async function validateDesignSystem(projectPath, featureName = null, opti
|
|
|
58
58
|
return result;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// Build allowed color set
|
|
61
|
+
// Build allowed color set — iterate all color categories
|
|
62
62
|
const allowedColors = new Set();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
const colorCategories = ['primary', 'accent', 'secondary', 'neutral', 'semantic', 'surface', 'text', 'other'];
|
|
64
|
+
for (const category of colorCategories) {
|
|
65
|
+
const catColors = designSystem.colors[category] || {};
|
|
66
|
+
Object.values(catColors).forEach(val => {
|
|
67
|
+
// Only add hex-like values (skip rgb tuples, rgba, var() references, etc.)
|
|
68
|
+
if (typeof val === 'string' && val.startsWith('#')) {
|
|
69
|
+
allowedColors.add(val.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
67
73
|
|
|
68
74
|
// Common system colors (allowed)
|
|
69
75
|
const systemColors = new Set([
|
|
@@ -15,7 +15,7 @@ import { homedir } from 'os';
|
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
16
|
|
|
17
17
|
/** Current hooks schema version — bump when hook definitions change */
|
|
18
|
-
const HOOKS_VERSION = '2.
|
|
18
|
+
const HOOKS_VERSION = '2.13.0';
|
|
19
19
|
|
|
20
20
|
/** Marker for old dispatch.js (v1) */
|
|
21
21
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
@@ -119,6 +119,16 @@ Otherwise respond: {"ok": true}`
|
|
|
119
119
|
}]
|
|
120
120
|
},
|
|
121
121
|
|
|
122
|
+
// === PostToolUse: Agent ===
|
|
123
|
+
{
|
|
124
|
+
event: 'PostToolUse',
|
|
125
|
+
matcher: 'Agent',
|
|
126
|
+
hooks: [{
|
|
127
|
+
type: 'command',
|
|
128
|
+
command: 'node framework/hooks/claude-code/post-tool-use/agent-complete.js'
|
|
129
|
+
}]
|
|
130
|
+
},
|
|
131
|
+
|
|
122
132
|
// === PostToolUse: Bash ===
|
|
123
133
|
{
|
|
124
134
|
event: 'PostToolUse',
|