@polymorphism-tech/morph-spec 4.9.0 → 4.10.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.
- package/README.md +2 -2
- package/bin/morph-spec.js +30 -0
- package/bin/task-manager.js +34 -22
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +35 -98
- package/framework/agents/backend/api-designer.md +3 -0
- package/framework/agents/backend/dotnet-senior.md +3 -0
- package/framework/agents/backend/ef-modeler.md +2 -0
- package/framework/agents/backend/hangfire-orchestrator.md +2 -0
- package/framework/agents/backend/ms-agent-expert.md +2 -0
- package/framework/agents/frontend/blazor-builder.md +2 -0
- package/framework/agents/frontend/nextjs-expert.md +2 -0
- package/framework/agents/infrastructure/azure-architect.md +2 -0
- package/framework/agents/infrastructure/azure-deploy-specialist.md +2 -0
- package/framework/agents/infrastructure/bicep-architect.md +2 -0
- package/framework/agents/infrastructure/container-specialist.md +2 -0
- package/framework/agents/infrastructure/devops-engineer.md +3 -0
- package/framework/agents/infrastructure/infra-architect.md +3 -0
- package/framework/agents/integrations/asaas-financial.md +2 -0
- package/framework/agents/integrations/azure-identity.md +2 -0
- package/framework/agents/integrations/clerk-auth.md +3 -0
- package/framework/agents/integrations/hangfire-integration.md +2 -0
- package/framework/agents/integrations/resend-email.md +2 -0
- package/framework/agents.json +37 -7
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +156 -155
- package/framework/commands/morph-archive.md +33 -27
- package/framework/commands/morph-infra.md +83 -77
- package/framework/commands/morph-preflight.md +97 -55
- package/framework/commands/morph-proposal.md +131 -58
- package/framework/commands/morph-status.md +36 -30
- package/framework/commands/morph-troubleshoot.md +68 -59
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +154 -31
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +7 -84
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +3 -2
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +55 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- package/framework/hooks/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +9 -5
- package/framework/hooks/shared/state-reader.js +27 -3
- package/framework/phases.json +30 -7
- package/framework/rules/csharp-standards.md +3 -0
- package/framework/rules/frontend-standards.md +2 -0
- package/framework/rules/infrastructure-standards.md +3 -0
- package/framework/rules/morph-workflow.md +143 -86
- package/framework/rules/nextjs-standards.md +2 -0
- package/framework/rules/testing-standards.md +3 -0
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +139 -0
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +42 -19
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +8 -5
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +8 -6
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +362 -0
- package/framework/skills/level-0-meta/morph-init/SKILL.md +114 -20
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +362 -0
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +95 -87
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +24 -0
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +43 -43
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +1 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +23 -12
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +247 -0
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +270 -0
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +499 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +38 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +472 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +246 -0
- package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +14 -0
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +312 -0
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +324 -0
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +146 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/docs/onboarding.md +2 -2
- package/package.json +3 -4
- package/src/commands/agents/dispatch-agents.js +50 -3
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +26 -7
- package/src/commands/project/update.js +4 -4
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +27 -53
- package/src/commands/state/state.js +1 -1
- package/src/commands/task/expand.js +100 -0
- package/src/core/paths/output-schema.js +4 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +4 -3
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +22 -16
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack-filter.js +58 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/scripts/setup-infra.js +68 -18
- package/src/utils/agents-installer.js +51 -17
- package/src/utils/claude-md-injector.js +90 -0
- package/src/utils/file-copier.js +0 -1
- package/src/utils/hooks-installer.js +16 -5
- package/src/utils/skills-installer.js +67 -7
- package/CLAUDE.md +0 -98
- package/framework/memory/patterns-learned.md +0 -766
- package/framework/skills/level-0-meta/brainstorming/SKILL.md +0 -137
- package/framework/skills/level-0-meta/frontend-review/SKILL.md +0 -359
- package/framework/skills/level-0-meta/post-implementation/SKILL.md +0 -362
- package/framework/skills/level-0-meta/terminal-title/SKILL.md +0 -61
- package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +0 -65
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -216
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +0 -252
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -383
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +0 -492
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +0 -195
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +0 -271
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +0 -286
- package/src/commands/project/index.js +0 -8
- package/src/core/index.js +0 -10
- package/src/core/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/workflows/index.js +0 -7
- package/src/lib/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- package/src/lib/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- package/src/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- package/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Design System Detector
|
|
3
|
-
*
|
|
4
|
-
* Detects if a design system exists for the project or feature.
|
|
5
|
-
* Checks multiple locations in priority order.
|
|
6
|
-
*
|
|
7
|
-
* @module design-system-detector
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { existsSync, readFileSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Detect design system for project or feature
|
|
15
|
-
* @param {string} projectPath - Root path of the project
|
|
16
|
-
* @param {string|null} featureName - Feature name (optional, checks feature-level if provided)
|
|
17
|
-
* @returns {Object} { hasDesignSystem, path, isProjectLevel, isFeatureLevel, source }
|
|
18
|
-
*/
|
|
19
|
-
export function detectDesignSystem(projectPath, featureName = null) {
|
|
20
|
-
const result = {
|
|
21
|
-
hasDesignSystem: false,
|
|
22
|
-
path: null,
|
|
23
|
-
isProjectLevel: false,
|
|
24
|
-
isFeatureLevel: false,
|
|
25
|
-
source: null
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Priority 1: Feature-level design system (if feature specified)
|
|
29
|
-
if (featureName) {
|
|
30
|
-
const featureDesignSystemPath = join(
|
|
31
|
-
projectPath,
|
|
32
|
-
'.morph/features',
|
|
33
|
-
featureName,
|
|
34
|
-
'2-ui',
|
|
35
|
-
'design-system.md'
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
if (existsSync(featureDesignSystemPath)) {
|
|
39
|
-
return {
|
|
40
|
-
hasDesignSystem: true,
|
|
41
|
-
path: featureDesignSystemPath,
|
|
42
|
-
isProjectLevel: false,
|
|
43
|
-
isFeatureLevel: true,
|
|
44
|
-
source: 'feature-output'
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Priority 2: Project-level design system (markdown)
|
|
50
|
-
const projectDesignSystemPath = join(projectPath, '.morph/context/design-system.md');
|
|
51
|
-
if (existsSync(projectDesignSystemPath)) {
|
|
52
|
-
return {
|
|
53
|
-
hasDesignSystem: true,
|
|
54
|
-
path: projectDesignSystemPath,
|
|
55
|
-
isProjectLevel: true,
|
|
56
|
-
isFeatureLevel: false,
|
|
57
|
-
source: 'project-markdown'
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Priority 3: Generated CSS design system
|
|
62
|
-
const cssDesignSystemPath = join(projectPath, 'wwwroot/css/design-system.css');
|
|
63
|
-
if (existsSync(cssDesignSystemPath)) {
|
|
64
|
-
return {
|
|
65
|
-
hasDesignSystem: true,
|
|
66
|
-
path: cssDesignSystemPath,
|
|
67
|
-
isProjectLevel: true,
|
|
68
|
-
isFeatureLevel: false,
|
|
69
|
-
source: 'generated-css'
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Priority 4: Check common CSS locations for design system variables
|
|
74
|
-
const commonCssLocations = [
|
|
75
|
-
'wwwroot/css/site.css',
|
|
76
|
-
'wwwroot/css/app.css',
|
|
77
|
-
'styles/globals.css',
|
|
78
|
-
'src/styles/globals.css'
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
for (const cssPath of commonCssLocations) {
|
|
82
|
-
const fullPath = join(projectPath, cssPath);
|
|
83
|
-
if (existsSync(fullPath)) {
|
|
84
|
-
// Check if file contains design system variables (CSS custom properties)
|
|
85
|
-
try {
|
|
86
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
87
|
-
|
|
88
|
-
// Look for CSS custom properties that indicate a design system
|
|
89
|
-
// Match :root { ... --variable ... } (with newlines)
|
|
90
|
-
const hasDesignTokens = /:root\s*\{[\s\S]*?--/.test(content);
|
|
91
|
-
|
|
92
|
-
if (hasDesignTokens) {
|
|
93
|
-
return {
|
|
94
|
-
hasDesignSystem: true,
|
|
95
|
-
path: fullPath,
|
|
96
|
-
isProjectLevel: true,
|
|
97
|
-
isFeatureLevel: false,
|
|
98
|
-
source: 'existing-css-variables'
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
// Continue checking other locations
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// No design system found
|
|
108
|
-
return result;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Check if UI agents are active for a feature
|
|
113
|
-
* @param {string} projectPath - Root path of the project
|
|
114
|
-
* @param {string} featureName - Feature name
|
|
115
|
-
* @returns {boolean} True if UI agents are active
|
|
116
|
-
*/
|
|
117
|
-
export function hasUIAgentsActive(projectPath, featureName) {
|
|
118
|
-
try {
|
|
119
|
-
const statePath = join(projectPath, '.morph/state.json');
|
|
120
|
-
|
|
121
|
-
if (!existsSync(statePath)) {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
126
|
-
|
|
127
|
-
// Handle both array and object format
|
|
128
|
-
let feature;
|
|
129
|
-
if (Array.isArray(state.features)) {
|
|
130
|
-
feature = state.features.find(f => f.name === featureName);
|
|
131
|
-
} else if (state.features && typeof state.features === 'object') {
|
|
132
|
-
feature = state.features[featureName];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (!feature || !feature.activeAgents) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// UI design agents that require a design system before implement phase.
|
|
140
|
-
// Implementation-only agents (blazor-builder, nextjs-expert, css-specialist)
|
|
141
|
-
// build from existing designs and do NOT trigger this gate.
|
|
142
|
-
const uiAgents = [
|
|
143
|
-
'ui-designer',
|
|
144
|
-
'uiux-designer',
|
|
145
|
-
'ui-ux-designer',
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
return feature.activeAgents.some(agentId => uiAgents.includes(agentId));
|
|
149
|
-
} catch {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Get recommended design system locations based on project type
|
|
156
|
-
* @param {string} projectPath - Root path of the project
|
|
157
|
-
* @returns {Object} { markdown, css, description }
|
|
158
|
-
*/
|
|
159
|
-
export function getRecommendedLocations(projectPath) {
|
|
160
|
-
try {
|
|
161
|
-
const configPath = join(projectPath, '.morph/config/config.json');
|
|
162
|
-
|
|
163
|
-
let projectType = 'unknown';
|
|
164
|
-
if (existsSync(configPath)) {
|
|
165
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
166
|
-
projectType = config.project?.type || 'unknown';
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Default locations (Blazor)
|
|
170
|
-
const locations = {
|
|
171
|
-
markdown: '.morph/context/design-system.md',
|
|
172
|
-
css: 'wwwroot/css/design-system.css',
|
|
173
|
-
description: 'Project-level design system (shared across features)'
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
// Adjust based on project type
|
|
177
|
-
if (projectType === 'nextjs' || projectType.includes('nextjs')) {
|
|
178
|
-
locations.css = 'styles/design-system.css';
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return locations;
|
|
182
|
-
} catch {
|
|
183
|
-
return {
|
|
184
|
-
markdown: '.morph/context/design-system.md',
|
|
185
|
-
css: 'wwwroot/css/design-system.css',
|
|
186
|
-
description: 'Project-level design system (shared across features)'
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Design System Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects if a design system exists for the project or feature.
|
|
5
|
+
* Checks multiple locations in priority order.
|
|
6
|
+
*
|
|
7
|
+
* @module design-system-detector
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect design system for project or feature
|
|
15
|
+
* @param {string} projectPath - Root path of the project
|
|
16
|
+
* @param {string|null} featureName - Feature name (optional, checks feature-level if provided)
|
|
17
|
+
* @returns {Object} { hasDesignSystem, path, isProjectLevel, isFeatureLevel, source }
|
|
18
|
+
*/
|
|
19
|
+
export function detectDesignSystem(projectPath, featureName = null) {
|
|
20
|
+
const result = {
|
|
21
|
+
hasDesignSystem: false,
|
|
22
|
+
path: null,
|
|
23
|
+
isProjectLevel: false,
|
|
24
|
+
isFeatureLevel: false,
|
|
25
|
+
source: null
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Priority 1: Feature-level design system (if feature specified)
|
|
29
|
+
if (featureName) {
|
|
30
|
+
const featureDesignSystemPath = join(
|
|
31
|
+
projectPath,
|
|
32
|
+
'.morph/features',
|
|
33
|
+
featureName,
|
|
34
|
+
'2-ui',
|
|
35
|
+
'design-system.md'
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (existsSync(featureDesignSystemPath)) {
|
|
39
|
+
return {
|
|
40
|
+
hasDesignSystem: true,
|
|
41
|
+
path: featureDesignSystemPath,
|
|
42
|
+
isProjectLevel: false,
|
|
43
|
+
isFeatureLevel: true,
|
|
44
|
+
source: 'feature-output'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Priority 2: Project-level design system (markdown)
|
|
50
|
+
const projectDesignSystemPath = join(projectPath, '.morph/context/design-system.md');
|
|
51
|
+
if (existsSync(projectDesignSystemPath)) {
|
|
52
|
+
return {
|
|
53
|
+
hasDesignSystem: true,
|
|
54
|
+
path: projectDesignSystemPath,
|
|
55
|
+
isProjectLevel: true,
|
|
56
|
+
isFeatureLevel: false,
|
|
57
|
+
source: 'project-markdown'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Priority 3: Generated CSS design system
|
|
62
|
+
const cssDesignSystemPath = join(projectPath, 'wwwroot/css/design-system.css');
|
|
63
|
+
if (existsSync(cssDesignSystemPath)) {
|
|
64
|
+
return {
|
|
65
|
+
hasDesignSystem: true,
|
|
66
|
+
path: cssDesignSystemPath,
|
|
67
|
+
isProjectLevel: true,
|
|
68
|
+
isFeatureLevel: false,
|
|
69
|
+
source: 'generated-css'
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Priority 4: Check common CSS locations for design system variables
|
|
74
|
+
const commonCssLocations = [
|
|
75
|
+
'wwwroot/css/site.css',
|
|
76
|
+
'wwwroot/css/app.css',
|
|
77
|
+
'styles/globals.css',
|
|
78
|
+
'src/styles/globals.css'
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
for (const cssPath of commonCssLocations) {
|
|
82
|
+
const fullPath = join(projectPath, cssPath);
|
|
83
|
+
if (existsSync(fullPath)) {
|
|
84
|
+
// Check if file contains design system variables (CSS custom properties)
|
|
85
|
+
try {
|
|
86
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
87
|
+
|
|
88
|
+
// Look for CSS custom properties that indicate a design system
|
|
89
|
+
// Match :root { ... --variable ... } (with newlines)
|
|
90
|
+
const hasDesignTokens = /:root\s*\{[\s\S]*?--/.test(content);
|
|
91
|
+
|
|
92
|
+
if (hasDesignTokens) {
|
|
93
|
+
return {
|
|
94
|
+
hasDesignSystem: true,
|
|
95
|
+
path: fullPath,
|
|
96
|
+
isProjectLevel: true,
|
|
97
|
+
isFeatureLevel: false,
|
|
98
|
+
source: 'existing-css-variables'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Continue checking other locations
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// No design system found
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if UI agents are active for a feature
|
|
113
|
+
* @param {string} projectPath - Root path of the project
|
|
114
|
+
* @param {string} featureName - Feature name
|
|
115
|
+
* @returns {boolean} True if UI agents are active
|
|
116
|
+
*/
|
|
117
|
+
export function hasUIAgentsActive(projectPath, featureName) {
|
|
118
|
+
try {
|
|
119
|
+
const statePath = join(projectPath, '.morph/state.json');
|
|
120
|
+
|
|
121
|
+
if (!existsSync(statePath)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
126
|
+
|
|
127
|
+
// Handle both array and object format
|
|
128
|
+
let feature;
|
|
129
|
+
if (Array.isArray(state.features)) {
|
|
130
|
+
feature = state.features.find(f => f.name === featureName);
|
|
131
|
+
} else if (state.features && typeof state.features === 'object') {
|
|
132
|
+
feature = state.features[featureName];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!feature || !feature.activeAgents) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// UI design agents that require a design system before implement phase.
|
|
140
|
+
// Implementation-only agents (blazor-builder, nextjs-expert, css-specialist)
|
|
141
|
+
// build from existing designs and do NOT trigger this gate.
|
|
142
|
+
const uiAgents = [
|
|
143
|
+
'ui-designer',
|
|
144
|
+
'uiux-designer',
|
|
145
|
+
'ui-ux-designer',
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
return feature.activeAgents.some(agentId => uiAgents.includes(agentId));
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get recommended design system locations based on project type
|
|
156
|
+
* @param {string} projectPath - Root path of the project
|
|
157
|
+
* @returns {Object} { markdown, css, description }
|
|
158
|
+
*/
|
|
159
|
+
export function getRecommendedLocations(projectPath) {
|
|
160
|
+
try {
|
|
161
|
+
const configPath = join(projectPath, '.morph/config/config.json');
|
|
162
|
+
|
|
163
|
+
let projectType = 'unknown';
|
|
164
|
+
if (existsSync(configPath)) {
|
|
165
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
166
|
+
projectType = config.project?.type || 'unknown';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Default locations (Blazor)
|
|
170
|
+
const locations = {
|
|
171
|
+
markdown: '.morph/context/design-system.md',
|
|
172
|
+
css: 'wwwroot/css/design-system.css',
|
|
173
|
+
description: 'Project-level design system (shared across features)'
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Adjust based on project type
|
|
177
|
+
if (projectType === 'nextjs' || projectType.includes('nextjs')) {
|
|
178
|
+
locations.css = 'styles/design-system.css';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return locations;
|
|
182
|
+
} catch {
|
|
183
|
+
return {
|
|
184
|
+
markdown: '.morph/context/design-system.md',
|
|
185
|
+
css: 'wwwroot/css/design-system.css',
|
|
186
|
+
description: 'Project-level design system (shared across features)'
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -1,81 +1,179 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* @fileoverview Project Detector
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Scans a project directory for stack, architecture, UI library, and integrations.
|
|
5
|
+
* Used by mcp-setup, morph-init, and session-start hooks.
|
|
6
|
+
*
|
|
7
|
+
* @module detectors/index
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { generateStandards } from './standards-generator.js';
|
|
10
|
-
|
|
11
|
-
// Re-export individual detectors
|
|
12
|
-
export * from './design-system-detector.js';
|
|
10
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @param {
|
|
18
|
-
* @
|
|
14
|
+
* Detect project structure from filesystem evidence.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} targetPath - Root path of the project
|
|
17
|
+
* @param {Object} [opts] - Options
|
|
18
|
+
* @param {boolean} [opts.generateStandards=false] - Whether to generate inferred markdown
|
|
19
|
+
* @returns {{ structure: { stack: string, architecture: string|null, uiLibrary: string|null, integrations: string[], hasVercel: boolean }, inferred: { markdown: string } }}
|
|
19
20
|
*/
|
|
20
|
-
export
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
inferred: null
|
|
21
|
+
export function detectProject(targetPath, opts = {}) {
|
|
22
|
+
const structure = {
|
|
23
|
+
stack: 'unknown',
|
|
24
|
+
architecture: null,
|
|
25
|
+
uiLibrary: null,
|
|
26
|
+
integrations: [],
|
|
27
|
+
hasVercel: false,
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
// ── Package.json analysis ──────────────────────────────────────────────
|
|
32
|
+
const pkgPath = join(targetPath, 'package.json');
|
|
33
|
+
let pkg = null;
|
|
34
|
+
if (existsSync(pkgPath)) {
|
|
35
|
+
try {
|
|
36
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
37
|
+
} catch { /* invalid JSON */ }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (pkg) {
|
|
41
|
+
const allDeps = {
|
|
42
|
+
...pkg.dependencies,
|
|
43
|
+
...pkg.devDependencies,
|
|
44
|
+
};
|
|
45
|
+
const depNames = Object.keys(allDeps);
|
|
46
|
+
const scripts = Object.keys(pkg.scripts || {});
|
|
47
|
+
|
|
48
|
+
// Stack detection from deps
|
|
49
|
+
if (depNames.includes('next')) {
|
|
50
|
+
structure.stack = 'nextjs';
|
|
51
|
+
} else if (depNames.includes('react')) {
|
|
52
|
+
structure.stack = 'react';
|
|
53
|
+
} else if (depNames.includes('vue')) {
|
|
54
|
+
structure.stack = 'vue';
|
|
55
|
+
} else if (depNames.includes('express') || depNames.includes('fastify') || depNames.includes('hono')) {
|
|
56
|
+
structure.stack = 'nodejs';
|
|
57
|
+
} else if (pkg.name) {
|
|
58
|
+
structure.stack = 'nodejs';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// UI library detection
|
|
62
|
+
if (existsSync(join(targetPath, 'components.json')) || depNames.includes('@shadcn/ui')) {
|
|
63
|
+
structure.uiLibrary = 'shadcn';
|
|
64
|
+
} else if (depNames.some(d => d.includes('mudblazor'))) {
|
|
65
|
+
structure.uiLibrary = 'mudblazor';
|
|
66
|
+
} else if (depNames.some(d => d.includes('fluent'))) {
|
|
67
|
+
structure.uiLibrary = 'fluent-ui';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Integrations
|
|
71
|
+
if (depNames.includes('@supabase/supabase-js') || depNames.includes('@supabase/ssr')) {
|
|
72
|
+
structure.integrations.push('supabase');
|
|
73
|
+
}
|
|
74
|
+
if (depNames.includes('@clerk/nextjs') || depNames.includes('@clerk/clerk-sdk-node')) {
|
|
75
|
+
structure.integrations.push('clerk');
|
|
76
|
+
}
|
|
77
|
+
if (depNames.includes('stripe')) {
|
|
78
|
+
structure.integrations.push('stripe');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Vercel detection
|
|
82
|
+
if (depNames.includes('vercel') || scripts.includes('vercel') ||
|
|
83
|
+
scripts.some(s => (pkg.scripts[s] || '').includes('vercel'))) {
|
|
84
|
+
structure.hasVercel = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── .csproj detection ──────────────────────────────────────────────────
|
|
89
|
+
if (structure.stack === 'unknown') {
|
|
90
|
+
// Check for .csproj in common locations
|
|
91
|
+
const csprojLocations = ['.', 'src', 'src/Api', 'src/Web'];
|
|
92
|
+
for (const loc of csprojLocations) {
|
|
93
|
+
try {
|
|
94
|
+
const dir = join(targetPath, loc);
|
|
95
|
+
if (existsSync(dir)) {
|
|
96
|
+
const entries = readdirSyncSafe(dir);
|
|
97
|
+
if (entries.some(e => e.endsWith('.csproj'))) {
|
|
98
|
+
structure.stack = 'dotnet';
|
|
99
|
+
// Check for Blazor
|
|
100
|
+
for (const entry of entries.filter(e => e.endsWith('.csproj'))) {
|
|
101
|
+
try {
|
|
102
|
+
const content = readFileSync(join(dir, entry), 'utf8');
|
|
103
|
+
if (content.includes('Microsoft.AspNetCore.Components') || content.includes('Blazor')) {
|
|
104
|
+
structure.stack = 'blazor';
|
|
105
|
+
}
|
|
106
|
+
} catch { /* ignore */ }
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch { /* ignore */ }
|
|
112
|
+
}
|
|
34
113
|
}
|
|
35
114
|
|
|
36
|
-
//
|
|
37
|
-
if (
|
|
38
|
-
|
|
115
|
+
// ── vercel.json detection ──────────────────────────────────────────────
|
|
116
|
+
if (existsSync(join(targetPath, 'vercel.json'))) {
|
|
117
|
+
structure.hasVercel = true;
|
|
39
118
|
}
|
|
40
119
|
|
|
41
|
-
//
|
|
42
|
-
if (
|
|
43
|
-
|
|
120
|
+
// ── tsconfig.json detection (fallback for stack) ────────────────────────
|
|
121
|
+
if (structure.stack === 'unknown' && existsSync(join(targetPath, 'tsconfig.json'))) {
|
|
122
|
+
structure.stack = 'nodejs';
|
|
44
123
|
}
|
|
45
124
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
125
|
+
// ── Architecture detection ─────────────────────────────────────────────
|
|
126
|
+
try {
|
|
127
|
+
const configPath = join(targetPath, '.morph/config/config.json');
|
|
128
|
+
if (existsSync(configPath)) {
|
|
129
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
130
|
+
if (config.architecture?.style) {
|
|
131
|
+
structure.architecture = config.architecture.style;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch { /* ignore */ }
|
|
135
|
+
|
|
136
|
+
// ── Docker detection ───────────────────────────────────────────────────
|
|
137
|
+
if (existsSync(join(targetPath, 'docker-compose.yml')) ||
|
|
138
|
+
existsSync(join(targetPath, 'docker-compose.yaml')) ||
|
|
139
|
+
existsSync(join(targetPath, 'Dockerfile'))) {
|
|
140
|
+
structure.integrations.push('docker');
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// Fail-safe — return defaults
|
|
49
144
|
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
structure,
|
|
148
|
+
inferred: {
|
|
149
|
+
markdown: opts.generateStandards ? generateInferredMarkdown(structure) : '',
|
|
150
|
+
},
|
|
151
|
+
};
|
|
50
152
|
}
|
|
51
153
|
|
|
52
154
|
/**
|
|
53
|
-
*
|
|
54
|
-
* @param {
|
|
55
|
-
* @returns {string}
|
|
155
|
+
* Safe readdir that returns [] on error.
|
|
156
|
+
* @param {string} dir
|
|
157
|
+
* @returns {string[]}
|
|
56
158
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`- **Type**: ${structure?.stack || 'unknown'}`,
|
|
65
|
-
`- **Architecture**: ${structure?.architecture || 'unknown'}`,
|
|
66
|
-
...(structure?.uiLibrary ? [`- **UI Library**: ${structure.uiLibrary}`] : []),
|
|
67
|
-
'',
|
|
68
|
-
'### Technologies',
|
|
69
|
-
`- **Language**: ${config?.language || 'unknown'}`,
|
|
70
|
-
`- **Version**: ${config?.version || 'unknown'}`,
|
|
71
|
-
`- **Package Manager**: ${config?.packageManager || 'unknown'}`,
|
|
72
|
-
'',
|
|
73
|
-
'### Patterns Detected',
|
|
74
|
-
...(structure?.patterns || []).map(p => `- ${p}`),
|
|
75
|
-
'',
|
|
76
|
-
'### Recommendations',
|
|
77
|
-
...(results.inferred?.recommendations || []).map(r => `- ${r}`)
|
|
78
|
-
];
|
|
159
|
+
function readdirSyncSafe(dir) {
|
|
160
|
+
try {
|
|
161
|
+
return readdirSync(dir);
|
|
162
|
+
} catch {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
79
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Generate a markdown summary from detected structure.
|
|
169
|
+
* @param {Object} structure
|
|
170
|
+
* @returns {string}
|
|
171
|
+
*/
|
|
172
|
+
function generateInferredMarkdown(structure) {
|
|
173
|
+
const lines = [`## Detected Stack: ${structure.stack}`];
|
|
174
|
+
if (structure.architecture) lines.push(`Architecture: ${structure.architecture}`);
|
|
175
|
+
if (structure.uiLibrary) lines.push(`UI Library: ${structure.uiLibrary}`);
|
|
176
|
+
if (structure.integrations.length > 0) lines.push(`Integrations: ${structure.integrations.join(', ')}`);
|
|
177
|
+
if (structure.hasVercel) lines.push(`Vercel: detected`);
|
|
80
178
|
return lines.join('\n');
|
|
81
179
|
}
|
|
@@ -358,7 +358,7 @@ export async function generateFeatureContext(projectPath, featureName) {
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
try {
|
|
361
|
-
const tasksPath = path.join(outputsDir, '
|
|
361
|
+
const tasksPath = path.join(outputsDir, '4-tasks', 'tasks.json');
|
|
362
362
|
const tasksContent = await fs.readFile(tasksPath, 'utf8');
|
|
363
363
|
tasks = JSON.parse(tasksContent).tasks || [];
|
|
364
364
|
} catch (err) {
|
|
@@ -483,7 +483,7 @@ export async function generateFeatureContext(projectPath, featureName) {
|
|
|
483
483
|
PROPOSAL_PATH: getOutputPath(featureName, 'proposal'),
|
|
484
484
|
SPEC_PATH: getOutputPath(featureName, 'spec'),
|
|
485
485
|
CONTRACTS_PATH: getOutputPath(featureName, 'contracts'),
|
|
486
|
-
TASKS_PATH: `.morph/features/${featureName}/
|
|
486
|
+
TASKS_PATH: `.morph/features/${featureName}/4-tasks/tasks.json`,
|
|
487
487
|
DECISIONS_PATH: getOutputPath(featureName, 'decisions'),
|
|
488
488
|
RECAP_PATH: getOutputPath(featureName, 'recap'),
|
|
489
489
|
|