@polymorphism-tech/morph-spec 7.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 (79) hide show
  1. package/README.md +367 -367
  2. package/framework/agents.json +132 -127
  3. package/framework/rubrics/backend.json +41 -36
  4. package/framework/rules/ai-agents-standards.md +45 -15
  5. package/framework/rules/neon-standards.md +25 -26
  6. package/framework/standards/STANDARDS.json +1729 -1133
  7. package/framework/standards/ai-agents/agent-session.md +160 -0
  8. package/framework/standards/ai-agents/batching.md +193 -0
  9. package/framework/standards/ai-agents/context-providers.md +188 -0
  10. package/framework/standards/ai-agents/mcp-server.md +250 -0
  11. package/framework/standards/ai-agents/mcp-tools.md +197 -0
  12. package/framework/standards/ai-agents/middleware-patterns.md +207 -0
  13. package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
  14. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
  15. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
  16. package/framework/standards/ai-agents/modalities-vision.md +201 -0
  17. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
  18. package/framework/standards/ai-agents/production.md +114 -258
  19. package/framework/standards/ai-agents/prompts-in-db.md +214 -0
  20. package/framework/standards/ai-agents/providers/model-registry.md +158 -0
  21. package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
  22. package/framework/standards/ai-agents/service-tier-flex.md +135 -0
  23. package/framework/standards/ai-agents/setup.md +142 -316
  24. package/framework/standards/ai-agents/structured-output.md +249 -0
  25. package/framework/standards/ai-agents/sweet-spot.md +109 -0
  26. package/framework/standards/ai-agents/workflows.md +114 -284
  27. package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
  28. package/framework/standards/backend/api/validation.md +92 -88
  29. package/framework/standards/backend/database/postgresql/database.md +336 -332
  30. package/framework/standards/backend/database/vector-search-rag.md +599 -541
  31. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
  32. package/framework/standards/backend/dotnet/result-pattern.md +4 -0
  33. package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
  34. package/framework/standards/backend/dotnet/scrutor.md +4 -0
  35. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
  36. package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
  37. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
  38. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
  39. package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
  40. package/framework/standards/data/nosql/cache/redis.md +106 -97
  41. package/framework/standards/data/nosql/cosmos-db.md +127 -118
  42. package/framework/standards/data/vector-search/rag-chunking.md +113 -104
  43. package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
  44. package/framework/standards/frontend/design-system/animations.md +164 -160
  45. package/framework/standards/frontend/design-system/naming.md +68 -64
  46. package/framework/standards/frontend/gsap/gsap-core.md +14 -8
  47. package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
  48. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
  49. package/framework/standards/frontend/nextjs/app-router.md +127 -123
  50. package/framework/standards/frontend/nextjs/components.md +136 -132
  51. package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
  52. package/framework/standards/frontend/nextjs/forms.md +134 -128
  53. package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
  54. package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
  55. package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
  56. package/framework/standards/frontend/nextjs/project-structure.md +106 -102
  57. package/framework/standards/frontend/nextjs/state-management.md +76 -72
  58. package/framework/standards/frontend/nextjs/testing.md +115 -111
  59. package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
  60. package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
  61. package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
  62. package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
  63. package/framework/standards/integration/mcp/mcp-tools.md +471 -462
  64. package/framework/templates/REGISTRY.json +1745 -1239
  65. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
  66. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
  67. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
  68. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
  69. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
  70. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
  71. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
  72. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
  73. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
  74. package/package.json +89 -83
  75. package/src/core/templates/template-registry.js +289 -256
  76. package/src/lib/generators/context-generator.js +516 -518
  77. package/src/lib/generators/scaffold-generator.js +23 -26
  78. package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
  79. 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
+