@polymorphism-tech/morph-spec 2.4.0 → 3.0.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/CLAUDE.md +158 -26
- package/LICENSE +72 -72
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +8 -0
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-deploy.md +529 -0
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -227
- package/content/.claude/commands/morph-troubleshoot.md +122 -122
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/infra/azure-deploy-specialist.md +699 -0
- package/content/.claude/skills/level-0-meta/README.md +7 -0
- package/content/.claude/skills/{checklists → level-0-meta}/morph-checklist.md +117 -117
- package/content/.claude/skills/level-1-workflows/README.md +7 -0
- package/content/.claude/skills/{workflows → level-1-workflows}/morph-replicate.md +213 -213
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-clarify.md +131 -131
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-design.md +213 -205
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-setup.md +106 -92
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-tasks.md +164 -164
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-uiux.md +169 -138
- package/content/.claude/skills/level-2-domains/README.md +14 -0
- package/content/.claude/skills/{specialists → level-2-domains/quality}/testing-specialist.md +126 -126
- package/content/.claude/skills/level-3-technologies/README.md +7 -0
- package/content/.claude/skills/level-4-patterns/README.md +7 -0
- package/content/.claude/skills/specialists/prompt-engineer.md +189 -0
- package/content/.claude/skills/specialists/seo-growth-hacker.md +320 -0
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +742 -358
- package/content/.morph/config/config.template.json +33 -0
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/enforcement-pipeline.md +668 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -158
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -95
- package/content/.morph/examples/scheduled-reports/spec.md +267 -267
- package/content/.morph/examples/state-v3.json +188 -188
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +158 -0
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/hooks/task-completed.js +73 -0
- package/content/.morph/hooks/teammate-idle.js +68 -0
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -220
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-teams-workflow.md +474 -0
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/CONTEXT-FEATURE.md +276 -0
- package/content/.morph/templates/CONTEXT.md +170 -0
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/clarify-questions.md +159 -159
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -74
- package/content/.morph/templates/contracts/Entities.cs +25 -25
- package/content/.morph/templates/contracts/Queries.cs +74 -74
- package/content/.morph/templates/contracts/README.md +74 -74
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/azure-pipelines-deploy.yml +480 -0
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -426
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/templates.md +418 -418
- package/package.json +1 -1
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +83 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -193
- package/src/commands/create-story.js +351 -351
- package/src/commands/deploy.js +780 -0
- package/src/commands/detect-agents.js +34 -6
- package/src/commands/detect.js +104 -104
- package/src/commands/generate-context.js +40 -0
- package/src/commands/generate.js +149 -149
- package/src/commands/lint-fluent.js +352 -352
- package/src/commands/rollback-phase.js +185 -185
- package/src/commands/session-summary.js +291 -291
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/troubleshoot.js +222 -222
- package/src/commands/validate-blazor-state.js +210 -210
- package/src/commands/validate-blazor.js +156 -156
- package/src/commands/validate-css.js +84 -84
- package/src/commands/validate-phase.js +221 -221
- package/src/lib/blazor-concurrency-analyzer.js +288 -288
- package/src/lib/blazor-state-validator.js +291 -291
- package/src/lib/blazor-validator.js +374 -374
- package/src/lib/context-generator.js +513 -0
- package/src/lib/css-validator.js +352 -352
- package/src/lib/design-system-detector.js +187 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/design-system-scaffolder.js +299 -0
- package/src/lib/hook-executor.js +256 -0
- package/src/lib/learning-system.js +520 -520
- package/src/lib/mockup-generator.js +366 -366
- package/src/lib/spec-validator.js +258 -0
- package/src/lib/standards-context-injector.js +287 -0
- package/src/lib/team-orchestrator.js +322 -0
- package/src/lib/troubleshoot-grep.js +194 -194
- package/src/lib/troubleshoot-index.js +144 -144
- package/src/lib/ui-detector.js +350 -350
- package/src/lib/validation-runner.js +65 -13
- package/src/lib/validators/architecture-validator.js +387 -387
- package/src/lib/validators/design-system-validator.js +231 -0
- package/src/lib/validators/package-validator.js +360 -360
- package/src/lib/validators/ui-contrast-validator.js +422 -422
- package/src/utils/file-copier.js +9 -1
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- /package/content/.claude/skills/{checklists → level-0-meta}/code-review.md +0 -0
- /package/content/.claude/skills/{checklists → level-0-meta}/simulation-checklist.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/ai-agents}/ai-system-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/po-pm-advisor.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/standards-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/dotnet-senior.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ef-modeler.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/hangfire-orchestrator.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ms-agent-expert.md +0 -0
- /package/content/.claude/skills/{stacks/dotnet-blazor.md → level-2-domains/frontend/blazor-builder.md} +0 -0
- /package/content/.claude/skills/{stacks/dotnet-nextjs.md → level-2-domains/frontend/nextjs-expert.md} +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/frontend}/ui-ux-designer.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/infrastructure}/azure-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/bicep-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/container-specialist.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/devops-engineer.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/asaas-financial.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/azure-identity.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/clerk-auth.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/resend-email.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/quality}/code-analyzer.md +0 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design System Scaffolder
|
|
3
|
+
*
|
|
4
|
+
* Automatically scaffolds design system files when needed.
|
|
5
|
+
* Called internally by advance-phase.js gate and phase-uiux skill.
|
|
6
|
+
*
|
|
7
|
+
* NOT a user-facing CLI command - internal automation only.
|
|
8
|
+
*
|
|
9
|
+
* MORPH-SPEC 3.0 - Phase 3.5: Design System Enforcement
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
13
|
+
import { join, dirname } from 'path';
|
|
14
|
+
import { parseColors, parseTypography, parseSpacing } from './design-system-generator.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Scaffold a design system by scanning existing CSS or using template
|
|
18
|
+
* @param {string} projectPath - Root path of the project
|
|
19
|
+
* @param {string|null} featureName - Feature name (optional, for feature-level)
|
|
20
|
+
* @param {Object} options - Scaffolding options
|
|
21
|
+
* @param {boolean} options.featureLevel - Create feature-level design system
|
|
22
|
+
* @param {boolean} options.scanExisting - Scan existing CSS files
|
|
23
|
+
* @returns {Object} { created: boolean, path: string, source: 'scanned'|'template' }
|
|
24
|
+
*/
|
|
25
|
+
export function scaffoldDesignSystem(projectPath, featureName = null, options = {}) {
|
|
26
|
+
const { featureLevel = false, scanExisting = true } = options;
|
|
27
|
+
|
|
28
|
+
// Determine output path
|
|
29
|
+
const outputPath = featureLevel && featureName
|
|
30
|
+
? join(projectPath, '.morph/project/outputs', featureName, 'ui-design-system.md')
|
|
31
|
+
: join(projectPath, '.morph/project/design-system.md');
|
|
32
|
+
|
|
33
|
+
// Don't overwrite existing design system
|
|
34
|
+
if (existsSync(outputPath)) {
|
|
35
|
+
return {
|
|
36
|
+
created: false,
|
|
37
|
+
path: outputPath,
|
|
38
|
+
source: 'existing',
|
|
39
|
+
message: 'Design system already exists'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let designSystemContent;
|
|
44
|
+
let source;
|
|
45
|
+
|
|
46
|
+
// Try to scan existing CSS files for design tokens
|
|
47
|
+
if (scanExisting) {
|
|
48
|
+
const scannedTokens = scanExistingCSSForTokens(projectPath);
|
|
49
|
+
|
|
50
|
+
if (scannedTokens.hasTokens) {
|
|
51
|
+
designSystemContent = generateFromScannedTokens(scannedTokens);
|
|
52
|
+
source = 'scanned';
|
|
53
|
+
} else {
|
|
54
|
+
designSystemContent = generateFromTemplate();
|
|
55
|
+
source = 'template';
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
designSystemContent = generateFromTemplate();
|
|
59
|
+
source = 'template';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Ensure directory exists
|
|
63
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
64
|
+
|
|
65
|
+
// Write design system
|
|
66
|
+
writeFileSync(outputPath, designSystemContent, 'utf8');
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
created: true,
|
|
70
|
+
path: outputPath,
|
|
71
|
+
source,
|
|
72
|
+
message: source === 'scanned'
|
|
73
|
+
? 'Design system created from existing CSS variables'
|
|
74
|
+
: 'Design system created from template with sensible defaults'
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Scan existing CSS files for design tokens
|
|
80
|
+
* @param {string} projectPath - Root path of the project
|
|
81
|
+
* @returns {Object} { hasTokens: boolean, colors: Object, typography: Object, spacing: Object }
|
|
82
|
+
*/
|
|
83
|
+
function scanExistingCSSForTokens(projectPath) {
|
|
84
|
+
const result = {
|
|
85
|
+
hasTokens: false,
|
|
86
|
+
colors: { primary: {}, secondary: {}, neutral: {}, semantic: {} },
|
|
87
|
+
typography: { fontFamily: {}, fontSize: {}, fontWeight: {}, lineHeight: {} },
|
|
88
|
+
spacing: {}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Common CSS locations to scan
|
|
92
|
+
const cssLocations = [
|
|
93
|
+
'wwwroot/css/site.css',
|
|
94
|
+
'wwwroot/css/app.css',
|
|
95
|
+
'styles/globals.css',
|
|
96
|
+
'src/styles/globals.css'
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
for (const cssPath of cssLocations) {
|
|
100
|
+
const fullPath = join(projectPath, cssPath);
|
|
101
|
+
if (!existsSync(fullPath)) continue;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
105
|
+
|
|
106
|
+
// Check if file has CSS custom properties
|
|
107
|
+
const hasCustomProps = /:root\s*\{[\s\S]*?--/.test(content);
|
|
108
|
+
if (!hasCustomProps) continue;
|
|
109
|
+
|
|
110
|
+
result.hasTokens = true;
|
|
111
|
+
|
|
112
|
+
// Extract CSS variables
|
|
113
|
+
const varRegex = /--([\w-]+)\s*:\s*([^;]+);/g;
|
|
114
|
+
let match;
|
|
115
|
+
|
|
116
|
+
while ((match = varRegex.exec(content)) !== null) {
|
|
117
|
+
const [, varName, varValue] = match;
|
|
118
|
+
const cleanValue = varValue.trim();
|
|
119
|
+
|
|
120
|
+
// Categorize by variable name
|
|
121
|
+
if (varName.includes('color') || varName.includes('primary') || varName.includes('secondary')) {
|
|
122
|
+
if (varName.includes('primary')) {
|
|
123
|
+
result.colors.primary[varName] = cleanValue;
|
|
124
|
+
} else if (varName.includes('secondary')) {
|
|
125
|
+
result.colors.secondary[varName] = cleanValue;
|
|
126
|
+
} else if (varName.includes('neutral') || varName.includes('gray')) {
|
|
127
|
+
result.colors.neutral[varName] = cleanValue;
|
|
128
|
+
} else if (varName.includes('success') || varName.includes('error') || varName.includes('warning') || varName.includes('info')) {
|
|
129
|
+
result.colors.semantic[varName] = cleanValue;
|
|
130
|
+
}
|
|
131
|
+
} else if (varName.includes('font')) {
|
|
132
|
+
if (varName.includes('family')) {
|
|
133
|
+
result.typography.fontFamily[varName] = cleanValue;
|
|
134
|
+
} else if (varName.includes('size')) {
|
|
135
|
+
result.typography.fontSize[varName] = cleanValue;
|
|
136
|
+
}
|
|
137
|
+
} else if (varName.includes('spacing') || varName.includes('gap') || varName.includes('margin') || varName.includes('padding')) {
|
|
138
|
+
result.spacing[varName] = cleanValue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch (err) {
|
|
142
|
+
// Continue scanning other files
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generate design system content from scanned tokens
|
|
151
|
+
* @param {Object} tokens - Scanned tokens
|
|
152
|
+
* @returns {string} Markdown content
|
|
153
|
+
*/
|
|
154
|
+
function generateFromScannedTokens(tokens) {
|
|
155
|
+
let md = '# Design System\n\n';
|
|
156
|
+
md += '> Auto-generated from existing CSS variables by MORPH-SPEC\n\n';
|
|
157
|
+
|
|
158
|
+
// Colors
|
|
159
|
+
if (Object.keys(tokens.colors.primary).length > 0 ||
|
|
160
|
+
Object.keys(tokens.colors.secondary).length > 0 ||
|
|
161
|
+
Object.keys(tokens.colors.neutral).length > 0 ||
|
|
162
|
+
Object.keys(tokens.colors.semantic).length > 0) {
|
|
163
|
+
md += '## Colors\n\n';
|
|
164
|
+
|
|
165
|
+
if (Object.keys(tokens.colors.primary).length > 0) {
|
|
166
|
+
md += '### Primary Colors\n\n';
|
|
167
|
+
Object.entries(tokens.colors.primary).forEach(([name, value]) => {
|
|
168
|
+
md += `- **${formatVariableName(name)}** (\`${value}\`)\n`;
|
|
169
|
+
});
|
|
170
|
+
md += '\n';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (Object.keys(tokens.colors.secondary).length > 0) {
|
|
174
|
+
md += '### Secondary Colors\n\n';
|
|
175
|
+
Object.entries(tokens.colors.secondary).forEach(([name, value]) => {
|
|
176
|
+
md += `- **${formatVariableName(name)}** (\`${value}\`)\n`;
|
|
177
|
+
});
|
|
178
|
+
md += '\n';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (Object.keys(tokens.colors.neutral).length > 0) {
|
|
182
|
+
md += '### Neutral Colors\n\n';
|
|
183
|
+
Object.entries(tokens.colors.neutral).forEach(([name, value]) => {
|
|
184
|
+
md += `- **${formatVariableName(name)}** (\`${value}\`)\n`;
|
|
185
|
+
});
|
|
186
|
+
md += '\n';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (Object.keys(tokens.colors.semantic).length > 0) {
|
|
190
|
+
md += '### Semantic Colors\n\n';
|
|
191
|
+
Object.entries(tokens.colors.semantic).forEach(([name, value]) => {
|
|
192
|
+
md += `- **${formatVariableName(name)}** (\`${value}\`)\n`;
|
|
193
|
+
});
|
|
194
|
+
md += '\n';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Typography
|
|
199
|
+
if (Object.keys(tokens.typography.fontFamily).length > 0 || Object.keys(tokens.typography.fontSize).length > 0) {
|
|
200
|
+
md += '## Typography\n\n';
|
|
201
|
+
|
|
202
|
+
Object.entries(tokens.typography.fontFamily).forEach(([name, value]) => {
|
|
203
|
+
md += `- **${formatVariableName(name)}**: ${value}\n`;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
Object.entries(tokens.typography.fontSize).forEach(([name, value]) => {
|
|
207
|
+
md += `- **${formatVariableName(name)}**: ${value}\n`;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
md += '\n';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Spacing
|
|
214
|
+
if (Object.keys(tokens.spacing).length > 0) {
|
|
215
|
+
md += '## Spacing\n\n';
|
|
216
|
+
|
|
217
|
+
Object.entries(tokens.spacing).forEach(([name, value]) => {
|
|
218
|
+
md += `- **${formatVariableName(name)}**: ${value}\n`;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
md += '\n';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return md;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Generate design system content from template
|
|
229
|
+
* @returns {string} Markdown content
|
|
230
|
+
*/
|
|
231
|
+
function generateFromTemplate() {
|
|
232
|
+
return `# Design System
|
|
233
|
+
|
|
234
|
+
> Created by MORPH-SPEC with sensible defaults
|
|
235
|
+
|
|
236
|
+
## Colors
|
|
237
|
+
|
|
238
|
+
### Primary Colors
|
|
239
|
+
- **Primary 500** (\`#007bff\`) - Main brand color
|
|
240
|
+
- **Primary 600** (\`#0056b3\`) - Darker variant
|
|
241
|
+
- **Primary 700** (\`#004085\`) - Darkest variant
|
|
242
|
+
|
|
243
|
+
### Secondary Colors
|
|
244
|
+
- **Secondary 500** (\`#6c757d\`) - Secondary color
|
|
245
|
+
- **Secondary 600** (\`#545b62\`) - Darker variant
|
|
246
|
+
|
|
247
|
+
### Neutral Colors
|
|
248
|
+
- **Neutral 100** (\`#f8f9fa\`) - Lightest gray
|
|
249
|
+
- **Neutral 200** (\`#e9ecef\`) - Light gray
|
|
250
|
+
- **Neutral 500** (\`#adb5bd\`) - Medium gray
|
|
251
|
+
- **Neutral 900** (\`#212529\`) - Almost black
|
|
252
|
+
|
|
253
|
+
### Semantic Colors
|
|
254
|
+
- **Success** (\`#28a745\`) - Success state
|
|
255
|
+
- **Error** (\`#dc3545\`) - Error state
|
|
256
|
+
- **Warning** (\`#ffc107\`) - Warning state
|
|
257
|
+
- **Info** (\`#17a2b8\`) - Info state
|
|
258
|
+
|
|
259
|
+
## Typography
|
|
260
|
+
|
|
261
|
+
- **Font Family**: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif
|
|
262
|
+
- **Base Size**: 16px
|
|
263
|
+
- **H1**: 2.5rem
|
|
264
|
+
- **H2**: 2rem
|
|
265
|
+
- **H3**: 1.75rem
|
|
266
|
+
- **Body**: 1rem
|
|
267
|
+
- **Small**: 0.875rem
|
|
268
|
+
|
|
269
|
+
## Spacing
|
|
270
|
+
|
|
271
|
+
- **Spacing XS**: 0.25rem
|
|
272
|
+
- **Spacing SM**: 0.5rem
|
|
273
|
+
- **Spacing MD**: 1rem
|
|
274
|
+
- **Spacing LG**: 1.5rem
|
|
275
|
+
- **Spacing XL**: 2rem
|
|
276
|
+
- **Spacing 2XL**: 3rem
|
|
277
|
+
|
|
278
|
+
## Usage
|
|
279
|
+
|
|
280
|
+
This design system should be used for all UI components in this project.
|
|
281
|
+
|
|
282
|
+
- Use CSS variables: \`var(--color-primary-500)\`
|
|
283
|
+
- Avoid hardcoded hex colors
|
|
284
|
+
- Use spacing tokens for consistent layout
|
|
285
|
+
- Follow typography scale for text sizes
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Format CSS variable name to readable title
|
|
291
|
+
* @param {string} varName - CSS variable name (e.g., 'color-primary-500')
|
|
292
|
+
* @returns {string} Formatted name (e.g., 'Color Primary 500')
|
|
293
|
+
*/
|
|
294
|
+
function formatVariableName(varName) {
|
|
295
|
+
return varName
|
|
296
|
+
.split('-')
|
|
297
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
298
|
+
.join(' ');
|
|
299
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Hook Executor for Claude Code Agent Teams
|
|
3
|
+
*
|
|
4
|
+
* Executes validators in response to Claude Code hook events:
|
|
5
|
+
* - TeammateIdle: When a teammate is waiting (validate their work before proceeding)
|
|
6
|
+
* - TaskCompleted: When a task is marked complete (validate deliverables)
|
|
7
|
+
*
|
|
8
|
+
* Validators can be configured to block on failure (severity: "error") or
|
|
9
|
+
* warn without blocking (severity: "warning").
|
|
10
|
+
*
|
|
11
|
+
* @module hook-executor
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs/promises';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { runValidation } from './validation-runner.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load agents.json to get hook configuration
|
|
20
|
+
* @param {string} projectPath - Root path of the project
|
|
21
|
+
* @returns {Promise<Object>} Parsed agents configuration
|
|
22
|
+
*/
|
|
23
|
+
async function loadAgentsConfig(projectPath) {
|
|
24
|
+
const agentsPath = path.join(projectPath, 'content/.morph/config/agents.json');
|
|
25
|
+
const content = await fs.readFile(agentsPath, 'utf8');
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load feature state to get active agents
|
|
31
|
+
* @param {string} projectPath - Root path of the project
|
|
32
|
+
* @param {string} featureName - Feature name
|
|
33
|
+
* @returns {Promise<Object|null>} Feature state or null if not found
|
|
34
|
+
*/
|
|
35
|
+
async function loadFeatureState(projectPath, featureName) {
|
|
36
|
+
const statePath = path.join(projectPath, '.morph/state.json');
|
|
37
|
+
try {
|
|
38
|
+
const content = await fs.readFile(statePath, 'utf8');
|
|
39
|
+
const state = JSON.parse(content);
|
|
40
|
+
|
|
41
|
+
// Handle both array and object format
|
|
42
|
+
if (Array.isArray(state.features)) {
|
|
43
|
+
return state.features.find(f => f.name === featureName);
|
|
44
|
+
} else if (state.features && typeof state.features === 'object') {
|
|
45
|
+
const feature = state.features[featureName];
|
|
46
|
+
return feature ? { name: featureName, ...feature } : null;
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get validators that should run for a given hook event
|
|
57
|
+
* @param {Object} agentsConfig - Loaded agents.json
|
|
58
|
+
* @param {string[]} activeAgentIds - Active agent IDs for the feature
|
|
59
|
+
* @param {string} hookEvent - Hook event name (e.g., "TeammateIdle", "TaskCompleted")
|
|
60
|
+
* @returns {Array} Validators to run: [{ agentId, hookBehavior, validators }]
|
|
61
|
+
*/
|
|
62
|
+
function getHookValidators(agentsConfig, activeAgentIds, hookEvent) {
|
|
63
|
+
const hookValidators = [];
|
|
64
|
+
|
|
65
|
+
for (const agentId of activeAgentIds) {
|
|
66
|
+
const agent = agentsConfig.agents[agentId];
|
|
67
|
+
if (!agent) continue;
|
|
68
|
+
|
|
69
|
+
// Check if this is a Tier 4 validator agent with hook configuration
|
|
70
|
+
if (agent.tier === 4 && agent.role === 'validator' && agent.relationships?.runs_in === 'hooks') {
|
|
71
|
+
const hookTriggers = agent.relationships.hook_triggers || [];
|
|
72
|
+
|
|
73
|
+
// Check if this hook event triggers this validator
|
|
74
|
+
if (hookTriggers.includes(hookEvent)) {
|
|
75
|
+
hookValidators.push({
|
|
76
|
+
agentId,
|
|
77
|
+
hookBehavior: agent.hook_behavior || {},
|
|
78
|
+
validators: agent.validators || []
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return hookValidators;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Execute hook validators
|
|
89
|
+
* @param {string} projectPath - Root path of the project
|
|
90
|
+
* @param {string} featureName - Feature name
|
|
91
|
+
* @param {string} hookEvent - Hook event name
|
|
92
|
+
* @param {Object} options - Execution options
|
|
93
|
+
* @param {boolean} options.dryRun - If true, only show what would run without executing
|
|
94
|
+
* @param {boolean} options.verbose - Show detailed output
|
|
95
|
+
* @returns {Promise<Object>} Execution result: { passed, blocked, warnings, errors }
|
|
96
|
+
*/
|
|
97
|
+
export async function executeHook(projectPath, featureName, hookEvent, options = {}) {
|
|
98
|
+
const { dryRun = false, verbose = false } = options;
|
|
99
|
+
|
|
100
|
+
// Load configuration
|
|
101
|
+
const agentsConfig = await loadAgentsConfig(projectPath);
|
|
102
|
+
const feature = await loadFeatureState(projectPath, featureName);
|
|
103
|
+
|
|
104
|
+
if (!feature) {
|
|
105
|
+
return {
|
|
106
|
+
passed: false,
|
|
107
|
+
blocked: true,
|
|
108
|
+
errors: [`Feature "${featureName}" not found in state.json`],
|
|
109
|
+
warnings: []
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Get validators for this hook event
|
|
114
|
+
const activeAgentIds = feature.activeAgents || [];
|
|
115
|
+
const hookValidators = getHookValidators(agentsConfig, activeAgentIds, hookEvent);
|
|
116
|
+
|
|
117
|
+
if (hookValidators.length === 0) {
|
|
118
|
+
if (verbose) {
|
|
119
|
+
console.log(`ℹ️ No validators configured for hook event: ${hookEvent}`);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
passed: true,
|
|
123
|
+
blocked: false,
|
|
124
|
+
errors: [],
|
|
125
|
+
warnings: []
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (verbose) {
|
|
130
|
+
console.log(`\n🎯 Executing ${hookEvent} hook for feature: ${featureName}`);
|
|
131
|
+
console.log(` Validators: ${hookValidators.map(v => v.agentId).join(', ')}\n`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (dryRun) {
|
|
135
|
+
console.log('🔍 DRY RUN - Would execute:');
|
|
136
|
+
hookValidators.forEach(v => {
|
|
137
|
+
console.log(` - ${v.agentId} (severity: ${v.hookBehavior.severity || 'warning'})`);
|
|
138
|
+
if (v.hookBehavior.validates) {
|
|
139
|
+
v.hookBehavior.validates.forEach(check => {
|
|
140
|
+
console.log(` • ${check}`);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return {
|
|
145
|
+
passed: true,
|
|
146
|
+
blocked: false,
|
|
147
|
+
errors: [],
|
|
148
|
+
warnings: []
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Execute validators
|
|
153
|
+
const results = {
|
|
154
|
+
passed: true,
|
|
155
|
+
blocked: false,
|
|
156
|
+
errors: [],
|
|
157
|
+
warnings: []
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
for (const hookValidator of hookValidators) {
|
|
161
|
+
const { agentId, hookBehavior, validators } = hookValidator;
|
|
162
|
+
|
|
163
|
+
if (verbose) {
|
|
164
|
+
console.log(`\n🔍 Running ${agentId} validators...`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Run all validators for this agent
|
|
168
|
+
for (const validatorId of validators) {
|
|
169
|
+
try {
|
|
170
|
+
const validationResult = await runValidation(
|
|
171
|
+
projectPath,
|
|
172
|
+
featureName,
|
|
173
|
+
[validatorId],
|
|
174
|
+
{ quiet: !verbose }
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (!validationResult.passed) {
|
|
178
|
+
const severity = hookBehavior.severity || 'warning';
|
|
179
|
+
const blocksOnFail = hookBehavior.blocks_on_fail !== false; // Default true
|
|
180
|
+
|
|
181
|
+
if (severity === 'error' && blocksOnFail) {
|
|
182
|
+
results.passed = false;
|
|
183
|
+
results.blocked = true;
|
|
184
|
+
results.errors.push({
|
|
185
|
+
agentId,
|
|
186
|
+
validatorId,
|
|
187
|
+
issues: validationResult.results[validatorId]?.issues || []
|
|
188
|
+
});
|
|
189
|
+
} else {
|
|
190
|
+
results.warnings.push({
|
|
191
|
+
agentId,
|
|
192
|
+
validatorId,
|
|
193
|
+
issues: validationResult.results[validatorId]?.issues || []
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (err) {
|
|
198
|
+
results.errors.push({
|
|
199
|
+
agentId,
|
|
200
|
+
validatorId,
|
|
201
|
+
error: err.message
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return results;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Format hook execution results for display
|
|
212
|
+
* @param {Object} results - Results from executeHook
|
|
213
|
+
* @param {string} hookEvent - Hook event name
|
|
214
|
+
* @returns {string} Formatted output
|
|
215
|
+
*/
|
|
216
|
+
export function formatHookResults(results, hookEvent) {
|
|
217
|
+
const lines = [];
|
|
218
|
+
|
|
219
|
+
if (results.passed) {
|
|
220
|
+
lines.push(`✅ ${hookEvent} hook: All validators passed`);
|
|
221
|
+
} else {
|
|
222
|
+
lines.push(`❌ ${hookEvent} hook: Validation FAILED`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (results.errors.length > 0) {
|
|
226
|
+
lines.push('\n🚫 ERRORS (blocking):');
|
|
227
|
+
results.errors.forEach(error => {
|
|
228
|
+
lines.push(` ${error.agentId} → ${error.validatorId}`);
|
|
229
|
+
if (error.error) {
|
|
230
|
+
lines.push(` Error: ${error.error}`);
|
|
231
|
+
} else if (error.issues) {
|
|
232
|
+
error.issues.forEach(issue => {
|
|
233
|
+
lines.push(` • ${issue.message} (${issue.file}:${issue.line})`);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (results.warnings.length > 0) {
|
|
240
|
+
lines.push('\n⚠️ WARNINGS (non-blocking):');
|
|
241
|
+
results.warnings.forEach(warning => {
|
|
242
|
+
lines.push(` ${warning.agentId} → ${warning.validatorId}`);
|
|
243
|
+
if (warning.issues) {
|
|
244
|
+
warning.issues.forEach(issue => {
|
|
245
|
+
lines.push(` • ${issue.message} (${issue.file}:${issue.line})`);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (results.blocked) {
|
|
252
|
+
lines.push('\n⛔ Hook execution BLOCKED - fix errors before proceeding');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return lines.join('\n');
|
|
256
|
+
}
|