@polymorphism-tech/morph-spec 4.8.19 → 4.10.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.
Files changed (214) hide show
  1. package/CLAUDE.md +21 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +44 -55
  4. package/bin/task-manager.js +133 -20
  5. package/bin/validate.js +67 -33
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +201 -203
  8. package/docs/QUICKSTART.md +2 -2
  9. package/framework/CLAUDE.md +99 -77
  10. package/framework/agents.json +734 -182
  11. package/framework/commands/commit.md +166 -0
  12. package/framework/commands/morph-apply.md +13 -2
  13. package/framework/commands/morph-archive.md +8 -2
  14. package/framework/commands/morph-infra.md +6 -0
  15. package/framework/commands/morph-preflight.md +6 -0
  16. package/framework/commands/morph-proposal.md +56 -7
  17. package/framework/commands/morph-status.md +6 -0
  18. package/framework/commands/morph-troubleshoot.md +6 -0
  19. package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
  20. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  21. package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
  22. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
  23. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
  24. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
  25. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
  26. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
  27. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
  28. package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
  29. package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
  30. package/framework/hooks/claude-code/statusline.py +76 -30
  31. package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
  32. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
  33. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  34. package/framework/hooks/shared/activity-logger.js +0 -24
  35. package/framework/hooks/shared/compact-restore.js +100 -0
  36. package/framework/hooks/shared/dispatch-helpers.js +116 -0
  37. package/framework/hooks/shared/phase-utils.js +12 -5
  38. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  39. package/framework/hooks/shared/stale-task-reset.js +57 -0
  40. package/framework/hooks/shared/state-reader.js +29 -5
  41. package/framework/hooks/shared/worktree-helpers.js +53 -0
  42. package/framework/phases.json +69 -14
  43. package/framework/rules/morph-workflow.md +88 -86
  44. package/framework/skills/level-0-meta/mcp-registry.json +86 -51
  45. package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
  46. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  47. package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
  48. package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
  49. package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
  50. package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
  51. package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
  52. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
  53. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  54. package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
  55. package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
  56. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
  57. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
  58. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
  59. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
  60. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
  61. package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
  62. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
  63. package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
  64. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
  65. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
  66. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
  67. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
  68. package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
  69. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
  70. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
  71. package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
  72. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
  73. package/framework/standards/STANDARDS.json +640 -88
  74. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  75. package/framework/standards/integration/mcp/mcp-tools.md +25 -7
  76. package/framework/templates/REGISTRY.json +1825 -1909
  77. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  78. package/framework/templates/docs/onboarding.md +3 -7
  79. package/package.json +2 -7
  80. package/src/commands/agents/dispatch-agents.js +104 -6
  81. package/src/commands/mcp/mcp-setup.js +39 -2
  82. package/src/commands/phase/phase-reset.js +74 -0
  83. package/src/commands/project/doctor.js +34 -51
  84. package/src/commands/project/init.js +1 -1
  85. package/src/commands/project/status.js +2 -2
  86. package/src/commands/project/update.js +381 -365
  87. package/src/commands/project/worktree.js +154 -0
  88. package/src/commands/scope/escalate.js +215 -0
  89. package/src/commands/state/advance-phase.js +132 -68
  90. package/src/commands/state/approve.js +2 -2
  91. package/src/commands/state/index.js +7 -8
  92. package/src/commands/state/phase-runner.js +1 -1
  93. package/src/commands/state/state.js +61 -6
  94. package/src/commands/task/expand.js +100 -0
  95. package/src/commands/tasks/task.js +78 -99
  96. package/src/commands/templates/template-render.js +93 -173
  97. package/src/commands/trust/trust.js +26 -21
  98. package/src/core/paths/output-schema.js +19 -3
  99. package/src/core/state/phase-state-machine.js +7 -4
  100. package/src/core/state/state-manager.js +32 -57
  101. package/src/core/workflows/workflow-detector.js +9 -87
  102. package/src/lib/detectors/claude-config-detector.js +93 -347
  103. package/src/lib/detectors/design-system-detector.js +189 -189
  104. package/src/lib/detectors/index.js +155 -57
  105. package/src/lib/generators/context-generator.js +2 -2
  106. package/src/lib/installers/mcp-installer.js +37 -5
  107. package/src/lib/phase-chain/phase-validator.js +336 -0
  108. package/src/lib/scope/impact-analyzer.js +106 -0
  109. package/src/lib/stack/stack-profile.js +88 -0
  110. package/src/lib/tasks/task-classifier.js +16 -0
  111. package/src/lib/tasks/task-parser.js +1 -1
  112. package/src/lib/tasks/test-runner.js +77 -0
  113. package/src/lib/trust/trust-manager.js +32 -144
  114. package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
  115. package/src/lib/validators/spec-validator.js +58 -4
  116. package/src/lib/validators/validation-runner.js +23 -11
  117. package/src/scripts/setup-infra.js +255 -224
  118. package/src/utils/agents-installer.js +34 -14
  119. package/src/utils/banner.js +1 -1
  120. package/src/utils/claude-settings-manager.js +1 -1
  121. package/src/utils/file-copier.js +1 -1
  122. package/src/utils/hooks-installer.js +272 -8
  123. package/framework/hooks/dev/check-sync-health.js +0 -117
  124. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  125. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  126. package/framework/hooks/dev/sync-template-registry.js +0 -60
  127. package/framework/hooks/dev/validate-skill-format.js +0 -70
  128. package/framework/hooks/dev/validate-standard-format.js +0 -73
  129. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
  130. package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
  131. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  132. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  133. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  134. package/framework/workflows/configs/design-impl.json +0 -49
  135. package/framework/workflows/configs/express.json +0 -45
  136. package/framework/workflows/configs/fast-track.json +0 -42
  137. package/framework/workflows/configs/full-morph.json +0 -79
  138. package/framework/workflows/configs/fusion.json +0 -39
  139. package/framework/workflows/configs/long-running.json +0 -33
  140. package/framework/workflows/configs/spec-only.json +0 -43
  141. package/framework/workflows/configs/ui-refresh.json +0 -49
  142. package/framework/workflows/configs/zero-touch.json +0 -82
  143. package/src/commands/project/index.js +0 -8
  144. package/src/commands/project/monitor.js +0 -295
  145. package/src/commands/project/tutorial.js +0 -115
  146. package/src/commands/state/validate-phase.js +0 -238
  147. package/src/commands/templates/generate-contracts.js +0 -445
  148. package/src/core/index.js +0 -10
  149. package/src/core/orchestrator.js +0 -171
  150. package/src/core/registry/command-registry.js +0 -28
  151. package/src/core/registry/index.js +0 -8
  152. package/src/core/registry/validator-registry.js +0 -204
  153. package/src/core/state/index.js +0 -8
  154. package/src/core/templates/index.js +0 -9
  155. package/src/core/templates/template-data-sources.js +0 -325
  156. package/src/core/templates/template-validator.js +0 -296
  157. package/src/core/workflows/index.js +0 -7
  158. package/src/generator/config-generator.js +0 -206
  159. package/src/generator/templates/config.json.template +0 -40
  160. package/src/generator/templates/project.md.template +0 -67
  161. package/src/lib/agents/micro-agent-factory.js +0 -161
  162. package/src/lib/analysis/complexity-analyzer.js +0 -441
  163. package/src/lib/analysis/index.js +0 -7
  164. package/src/lib/analytics/analytics-engine.js +0 -345
  165. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  166. package/src/lib/checkpoints/index.js +0 -7
  167. package/src/lib/context/context-bundler.js +0 -241
  168. package/src/lib/context/context-optimizer.js +0 -212
  169. package/src/lib/context/context-tracker.js +0 -273
  170. package/src/lib/context/core-four-tracker.js +0 -201
  171. package/src/lib/context/mcp-optimizer.js +0 -200
  172. package/src/lib/detectors/config-detector.js +0 -223
  173. package/src/lib/detectors/standards-generator.js +0 -335
  174. package/src/lib/detectors/structure-detector.js +0 -275
  175. package/src/lib/execution/fusion-executor.js +0 -304
  176. package/src/lib/execution/parallel-executor.js +0 -270
  177. package/src/lib/hooks/stop-hook-executor.js +0 -286
  178. package/src/lib/hops/hop-composer.js +0 -221
  179. package/src/lib/monitor/agent-resolver.js +0 -144
  180. package/src/lib/monitor/renderer.js +0 -230
  181. package/src/lib/orchestration/index.js +0 -7
  182. package/src/lib/orchestration/team-orchestrator.js +0 -404
  183. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  184. package/src/lib/threads/thread-coordinator.js +0 -238
  185. package/src/lib/threads/thread-manager.js +0 -317
  186. package/src/lib/tracking/artifact-trail.js +0 -202
  187. package/src/sanitizer/context-sanitizer.js +0 -221
  188. package/src/sanitizer/patterns.js +0 -163
  189. package/src/scanner/project-scanner.js +0 -242
  190. package/src/ui/diff-display.js +0 -91
  191. package/src/ui/interactive-wizard.js +0 -96
  192. package/src/ui/user-review.js +0 -211
  193. package/src/ui/wizard-questions.js +0 -188
  194. package/src/utils/color-utils.js +0 -70
  195. package/src/utils/process-handler.js +0 -97
  196. package/src/writer/file-writer.js +0 -86
  197. /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
  198. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
  199. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
  200. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
  201. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
  202. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
  203. /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
  204. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
  205. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
  206. /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
  207. /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
  208. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
  209. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
  210. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
  211. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
  212. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
  213. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
  214. /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
- * MORPH-SPEC Detectors - Main Orchestrator
2
+ * @fileoverview Project Detector
3
3
  *
4
- * Coordinates all detection modules to build a complete picture of the project.
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 { detectStructure } from './structure-detector.js';
8
- import { detectConfig } from './config-detector.js';
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
- * Main detection orchestrator
16
- * @param {string} projectPath - Path to project root
17
- * @param {Object} options - Detection options
18
- * @returns {Promise<Object>} Detection results
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 async function detectProject(projectPath, options = {}) {
21
- const results = {
22
- path: projectPath,
23
- timestamp: new Date().toISOString(),
24
- structure: null,
25
- config: null,
26
- conversation: null,
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
- // 1. Detect structure (stack, architecture, patterns)
32
- if (options.structure !== false) {
33
- results.structure = await detectStructure(projectPath);
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
- // 2. Detect config (technologies, dependencies, versions)
37
- if (options.config !== false) {
38
- results.config = await detectConfig(projectPath);
115
+ // ── vercel.json detection ──────────────────────────────────────────────
116
+ if (existsSync(join(targetPath, 'vercel.json'))) {
117
+ structure.hasVercel = true;
39
118
  }
40
119
 
41
- // 3. Generate inferred standards
42
- if (options.generateStandards !== false) {
43
- results.inferred = await generateStandards(results);
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
- return results;
47
- } catch (error) {
48
- throw new Error(`Detection failed: ${error.message}`);
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
- * Get detection summary
54
- * @param {Object} results - Detection results
55
- * @returns {string} Human-readable summary
155
+ * Safe readdir that returns [] on error.
156
+ * @param {string} dir
157
+ * @returns {string[]}
56
158
  */
57
- export function getDetectionSummary(results) {
58
- const { structure, config } = results;
59
-
60
- const lines = [
61
- '## Detection Summary',
62
- '',
63
- '### Stack',
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, '3-tasks', 'tasks.json');
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}/3-tasks/tasks.json`,
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