@polymorphism-tech/morph-spec 6.0.0 → 7.1.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 (114) hide show
  1. package/README.md +367 -366
  2. package/docs/CHEATSHEET.md +3 -3
  3. package/docs/QUICKSTART.md +4 -4
  4. package/framework/CLAUDE.md +13 -0
  5. package/framework/MORPH.md +57 -36
  6. package/framework/agents.json +132 -127
  7. package/framework/commands/morph-apply.md +4 -21
  8. package/framework/commands/morph-archive.md +1 -1
  9. package/framework/commands/morph-proposal.md +2 -0
  10. package/framework/hooks/claude-code/core/enforce-phase-writes.js +1 -1
  11. package/framework/hooks/claude-code/core/protect-spec-files.js +1 -1
  12. package/framework/hooks/claude-code/core/state-sync.js +212 -0
  13. package/framework/hooks/shared/phase-utils.js +5 -4
  14. package/framework/hooks/shared/state-reader.js +1 -1
  15. package/framework/phases.json +1 -1
  16. package/framework/rubrics/backend.json +41 -36
  17. package/framework/rules/ai-agents-standards.md +45 -15
  18. package/framework/rules/morph-workflow.md +15 -34
  19. package/framework/rules/neon-standards.md +25 -26
  20. package/framework/schemas/tasks.schema.json +35 -0
  21. package/framework/skills/core/morph-apply/SKILL.md +154 -0
  22. package/framework/skills/core/morph-brainstorming/SKILL.md +53 -30
  23. package/framework/skills/core/morph-implement/SKILL.md +25 -14
  24. package/framework/skills/core/morph-plan/SKILL.md +91 -23
  25. package/framework/skills/core/morph-proposal/SKILL.md +137 -0
  26. package/framework/skills/core/morph-review/SKILL.md +43 -7
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +40 -1
  28. package/framework/standards/STANDARDS.json +1729 -1134
  29. package/framework/standards/ai-agents/agent-session.md +160 -0
  30. package/framework/standards/ai-agents/batching.md +193 -0
  31. package/framework/standards/ai-agents/context-providers.md +188 -0
  32. package/framework/standards/ai-agents/mcp-server.md +250 -0
  33. package/framework/standards/ai-agents/mcp-tools.md +197 -0
  34. package/framework/standards/ai-agents/middleware-patterns.md +207 -0
  35. package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
  36. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
  37. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
  38. package/framework/standards/ai-agents/modalities-vision.md +201 -0
  39. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
  40. package/framework/standards/ai-agents/production.md +114 -258
  41. package/framework/standards/ai-agents/prompts-in-db.md +214 -0
  42. package/framework/standards/ai-agents/providers/model-registry.md +158 -0
  43. package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
  44. package/framework/standards/ai-agents/service-tier-flex.md +135 -0
  45. package/framework/standards/ai-agents/setup.md +142 -316
  46. package/framework/standards/ai-agents/structured-output.md +249 -0
  47. package/framework/standards/ai-agents/sweet-spot.md +109 -0
  48. package/framework/standards/ai-agents/workflows.md +114 -284
  49. package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
  50. package/framework/standards/backend/api/validation.md +92 -88
  51. package/framework/standards/backend/database/postgresql/database.md +336 -332
  52. package/framework/standards/backend/database/vector-search-rag.md +599 -541
  53. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
  54. package/framework/standards/backend/dotnet/result-pattern.md +4 -0
  55. package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
  56. package/framework/standards/backend/dotnet/scrutor.md +4 -0
  57. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
  58. package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
  59. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
  60. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
  61. package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
  62. package/framework/standards/data/nosql/cache/redis.md +106 -97
  63. package/framework/standards/data/nosql/cosmos-db.md +127 -118
  64. package/framework/standards/data/vector-search/rag-chunking.md +113 -104
  65. package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
  66. package/framework/standards/frontend/design-system/animations.md +164 -160
  67. package/framework/standards/frontend/design-system/naming.md +68 -64
  68. package/framework/standards/frontend/gsap/gsap-core.md +14 -8
  69. package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
  70. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
  71. package/framework/standards/frontend/nextjs/app-router.md +127 -123
  72. package/framework/standards/frontend/nextjs/components.md +136 -132
  73. package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
  74. package/framework/standards/frontend/nextjs/forms.md +134 -128
  75. package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
  76. package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
  77. package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
  78. package/framework/standards/frontend/nextjs/project-structure.md +106 -102
  79. package/framework/standards/frontend/nextjs/state-management.md +76 -72
  80. package/framework/standards/frontend/nextjs/testing.md +115 -111
  81. package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
  82. package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
  83. package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
  84. package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
  85. package/framework/standards/integration/mcp/mcp-tools.md +471 -462
  86. package/framework/templates/REGISTRY.json +1745 -1239
  87. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
  88. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
  89. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
  90. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
  91. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
  92. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
  93. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
  94. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
  95. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
  96. package/framework/templates/docs/onboarding.md +4 -4
  97. package/framework/templates/tasks.template.json +17 -0
  98. package/package.json +89 -83
  99. package/src/commands/project/init.js +16 -0
  100. package/src/commands/validation/validate-feature.js +1 -1
  101. package/src/core/paths/output-schema.js +6 -6
  102. package/src/core/state/state-manager.js +1 -1
  103. package/src/core/templates/template-registry.js +289 -256
  104. package/src/lib/detectors/design-system-detector.js +1 -1
  105. package/src/lib/generators/context-generator.js +516 -518
  106. package/src/lib/generators/scaffold-generator.js +23 -26
  107. package/src/lib/installers/memory-installer.js +138 -0
  108. package/src/lib/phase-chain/phase-validator.js +4 -4
  109. package/src/lib/risk-detector.js +80 -0
  110. package/src/lib/tasks/tasks-json-parser.js +185 -0
  111. package/src/scripts/setup-infra.js +25 -0
  112. package/src/utils/hooks-installer.js +59 -10
  113. package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
  114. package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
@@ -1,256 +1,289 @@
1
- /**
2
- * Template Registry - Discovery and metadata API for all framework templates.
3
- *
4
- * Provides programmatic access to REGISTRY.json for:
5
- * - Template discovery by category, phase, stack
6
- * - Template metadata lookup
7
- * - Path resolution with fallback logic
8
- * - Validation of template existence
9
- *
10
- * @module template-registry
11
- */
12
-
13
- import { readFileSync, existsSync } from 'fs';
14
- import { join, dirname } from 'path';
15
- import { fileURLToPath } from 'url';
16
-
17
- const __filename = fileURLToPath(import.meta.url);
18
- const __dirname = dirname(__filename);
19
-
20
- // Cache for loaded registry
21
- let _cachedRegistry = null;
22
-
23
- /**
24
- * Load the template registry from REGISTRY.json.
25
- *
26
- * @param {string} [projectPath] - Optional project path (defaults to framework root)
27
- * @returns {Object} Parsed registry object
28
- * @throws {Error} If registry file not found or invalid JSON
29
- */
30
- export function loadTemplateRegistry(projectPath = null) {
31
- // Return cached if already loaded
32
- if (_cachedRegistry) {
33
- return _cachedRegistry;
34
- }
35
-
36
- // Default to framework registry
37
- const registryPath = projectPath
38
- ? join(projectPath, 'framework/templates/REGISTRY.json')
39
- : join(__dirname, '../../../framework/templates/REGISTRY.json');
40
-
41
- if (!existsSync(registryPath)) {
42
- throw new Error(`Template registry not found: ${registryPath}`);
43
- }
44
-
45
- try {
46
- const content = readFileSync(registryPath, 'utf-8');
47
- _cachedRegistry = JSON.parse(content);
48
- return _cachedRegistry;
49
- } catch (error) {
50
- throw new Error(`Failed to parse template registry: ${error.message}`);
51
- }
52
- }
53
-
54
- /**
55
- * Clear cached registry (useful for testing).
56
- */
57
- export function clearRegistryCache() {
58
- _cachedRegistry = null;
59
- }
60
-
61
- /**
62
- * Get all templates from registry.
63
- *
64
- * @param {string} [projectPath] - Optional project path
65
- * @returns {Array} Array of template objects
66
- */
67
- export function getAllTemplates(projectPath = null) {
68
- const registry = loadTemplateRegistry(projectPath);
69
- return registry.templates || [];
70
- }
71
-
72
- /**
73
- * Get template by ID.
74
- *
75
- * @param {string} templateId - Template identifier (e.g., 'proposal', 'spec')
76
- * @param {string} [projectPath] - Optional project path
77
- * @returns {Object|null} Template object or null if not found
78
- */
79
- export function getTemplateById(templateId, projectPath = null) {
80
- const templates = getAllTemplates(projectPath);
81
- return templates.find(t => t.id === templateId) || null;
82
- }
83
-
84
- /**
85
- * Get templates by category.
86
- *
87
- * @param {string} category - Category ('documentation', 'code', 'infrastructure', 'context', 'examples')
88
- * @param {string} [projectPath] - Optional project path
89
- * @returns {Array} Filtered template array
90
- */
91
- export function getTemplatesByCategory(category, projectPath = null) {
92
- const templates = getAllTemplates(projectPath);
93
- return templates.filter(t => t.category === category);
94
- }
95
-
96
- /**
97
- * Get templates by phase.
98
- *
99
- * @param {string} phase - Phase name (e.g., 'proposal', 'design', 'implement')
100
- * @param {string} [projectPath] - Optional project path
101
- * @returns {Array} Filtered template array
102
- */
103
- export function getTemplatesByPhase(phase, projectPath = null) {
104
- const templates = getAllTemplates(projectPath);
105
- return templates.filter(t => t.phase === phase);
106
- }
107
-
108
- /**
109
- * Get templates by stack.
110
- *
111
- * @param {string} stackId - Stack identifier ('blazor-azure', 'nextjs-neon')
112
- * @param {string} [projectPath] - Optional project path
113
- * @returns {Array} Filtered template array (includes universal + stack-specific)
114
- */
115
- export function getTemplatesByStack(stackId, projectPath = null) {
116
- const templates = getAllTemplates(projectPath);
117
- return templates.filter(t => {
118
- // Include universal templates (stackSpecific: false)
119
- if (!t.stackSpecific) return true;
120
-
121
- // Include stack-specific if matches
122
- return t.stacks && t.stacks.includes(stackId);
123
- });
124
- }
125
-
126
- /**
127
- * Get required templates only.
128
- *
129
- * @param {string} [stackId] - Optional stack filter
130
- * @param {string} [projectPath] - Optional project path
131
- * @returns {Array} Filtered template array
132
- */
133
- export function getRequiredTemplates(stackId = null, projectPath = null) {
134
- let templates = getAllTemplates(projectPath);
135
-
136
- // Filter by required flag
137
- templates = templates.filter(t => t.required === true);
138
-
139
- // Optional: filter by stack
140
- if (stackId) {
141
- templates = templates.filter(t => {
142
- if (!t.stackSpecific) return true;
143
- return t.stacks && t.stacks.includes(stackId);
144
- });
145
- }
146
-
147
- return templates;
148
- }
149
-
150
- /**
151
- * Get infrastructure templates for a specific stack.
152
- *
153
- * @param {string} stackId - Stack identifier
154
- * @param {string} [projectPath] - Optional project path
155
- * @returns {Array} Infrastructure template array
156
- */
157
- export function getInfrastructureTemplates(stackId, projectPath = null) {
158
- const registry = loadTemplateRegistry(projectPath);
159
- return registry.infrastructureTemplates?.[stackId] || [];
160
- }
161
-
162
- /**
163
- * Get contract templates for a specific stack.
164
- *
165
- * @param {string} stackId - Stack identifier
166
- * @param {string} [projectPath] - Optional project path
167
- * @returns {Array} Contract template array
168
- */
169
- export function getContractTemplates(stackId, projectPath = null) {
170
- const registry = loadTemplateRegistry(projectPath);
171
- return registry.contractTemplates?.[stackId] || [];
172
- }
173
-
174
- /**
175
- * Get integration templates for a specific stack.
176
- *
177
- * @param {string} stackId - Stack identifier
178
- * @param {string} [projectPath] - Optional project path
179
- * @returns {Array} Integration template array
180
- */
181
- export function getIntegrationTemplates(stackId, projectPath = null) {
182
- const registry = loadTemplateRegistry(projectPath);
183
- return registry.integrationTemplates?.[stackId] || [];
184
- }
185
-
186
- /**
187
- * Get SaaS templates for a specific stack.
188
- *
189
- * @param {string} stackId - Stack identifier
190
- * @param {string} [projectPath] - Optional project path
191
- * @returns {Array} SaaS template array
192
- */
193
- export function getSaasTemplates(stackId, projectPath = null) {
194
- const registry = loadTemplateRegistry(projectPath);
195
- return registry.saasTemplates?.[stackId] || [];
196
- }
197
-
198
- /**
199
- * Get example templates.
200
- *
201
- * @param {string} [projectPath] - Optional project path
202
- * @returns {Array} Example template array
203
- */
204
- export function getExampleTemplates(projectPath = null) {
205
- const registry = loadTemplateRegistry(projectPath);
206
- return registry.exampleTemplates || [];
207
- }
208
-
209
- /**
210
- * Search templates by keyword (searches id, name, description).
211
- *
212
- * @param {string} keyword - Search term
213
- * @param {string} [projectPath] - Optional project path
214
- * @returns {Array} Matching templates
215
- */
216
- export function searchTemplates(keyword, projectPath = null) {
217
- const templates = getAllTemplates(projectPath);
218
- const lowerKeyword = keyword.toLowerCase();
219
-
220
- return templates.filter(t => {
221
- return (
222
- t.id.toLowerCase().includes(lowerKeyword) ||
223
- t.name.toLowerCase().includes(lowerKeyword) ||
224
- (t.description && t.description.toLowerCase().includes(lowerKeyword))
225
- );
226
- });
227
- }
228
-
229
- /**
230
- * Get template absolute path with fallback logic.
231
- *
232
- * Resolution order:
233
- * 1. Project-local override (.morph/framework/templates/{path})
234
- * 2. Framework universal template (framework/templates/{path})
235
- *
236
- * @param {string} templateId - Template identifier
237
- * @param {string} projectPath - Project root path
238
- * @returns {string|null} Absolute template path or null if not found
239
- */
240
- export function resolveTemplatePathById(templateId, projectPath) {
241
- const template = getTemplateById(templateId, projectPath);
242
- if (!template) {
243
- return null;
244
- }
245
-
246
- // 1. Project-local override
247
- const projectLocalPath = join(projectPath, '.morph/framework/templates', template.path);
248
- if (existsSync(projectLocalPath)) {
249
- return projectLocalPath;
250
- }
251
-
252
- // 2. Framework universal template
253
- const frameworkPath = join(projectPath, 'framework/templates', template.path);
254
- return existsSync(frameworkPath) ? frameworkPath : null;
255
- }
256
-
1
+ /**
2
+ * Template Registry - Discovery and metadata API for all framework templates.
3
+ *
4
+ * Provides programmatic access to REGISTRY.json for:
5
+ * - Template discovery by category, phase, stack
6
+ * - Template metadata lookup
7
+ * - Path resolution with fallback logic
8
+ * - Validation of template existence
9
+ *
10
+ * @module template-registry
11
+ */
12
+
13
+ import { readFileSync, existsSync } from 'fs';
14
+ import { join, dirname } from 'path';
15
+ import { fileURLToPath } from 'url';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+
20
+ // Parsed registries cached by resolved REGISTRY.json path. Keyed on the path —
21
+ // NOT a single global slot — so a process that loads the framework registry
22
+ // and a project's registry gets each one correctly, instead of whichever
23
+ // loaded first.
24
+ const _registryCache = new Map();
25
+
26
+ /**
27
+ * Load the template registry from REGISTRY.json.
28
+ *
29
+ * @param {string} [projectPath] - Optional project path (defaults to framework root)
30
+ * @returns {Object} Parsed registry object
31
+ * @throws {Error} If registry file not found or invalid JSON
32
+ */
33
+ export function loadTemplateRegistry(projectPath = null) {
34
+ // Resolve the path first — it is the cache key.
35
+ const registryPath = projectPath
36
+ ? join(projectPath, 'framework/templates/REGISTRY.json')
37
+ : join(__dirname, '../../../framework/templates/REGISTRY.json');
38
+
39
+ const cached = _registryCache.get(registryPath);
40
+ if (cached) {
41
+ return cached;
42
+ }
43
+
44
+ if (!existsSync(registryPath)) {
45
+ throw new Error(`Template registry not found: ${registryPath}`);
46
+ }
47
+
48
+ try {
49
+ const content = readFileSync(registryPath, 'utf-8');
50
+ const parsed = JSON.parse(content);
51
+ _registryCache.set(registryPath, parsed);
52
+ return parsed;
53
+ } catch (error) {
54
+ throw new Error(`Failed to parse template registry: ${error.message}`);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Clear all cached registries (useful for testing).
60
+ */
61
+ export function clearRegistryCache() {
62
+ _registryCache.clear();
63
+ }
64
+
65
+ /**
66
+ * Get all templates from registry.
67
+ *
68
+ * @param {string} [projectPath] - Optional project path
69
+ * @returns {Array} Array of template objects
70
+ */
71
+ export function getAllTemplates(projectPath = null) {
72
+ const registry = loadTemplateRegistry(projectPath);
73
+ return registry.templates || [];
74
+ }
75
+
76
+ /**
77
+ * Get template by ID.
78
+ *
79
+ * @param {string} templateId - Template identifier (e.g., 'proposal', 'spec')
80
+ * @param {string} [projectPath] - Optional project path
81
+ * @returns {Object|null} Template object or null if not found
82
+ */
83
+ export function getTemplateById(templateId, projectPath = null) {
84
+ const templates = getAllTemplates(projectPath);
85
+ return templates.find(t => t.id === templateId) || null;
86
+ }
87
+
88
+ /**
89
+ * Get templates by category.
90
+ *
91
+ * @param {string} category - Category ('documentation', 'code', 'infrastructure', 'context', 'examples')
92
+ * @param {string} [projectPath] - Optional project path
93
+ * @returns {Array} Filtered template array
94
+ */
95
+ export function getTemplatesByCategory(category, projectPath = null) {
96
+ const templates = getAllTemplates(projectPath);
97
+ return templates.filter(t => t.category === category);
98
+ }
99
+
100
+ /**
101
+ * Get templates by phase.
102
+ *
103
+ * @param {string} phase - Phase name (e.g., 'proposal', 'design', 'implement')
104
+ * @param {string} [projectPath] - Optional project path
105
+ * @returns {Array} Filtered template array
106
+ */
107
+ export function getTemplatesByPhase(phase, projectPath = null) {
108
+ const templates = getAllTemplates(projectPath);
109
+ return templates.filter(t => t.phase === phase);
110
+ }
111
+
112
+ /**
113
+ * Get templates by stack.
114
+ *
115
+ * @param {string} stackId - Stack identifier ('blazor-azure', 'nextjs-neon')
116
+ * @param {string} [projectPath] - Optional project path
117
+ * @returns {Array} Filtered template array (includes universal + stack-specific)
118
+ */
119
+ export function getTemplatesByStack(stackId, projectPath = null) {
120
+ const templates = getAllTemplates(projectPath);
121
+ return templates.filter(t => {
122
+ // Include universal templates (stackSpecific: false)
123
+ if (!t.stackSpecific) return true;
124
+
125
+ // Include stack-specific if matches
126
+ return t.stacks && t.stacks.includes(stackId);
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Get required templates only.
132
+ *
133
+ * @param {string} [stackId] - Optional stack filter
134
+ * @param {string} [projectPath] - Optional project path
135
+ * @returns {Array} Filtered template array
136
+ */
137
+ export function getRequiredTemplates(stackId = null, projectPath = null) {
138
+ let templates = getAllTemplates(projectPath);
139
+
140
+ // Filter by required flag
141
+ templates = templates.filter(t => t.required === true);
142
+
143
+ // Optional: filter by stack
144
+ if (stackId) {
145
+ templates = templates.filter(t => {
146
+ if (!t.stackSpecific) return true;
147
+ return t.stacks && t.stacks.includes(stackId);
148
+ });
149
+ }
150
+
151
+ return templates;
152
+ }
153
+
154
+ /**
155
+ * Get infrastructure templates for a specific stack.
156
+ *
157
+ * @param {string} stackId - Stack identifier
158
+ * @param {string} [projectPath] - Optional project path
159
+ * @returns {Array} Infrastructure template array
160
+ */
161
+ export function getInfrastructureTemplates(stackId, projectPath = null) {
162
+ const registry = loadTemplateRegistry(projectPath);
163
+ return registry.infrastructureTemplates?.[stackId] || [];
164
+ }
165
+
166
+ /**
167
+ * Get contract templates for a specific stack.
168
+ *
169
+ * @param {string} stackId - Stack identifier
170
+ * @param {string} [projectPath] - Optional project path
171
+ * @returns {Array} Contract template array
172
+ */
173
+ export function getContractTemplates(stackId, projectPath = null) {
174
+ const registry = loadTemplateRegistry(projectPath);
175
+ return registry.contractTemplates?.[stackId] || [];
176
+ }
177
+
178
+ /**
179
+ * Get integration templates for a specific stack.
180
+ *
181
+ * @param {string} stackId - Stack identifier
182
+ * @param {string} [projectPath] - Optional project path
183
+ * @returns {Array} Integration template array
184
+ */
185
+ export function getIntegrationTemplates(stackId, projectPath = null) {
186
+ const registry = loadTemplateRegistry(projectPath);
187
+ return registry.integrationTemplates?.[stackId] || [];
188
+ }
189
+
190
+ /**
191
+ * Get SaaS templates for a specific stack.
192
+ *
193
+ * @param {string} stackId - Stack identifier
194
+ * @param {string} [projectPath] - Optional project path
195
+ * @returns {Array} SaaS template array
196
+ */
197
+ export function getSaasTemplates(stackId, projectPath = null) {
198
+ const registry = loadTemplateRegistry(projectPath);
199
+ return registry.saasTemplates?.[stackId] || [];
200
+ }
201
+
202
+ /**
203
+ * Get example templates.
204
+ *
205
+ * @param {string} [projectPath] - Optional project path
206
+ * @returns {Array} Example template array
207
+ */
208
+ export function getExampleTemplates(projectPath = null) {
209
+ const registry = loadTemplateRegistry(projectPath);
210
+ return registry.exampleTemplates || [];
211
+ }
212
+
213
+ /**
214
+ * Search templates by keyword (searches id, name, description).
215
+ *
216
+ * @param {string} keyword - Search term
217
+ * @param {string} [projectPath] - Optional project path
218
+ * @returns {Array} Matching templates
219
+ */
220
+ export function searchTemplates(keyword, projectPath = null) {
221
+ const templates = getAllTemplates(projectPath);
222
+ const lowerKeyword = keyword.toLowerCase();
223
+
224
+ return templates.filter(t => {
225
+ return (
226
+ t.id.toLowerCase().includes(lowerKeyword) ||
227
+ t.name.toLowerCase().includes(lowerKeyword) ||
228
+ (t.description && t.description.toLowerCase().includes(lowerKeyword))
229
+ );
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Get template absolute path with fallback logic.
235
+ *
236
+ * Resolution order:
237
+ * 1. Project-local override (.morph/framework/templates/{path})
238
+ * 2. Framework universal template (framework/templates/{path})
239
+ *
240
+ * @param {string} templateId - Template identifier
241
+ * @param {string} projectPath - Project root path
242
+ * @returns {string|null} Absolute template path or null if not found
243
+ */
244
+ export function resolveTemplatePathById(templateId, projectPath) {
245
+ const template = getTemplateById(templateId, projectPath);
246
+ if (!template) {
247
+ return null;
248
+ }
249
+
250
+ // 1. Project-local override
251
+ const projectLocalPath = join(projectPath, '.morph/framework/templates', template.path);
252
+ if (existsSync(projectLocalPath)) {
253
+ return projectLocalPath;
254
+ }
255
+
256
+ // 2. Framework universal template
257
+ const frameworkPath = join(projectPath, 'framework/templates', template.path);
258
+ return existsSync(frameworkPath) ? frameworkPath : null;
259
+ }
260
+
261
+ /** Absolute path of this repo's own framework/templates/ directory. */
262
+ const FRAMEWORK_TEMPLATES_DIR = join(__dirname, '../../../framework/templates');
263
+
264
+ /**
265
+ * Resolve a template id to its absolute path inside THIS framework repo's
266
+ * `framework/templates/` directory — for generators running against the
267
+ * framework itself (scaffold-generator, context-generator), not against an
268
+ * installed `.morph/`. Use resolveTemplatePathById() when a project root and
269
+ * its project-local overrides are in play.
270
+ *
271
+ * @param {string} templateId - Template identifier (REGISTRY.json `id`)
272
+ * @returns {string} Absolute path to the template file
273
+ * @throws {Error} If the id is not in REGISTRY.json or the file is missing —
274
+ * a missing template is a framework bug, not a recoverable condition.
275
+ */
276
+ export function resolveFrameworkTemplatePath(templateId) {
277
+ const template = getTemplateById(templateId);
278
+ if (!template) {
279
+ throw new Error(`Template id "${templateId}" not found in REGISTRY.json`);
280
+ }
281
+ const abs = join(FRAMEWORK_TEMPLATES_DIR, template.path);
282
+ if (!existsSync(abs)) {
283
+ throw new Error(
284
+ `Template "${templateId}" path "${template.path}" does not resolve to a file`
285
+ );
286
+ }
287
+ return abs;
288
+ }
289
+
@@ -64,7 +64,7 @@ export function detectDesignSystem(projectPath, featureName = null) {
64
64
  projectPath,
65
65
  '.morph/features',
66
66
  featureName,
67
- '1-ui',
67
+ '1-design',
68
68
  'design-system.md'
69
69
  );
70
70