@polymorphism-tech/morph-spec 4.10.7 → 4.10.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CLAUDE.md +3 -3
  2. package/LICENSE +71 -71
  3. package/README.md +379 -379
  4. package/bin/morph-spec.js +1 -1
  5. package/claude-plugin.json +14 -14
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +206 -206
  8. package/framework/agents/README.md +14 -14
  9. package/framework/agents/architecture/standards-architect.md +159 -159
  10. package/framework/agents/frontend/blazor-builder.md +13 -0
  11. package/framework/agents/frontend/css-specialist.md +87 -0
  12. package/framework/agents/frontend/nextjs-expert.md +13 -0
  13. package/framework/agents/frontend/nextjs-ui-specialist.md +218 -0
  14. package/framework/agents/frontend/ui-ux-designer.md +80 -10
  15. package/framework/agents/infrastructure/azure-architect.md +149 -149
  16. package/framework/agents/infrastructure/infra-architect.md +48 -48
  17. package/framework/agents.json +2268 -2204
  18. package/framework/hooks/README.md +204 -204
  19. package/framework/hooks/claude-code/session-start/inject-morph-context.js +10 -24
  20. package/framework/hooks/shared/phase-utils.js +139 -139
  21. package/framework/hooks/shared/worktree-helpers.js +30 -8
  22. package/framework/rules/csharp-standards.md +13 -13
  23. package/framework/rules/frontend-standards.md +13 -13
  24. package/framework/rules/infrastructure-standards.md +16 -16
  25. package/framework/rules/testing-standards.md +14 -14
  26. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +12 -0
  27. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +254 -254
  28. package/framework/skills/level-0-meta/morph-init/SKILL.md +136 -3
  29. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +247 -247
  30. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +270 -270
  31. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +515 -499
  32. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +508 -500
  33. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +12 -1
  34. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +272 -262
  35. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +317 -311
  36. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +457 -323
  37. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  38. package/framework/standards/STANDARDS.json +2040 -2040
  39. package/framework/standards/frontend/nextjs/motion-patterns.md +321 -0
  40. package/framework/standards/frontend/nextjs/nextjs-patterns.md +17 -17
  41. package/framework/templates/README.md +814 -814
  42. package/framework/templates/code/dotnet/contracts/Commands.cs +74 -74
  43. package/framework/templates/code/dotnet/contracts/Entities.cs +25 -25
  44. package/framework/templates/code/dotnet/contracts/Queries.cs +74 -74
  45. package/framework/templates/code/dotnet/contracts/README.md +74 -74
  46. package/framework/templates/code/dotnet/contracts/api-contracts.cs +173 -173
  47. package/framework/templates/code/dotnet/frontend/component.razor +239 -239
  48. package/framework/templates/code/dotnet/jobs/agent.cs +163 -163
  49. package/framework/templates/code/dotnet/jobs/job.cs +171 -171
  50. package/framework/templates/code/dotnet/test.cs +239 -239
  51. package/framework/templates/code/sql/rls-policy.sql +57 -57
  52. package/framework/templates/code/sql/supabase-migration.sql +100 -100
  53. package/framework/templates/code/sql/supabase-migration.template.sql +113 -113
  54. package/framework/templates/code/typescript/contracts.ts +168 -168
  55. package/framework/templates/context/CONTEXT.md +181 -181
  56. package/framework/templates/context/design-system.md +211 -0
  57. package/framework/templates/docs/proposal.md +182 -182
  58. package/framework/templates/docs/user-stories.md +34 -34
  59. package/framework/templates/examples/design-system-examples.md +357 -357
  60. package/framework/templates/examples/spec-examples.md +90 -90
  61. package/framework/templates/feature/decisions.md +187 -187
  62. package/framework/templates/feature/recap.md +146 -146
  63. package/framework/templates/feature/tasks.md +199 -199
  64. package/framework/templates/infrastructure/azure/README.md +286 -286
  65. package/framework/templates/infrastructure/azure/deploy-checklist.md +426 -426
  66. package/framework/templates/infrastructure/azure/parameters.dev.json +29 -29
  67. package/framework/templates/infrastructure/azure/parameters.prod.json +29 -29
  68. package/framework/templates/infrastructure/azure/parameters.staging.json +29 -29
  69. package/framework/templates/infrastructure/docker/docker-compose.template.yml +67 -67
  70. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  71. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  72. package/framework/templates/infrastructure/docker/easypanel.template.json +54 -54
  73. package/package.json +82 -82
  74. package/src/commands/agents/index.js +13 -13
  75. package/src/commands/project/install-plugin-cmd.js +28 -28
  76. package/src/commands/project/setup-infra-cmd.js +12 -12
  77. package/src/commands/project/worktree.js +58 -2
  78. package/src/commands/tasks/index.js +4 -4
  79. package/src/commands/templates/index.js +5 -5
  80. package/src/commands/utils/index.js +3 -3
  81. package/src/commands/validation/index.js +4 -4
  82. package/src/core/state/phase-state-machine.js +217 -217
  83. package/src/lib/detectors/design-system-detector.js +73 -31
  84. package/src/lib/generators/design-system-generator.js +298 -298
  85. package/src/lib/generators/index.js +10 -10
  86. package/src/lib/generators/metadata-extractor.js +387 -387
  87. package/src/lib/stack-filter.js +65 -65
  88. package/src/lib/standards/index.js +7 -7
  89. package/src/lib/standards/standards-context-injector.js +302 -302
  90. package/src/lib/validators/architecture/architecture-validator.js +387 -387
  91. package/src/lib/validators/architecture/index.js +7 -7
  92. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +316 -316
  93. package/src/lib/validators/blazor/blazor-state-validator.js +279 -279
  94. package/src/lib/validators/blazor/blazor-validator.js +369 -369
  95. package/src/lib/validators/blazor/index.js +9 -9
  96. package/src/lib/validators/content/content-validator.js +287 -287
  97. package/src/lib/validators/content/index.js +7 -7
  98. package/src/lib/validators/contracts/index.js +7 -7
  99. package/src/lib/validators/css/css-validator.js +352 -352
  100. package/src/lib/validators/css/index.js +7 -7
  101. package/src/lib/validators/design-system/design-system-validator.js +231 -231
  102. package/src/lib/validators/design-system/index.js +7 -7
  103. package/src/lib/validators/nextjs/index.js +6 -6
  104. package/src/lib/validators/nextjs/next-component-validator.js +183 -183
  105. package/src/lib/validators/packages/index.js +7 -7
  106. package/src/lib/validators/packages/package-validator.js +360 -360
  107. package/src/lib/validators/shared/index.js +12 -12
  108. package/src/lib/validators/shared/issue-counter.js +18 -18
  109. package/src/lib/validators/shared/result-formatter.js +124 -124
  110. package/src/lib/validators/ui/index.js +7 -7
  111. package/src/lib/validators/ui/ui-contrast-validator.js +422 -422
  112. package/src/scripts/global-install.js +58 -58
  113. package/src/scripts/install-plugin.js +126 -126
  114. package/src/utils/claude-md-injector.js +90 -90
  115. package/src/utils/file-copier.js +200 -196
  116. package/src/utils/hooks-installer.js +556 -494
  117. package/src/utils/logger.js +32 -32
  118. package/src/utils/skills-installer.js +178 -178
  119. package/src/utils/version-checker.js +175 -175
  120. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  121. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  122. package/framework/templates/.idea/morph-templates.xml +0 -92
  123. package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
@@ -1,217 +1,217 @@
1
- /**
2
- * Phase State Machine - Enforces valid phase transitions
3
- *
4
- * Prevents invalid workflow jumps and ensures sequential phase progression.
5
- * Based on the MORPH 5-phase workflow.
6
- */
7
-
8
- /**
9
- * Valid phase transitions map
10
- * Each phase can only transition to specific next phases
11
- */
12
- const VALID_TRANSITIONS = {
13
- 'proposal': ['setup'],
14
- 'setup': ['uiux', 'design'], // Can skip UI/UX if no frontend
15
- 'uiux': ['design'],
16
- 'design': ['clarify'],
17
- 'clarify': ['plan'],
18
- 'plan': ['tasks'],
19
- 'tasks': ['implement'],
20
- 'implement': ['sync', 'archived'], // Can skip sync
21
- 'sync': ['archived']
22
- };
23
-
24
- /**
25
- * Phase display names for error messages
26
- */
27
- const PHASE_NAMES = {
28
- 'proposal': 'Proposal (Phase 0)',
29
- 'setup': 'Setup (Phase 1)',
30
- 'uiux': 'UI/UX Design (Phase 1.5)',
31
- 'design': 'Design (Phase 2)',
32
- 'clarify': 'Clarify (Phase 3)',
33
- 'plan': 'Plan (Phase 4)',
34
- 'tasks': 'Tasks (Phase 5)',
35
- 'implement': 'Implement (Phase 6)',
36
- 'sync': 'Sync (Phase 7)',
37
- 'archived': 'Archived'
38
- };
39
-
40
- /**
41
- * Optional phases that can be skipped
42
- */
43
- const OPTIONAL_PHASES = ['uiux', 'sync'];
44
-
45
- /**
46
- * Validate if a phase transition is allowed
47
- * @param {string} fromPhase - Current phase
48
- * @param {string} toPhase - Target phase
49
- * @returns {boolean} True if transition is valid
50
- */
51
- export function isValidTransition(fromPhase, toPhase) {
52
- if (!fromPhase || !toPhase) {
53
- return false;
54
- }
55
-
56
- const validNextPhases = VALID_TRANSITIONS[fromPhase];
57
- return validNextPhases ? validNextPhases.includes(toPhase) : false;
58
- }
59
-
60
- /**
61
- * Get list of valid next phases for a given phase
62
- * @param {string} currentPhase - Current phase
63
- * @returns {string[]} Array of valid next phase names
64
- */
65
- export function getValidNextPhases(currentPhase) {
66
- return VALID_TRANSITIONS[currentPhase] || [];
67
- }
68
-
69
- /**
70
- * Get display name for a phase
71
- * @param {string} phase - Phase identifier
72
- * @returns {string} Human-readable phase name
73
- */
74
- export function getPhaseDisplayName(phase) {
75
- return PHASE_NAMES[phase] || phase;
76
- }
77
-
78
- /**
79
- * Check if a phase is optional
80
- * @param {string} phase - Phase identifier
81
- * @returns {boolean} True if phase is optional
82
- */
83
- export function isOptionalPhase(phase) {
84
- return OPTIONAL_PHASES.includes(phase);
85
- }
86
-
87
- /**
88
- * Validate transition and throw error if invalid
89
- * @param {string} fromPhase - Current phase
90
- * @param {string} toPhase - Target phase
91
- * @throws {Error} If transition is invalid
92
- */
93
- export function validateTransition(fromPhase, toPhase) {
94
- if (!fromPhase) {
95
- throw new Error('Current phase is undefined. Cannot validate transition.');
96
- }
97
-
98
- if (!toPhase) {
99
- throw new Error('Target phase is undefined. Cannot validate transition.');
100
- }
101
-
102
- if (!isValidTransition(fromPhase, toPhase)) {
103
- const validPhases = getValidNextPhases(fromPhase);
104
- const fromDisplay = getPhaseDisplayName(fromPhase);
105
- const toDisplay = getPhaseDisplayName(toPhase);
106
-
107
- throw new Error(
108
- `Invalid phase transition: ${fromDisplay} → ${toDisplay}\n\n` +
109
- `Valid next phases from ${fromDisplay}:\n` +
110
- validPhases.map(p => ` • ${getPhaseDisplayName(p)}`).join('\n') +
111
- '\n\n' +
112
- 'You cannot skip phases in the MORPH workflow.\n' +
113
- 'To override this check, use the --force flag (not recommended).'
114
- );
115
- }
116
- }
117
-
118
- /**
119
- * Get the sequential order of phases
120
- * @returns {string[]} Ordered list of phases
121
- */
122
- export function getPhaseSequence() {
123
- return [
124
- 'proposal',
125
- 'setup',
126
- 'uiux', // optional
127
- 'design',
128
- 'clarify',
129
- 'plan',
130
- 'tasks',
131
- 'implement',
132
- 'sync', // optional
133
- 'archived'
134
- ];
135
- }
136
-
137
- /**
138
- * Get phase order index (for comparison)
139
- * @param {string} phase - Phase identifier
140
- * @returns {number} Index in sequence (-1 if not found)
141
- */
142
- export function getPhaseOrder(phase) {
143
- return getPhaseSequence().indexOf(phase);
144
- }
145
-
146
- /**
147
- * Check if target phase is ahead of current phase
148
- * @param {string} currentPhase - Current phase
149
- * @param {string} targetPhase - Target phase
150
- * @returns {boolean} True if target is ahead
151
- */
152
- export function isPhaseAhead(currentPhase, targetPhase) {
153
- return getPhaseOrder(targetPhase) > getPhaseOrder(currentPhase);
154
- }
155
-
156
- /**
157
- * Check if target phase is behind current phase (rollback scenario)
158
- * @param {string} currentPhase - Current phase
159
- * @param {string} targetPhase - Target phase
160
- * @returns {boolean} True if target is behind
161
- */
162
- export function isPhaseRollback(currentPhase, targetPhase) {
163
- return getPhaseOrder(targetPhase) < getPhaseOrder(currentPhase);
164
- }
165
-
166
- /**
167
- * Get all skipped phases between two phases
168
- * @param {string} fromPhase - Starting phase
169
- * @param {string} toPhase - Ending phase
170
- * @returns {string[]} Array of skipped phases
171
- */
172
- export function getSkippedPhases(fromPhase, toPhase) {
173
- const sequence = getPhaseSequence();
174
- const fromIndex = sequence.indexOf(fromPhase);
175
- const toIndex = sequence.indexOf(toPhase);
176
-
177
- if (fromIndex === -1 || toIndex === -1 || toIndex <= fromIndex) {
178
- return [];
179
- }
180
-
181
- return sequence.slice(fromIndex + 1, toIndex);
182
- }
183
-
184
- /**
185
- * Validate that no required phases were skipped
186
- * @param {string} fromPhase - Starting phase
187
- * @param {string} toPhase - Ending phase
188
- * @throws {Error} If required phases were skipped
189
- */
190
- export function validateNoRequiredPhasesSkipped(fromPhase, toPhase) {
191
- const skipped = getSkippedPhases(fromPhase, toPhase);
192
- const requiredSkipped = skipped.filter(p => !isOptionalPhase(p));
193
-
194
- if (requiredSkipped.length > 0) {
195
- throw new Error(
196
- `Cannot skip required phases:\n` +
197
- requiredSkipped.map(p => ` • ${getPhaseDisplayName(p)}`).join('\n') +
198
- '\n\nYou must complete each required phase in sequence.'
199
- );
200
- }
201
- }
202
-
203
- /**
204
- * Get comprehensive phase information
205
- * @param {string} phase - Phase identifier
206
- * @returns {Object} Phase metadata
207
- */
208
- export function getPhaseInfo(phase) {
209
- return {
210
- id: phase,
211
- displayName: getPhaseDisplayName(phase),
212
- order: getPhaseOrder(phase),
213
- isOptional: isOptionalPhase(phase),
214
- validNextPhases: getValidNextPhases(phase),
215
- canTransitionTo: (targetPhase) => isValidTransition(phase, targetPhase)
216
- };
217
- }
1
+ /**
2
+ * Phase State Machine - Enforces valid phase transitions
3
+ *
4
+ * Prevents invalid workflow jumps and ensures sequential phase progression.
5
+ * Based on the MORPH 5-phase workflow.
6
+ */
7
+
8
+ /**
9
+ * Valid phase transitions map
10
+ * Each phase can only transition to specific next phases
11
+ */
12
+ const VALID_TRANSITIONS = {
13
+ 'proposal': ['setup'],
14
+ 'setup': ['uiux', 'design'], // Can skip UI/UX if no frontend
15
+ 'uiux': ['design'],
16
+ 'design': ['clarify'],
17
+ 'clarify': ['plan'],
18
+ 'plan': ['tasks'],
19
+ 'tasks': ['implement'],
20
+ 'implement': ['sync', 'archived'], // Can skip sync
21
+ 'sync': ['archived']
22
+ };
23
+
24
+ /**
25
+ * Phase display names for error messages
26
+ */
27
+ const PHASE_NAMES = {
28
+ 'proposal': 'Proposal (Phase 0)',
29
+ 'setup': 'Setup (Phase 1)',
30
+ 'uiux': 'UI/UX Design (Phase 1.5)',
31
+ 'design': 'Design (Phase 2)',
32
+ 'clarify': 'Clarify (Phase 3)',
33
+ 'plan': 'Plan (Phase 4)',
34
+ 'tasks': 'Tasks (Phase 5)',
35
+ 'implement': 'Implement (Phase 6)',
36
+ 'sync': 'Sync (Phase 7)',
37
+ 'archived': 'Archived'
38
+ };
39
+
40
+ /**
41
+ * Optional phases that can be skipped
42
+ */
43
+ const OPTIONAL_PHASES = ['uiux', 'sync'];
44
+
45
+ /**
46
+ * Validate if a phase transition is allowed
47
+ * @param {string} fromPhase - Current phase
48
+ * @param {string} toPhase - Target phase
49
+ * @returns {boolean} True if transition is valid
50
+ */
51
+ export function isValidTransition(fromPhase, toPhase) {
52
+ if (!fromPhase || !toPhase) {
53
+ return false;
54
+ }
55
+
56
+ const validNextPhases = VALID_TRANSITIONS[fromPhase];
57
+ return validNextPhases ? validNextPhases.includes(toPhase) : false;
58
+ }
59
+
60
+ /**
61
+ * Get list of valid next phases for a given phase
62
+ * @param {string} currentPhase - Current phase
63
+ * @returns {string[]} Array of valid next phase names
64
+ */
65
+ export function getValidNextPhases(currentPhase) {
66
+ return VALID_TRANSITIONS[currentPhase] || [];
67
+ }
68
+
69
+ /**
70
+ * Get display name for a phase
71
+ * @param {string} phase - Phase identifier
72
+ * @returns {string} Human-readable phase name
73
+ */
74
+ export function getPhaseDisplayName(phase) {
75
+ return PHASE_NAMES[phase] || phase;
76
+ }
77
+
78
+ /**
79
+ * Check if a phase is optional
80
+ * @param {string} phase - Phase identifier
81
+ * @returns {boolean} True if phase is optional
82
+ */
83
+ export function isOptionalPhase(phase) {
84
+ return OPTIONAL_PHASES.includes(phase);
85
+ }
86
+
87
+ /**
88
+ * Validate transition and throw error if invalid
89
+ * @param {string} fromPhase - Current phase
90
+ * @param {string} toPhase - Target phase
91
+ * @throws {Error} If transition is invalid
92
+ */
93
+ export function validateTransition(fromPhase, toPhase) {
94
+ if (!fromPhase) {
95
+ throw new Error('Current phase is undefined. Cannot validate transition.');
96
+ }
97
+
98
+ if (!toPhase) {
99
+ throw new Error('Target phase is undefined. Cannot validate transition.');
100
+ }
101
+
102
+ if (!isValidTransition(fromPhase, toPhase)) {
103
+ const validPhases = getValidNextPhases(fromPhase);
104
+ const fromDisplay = getPhaseDisplayName(fromPhase);
105
+ const toDisplay = getPhaseDisplayName(toPhase);
106
+
107
+ throw new Error(
108
+ `Invalid phase transition: ${fromDisplay} → ${toDisplay}\n\n` +
109
+ `Valid next phases from ${fromDisplay}:\n` +
110
+ validPhases.map(p => ` • ${getPhaseDisplayName(p)}`).join('\n') +
111
+ '\n\n' +
112
+ 'You cannot skip phases in the MORPH workflow.\n' +
113
+ 'To override this check, use the --force flag (not recommended).'
114
+ );
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Get the sequential order of phases
120
+ * @returns {string[]} Ordered list of phases
121
+ */
122
+ export function getPhaseSequence() {
123
+ return [
124
+ 'proposal',
125
+ 'setup',
126
+ 'uiux', // optional
127
+ 'design',
128
+ 'clarify',
129
+ 'plan',
130
+ 'tasks',
131
+ 'implement',
132
+ 'sync', // optional
133
+ 'archived'
134
+ ];
135
+ }
136
+
137
+ /**
138
+ * Get phase order index (for comparison)
139
+ * @param {string} phase - Phase identifier
140
+ * @returns {number} Index in sequence (-1 if not found)
141
+ */
142
+ export function getPhaseOrder(phase) {
143
+ return getPhaseSequence().indexOf(phase);
144
+ }
145
+
146
+ /**
147
+ * Check if target phase is ahead of current phase
148
+ * @param {string} currentPhase - Current phase
149
+ * @param {string} targetPhase - Target phase
150
+ * @returns {boolean} True if target is ahead
151
+ */
152
+ export function isPhaseAhead(currentPhase, targetPhase) {
153
+ return getPhaseOrder(targetPhase) > getPhaseOrder(currentPhase);
154
+ }
155
+
156
+ /**
157
+ * Check if target phase is behind current phase (rollback scenario)
158
+ * @param {string} currentPhase - Current phase
159
+ * @param {string} targetPhase - Target phase
160
+ * @returns {boolean} True if target is behind
161
+ */
162
+ export function isPhaseRollback(currentPhase, targetPhase) {
163
+ return getPhaseOrder(targetPhase) < getPhaseOrder(currentPhase);
164
+ }
165
+
166
+ /**
167
+ * Get all skipped phases between two phases
168
+ * @param {string} fromPhase - Starting phase
169
+ * @param {string} toPhase - Ending phase
170
+ * @returns {string[]} Array of skipped phases
171
+ */
172
+ export function getSkippedPhases(fromPhase, toPhase) {
173
+ const sequence = getPhaseSequence();
174
+ const fromIndex = sequence.indexOf(fromPhase);
175
+ const toIndex = sequence.indexOf(toPhase);
176
+
177
+ if (fromIndex === -1 || toIndex === -1 || toIndex <= fromIndex) {
178
+ return [];
179
+ }
180
+
181
+ return sequence.slice(fromIndex + 1, toIndex);
182
+ }
183
+
184
+ /**
185
+ * Validate that no required phases were skipped
186
+ * @param {string} fromPhase - Starting phase
187
+ * @param {string} toPhase - Ending phase
188
+ * @throws {Error} If required phases were skipped
189
+ */
190
+ export function validateNoRequiredPhasesSkipped(fromPhase, toPhase) {
191
+ const skipped = getSkippedPhases(fromPhase, toPhase);
192
+ const requiredSkipped = skipped.filter(p => !isOptionalPhase(p));
193
+
194
+ if (requiredSkipped.length > 0) {
195
+ throw new Error(
196
+ `Cannot skip required phases:\n` +
197
+ requiredSkipped.map(p => ` • ${getPhaseDisplayName(p)}`).join('\n') +
198
+ '\n\nYou must complete each required phase in sequence.'
199
+ );
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Get comprehensive phase information
205
+ * @param {string} phase - Phase identifier
206
+ * @returns {Object} Phase metadata
207
+ */
208
+ export function getPhaseInfo(phase) {
209
+ return {
210
+ id: phase,
211
+ displayName: getPhaseDisplayName(phase),
212
+ order: getPhaseOrder(phase),
213
+ isOptional: isOptionalPhase(phase),
214
+ validNextPhases: getValidNextPhases(phase),
215
+ canTransitionTo: (targetPhase) => isValidTransition(phase, targetPhase)
216
+ };
217
+ }
@@ -7,9 +7,42 @@
7
7
  * @module design-system-detector
8
8
  */
9
9
 
10
- import { existsSync, readFileSync } from 'fs';
10
+ import { existsSync, readFileSync, readdirSync } from 'fs';
11
11
  import { join } from 'path';
12
12
 
13
+ const SKIP_DIRS = new Set(['node_modules', '.git', '.morph', 'dist', 'build', '.next', 'obj', 'bin', '.vs']);
14
+ const CSS_SCAN_MAX_DEPTH = 6;
15
+
16
+ /**
17
+ * Recursively walk a directory collecting files with given extensions.
18
+ * @param {string} dir - Directory to walk
19
+ * @param {string[]} extensions - File extensions to collect (e.g. ['.css', '.scss'])
20
+ * @param {string[]} files - Accumulator array (mutated in-place)
21
+ * @param {number} depth - Current recursion depth
22
+ * @param {number} maxDepth - Maximum recursion depth
23
+ */
24
+ function walkDirForCss(dir, extensions, files, depth, maxDepth) {
25
+ if (depth > maxDepth) return;
26
+ let entries;
27
+ try {
28
+ entries = readdirSync(dir, { withFileTypes: true });
29
+ } catch {
30
+ return;
31
+ }
32
+ for (const entry of entries) {
33
+ if (entry.isDirectory()) {
34
+ if (!SKIP_DIRS.has(entry.name)) {
35
+ walkDirForCss(join(dir, entry.name), extensions, files, depth + 1, maxDepth);
36
+ }
37
+ } else if (entry.isFile()) {
38
+ const lower = entry.name.toLowerCase();
39
+ if (extensions.some(ext => lower.endsWith(ext))) {
40
+ files.push(join(dir, entry.name));
41
+ }
42
+ }
43
+ }
44
+ }
45
+
13
46
  /**
14
47
  * Detect design system for project or feature
15
48
  * @param {string} projectPath - Root path of the project
@@ -70,37 +103,46 @@ export function detectDesignSystem(projectPath, featureName = null) {
70
103
  };
71
104
  }
72
105
 
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
106
+ // Priority 4: Recursively scan frontend root directories for CSS/SCSS with design tokens
107
+ const frontendRoots = ['wwwroot', 'styles', 'src/styles', 'src', 'public'];
108
+ const extensions = ['.css', '.scss'];
109
+ const collected = [];
110
+
111
+ for (const root of frontendRoots) {
112
+ const rootPath = join(projectPath, root);
113
+ if (existsSync(rootPath)) {
114
+ walkDirForCss(rootPath, extensions, collected, 0, CSS_SCAN_MAX_DEPTH);
115
+ }
116
+ }
117
+
118
+ // Deduplicate by resolved path
119
+ const seen = new Set();
120
+ const cssFiles = [];
121
+ for (const file of collected) {
122
+ if (!seen.has(file)) {
123
+ seen.add(file);
124
+ cssFiles.push(file);
125
+ }
126
+ }
127
+
128
+ // Sort for deterministic first-match across platforms
129
+ cssFiles.sort();
130
+
131
+ for (const cssFile of cssFiles) {
132
+ try {
133
+ const content = readFileSync(cssFile, 'utf8');
134
+ const hasDesignTokens = /:root\s*\{[\s\S]*?--/.test(content);
135
+ if (hasDesignTokens) {
136
+ return {
137
+ hasDesignSystem: true,
138
+ path: cssFile,
139
+ isProjectLevel: true,
140
+ isFeatureLevel: false,
141
+ source: 'existing-css-variables'
142
+ };
103
143
  }
144
+ } catch {
145
+ // Continue checking other files
104
146
  }
105
147
  }
106
148