@polymorphism-tech/morph-spec 3.0.0 → 3.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 (240) hide show
  1. package/CLAUDE.md +68 -400
  2. package/README.md +198 -76
  3. package/bin/detect-agents.js +227 -225
  4. package/bin/morph-spec.js +10 -0
  5. package/bin/render-template.js +303 -302
  6. package/bin/semantic-detect-agents.js +247 -246
  7. package/bin/{task-manager.js → task-manager.cjs} +12 -1
  8. package/bin/validate-agents-skills.js +257 -251
  9. package/bin/validate-agents.js +70 -69
  10. package/bin/validate-phase.js +263 -263
  11. package/docs/getting-started.md +3 -3
  12. package/package.json +3 -4
  13. package/scripts/reorganize-skills.cjs +175 -0
  14. package/scripts/validate-agents-structure.cjs +52 -0
  15. package/scripts/validate-skills.cjs +180 -0
  16. package/src/commands/create-story.js +354 -351
  17. package/src/commands/detect-agents.js +13 -2
  18. package/src/commands/detect.js +104 -104
  19. package/src/commands/state.js +334 -333
  20. package/src/commands/sync.js +167 -167
  21. package/src/commands/task.js +1 -1
  22. package/src/commands/update.js +13 -1
  23. package/src/lib/context-generator.js +7 -4
  24. package/{detectors → src/lib/detectors}/config-detector.js +223 -223
  25. package/{detectors → src/lib/detectors}/conversation-analyzer.js +163 -163
  26. package/{detectors → src/lib/detectors}/index.js +84 -84
  27. package/{detectors → src/lib/detectors}/standards-generator.js +275 -275
  28. package/src/lib/hook-executor.js +2 -1
  29. package/src/lib/stack-resolver.js +148 -0
  30. package/src/lib/standards-context-injector.js +4 -3
  31. package/src/lib/state-manager.js +21 -4
  32. package/src/lib/team-orchestrator.js +2 -1
  33. package/src/lib/troubleshoot-grep.js +13 -3
  34. package/src/lib/validation-runner.js +2 -1
  35. package/src/utils/file-copier.js +3 -1
  36. package/{content → stacks/blazor-azure}/.azure/README.md +293 -293
  37. package/{content → stacks/blazor-azure}/.azure/docs/azure-devops-setup.md +454 -454
  38. package/{content → stacks/blazor-azure}/.azure/docs/branch-strategy.md +398 -398
  39. package/{content → stacks/blazor-azure}/.azure/docs/local-development.md +515 -515
  40. package/{content → stacks/blazor-azure}/.azure/pipelines/pipeline-variables.yml +34 -34
  41. package/{content → stacks/blazor-azure}/.azure/pipelines/prod-pipeline.yml +319 -319
  42. package/{content → stacks/blazor-azure}/.azure/pipelines/staging-pipeline.yml +234 -234
  43. package/{content → stacks/blazor-azure}/.azure/pipelines/templates/build-dotnet.yml +75 -75
  44. package/{content → stacks/blazor-azure}/.azure/pipelines/templates/deploy-app-service.yml +94 -94
  45. package/{content → stacks/blazor-azure}/.azure/pipelines/templates/deploy-container-app.yml +120 -120
  46. package/{content → stacks/blazor-azure}/.azure/pipelines/templates/infra-deploy.yml +90 -90
  47. package/{content → stacks/blazor-azure}/.claude/commands/morph-archive.md +79 -79
  48. package/{content → stacks/blazor-azure}/.claude/commands/morph-deploy.md +529 -529
  49. package/{content → stacks/blazor-azure}/.claude/commands/morph-infra.md +209 -209
  50. package/{content → stacks/blazor-azure}/.claude/commands/morph-troubleshoot.md +1 -1
  51. package/{content → stacks/blazor-azure}/.claude/settings.local.json +15 -15
  52. package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-setup.md +1 -1
  53. package/{content/.claude/skills/specialists → stacks/blazor-azure/.claude/skills/level-2-domains/architecture}/prompt-engineer.md +189 -189
  54. package/{content/.claude/skills/specialists → stacks/blazor-azure/.claude/skills/level-2-domains/architecture}/seo-growth-hacker.md +320 -320
  55. package/{content/.claude/skills/infra → stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure}/azure-deploy-specialist.md +699 -699
  56. package/{content → stacks/blazor-azure}/.morph/.morphversion +5 -5
  57. package/{content → stacks/blazor-azure}/.morph/archive/.gitkeep +25 -25
  58. package/{content → stacks/blazor-azure}/.morph/config/agents.json +7 -5
  59. package/{content → stacks/blazor-azure}/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
  60. package/{content → stacks/blazor-azure}/.morph/docs/workflows/enforcement-pipeline.md +3 -3
  61. package/{content → stacks/blazor-azure}/.morph/examples/api-nextjs/README.md +241 -241
  62. package/{content → stacks/blazor-azure}/.morph/examples/api-nextjs/contracts.ts +307 -307
  63. package/{content → stacks/blazor-azure}/.morph/examples/api-nextjs/spec.md +399 -399
  64. package/{content → stacks/blazor-azure}/.morph/examples/api-nextjs/tasks.md +168 -168
  65. package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/README.md +125 -125
  66. package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/contracts.cs +358 -358
  67. package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/decisions.md +246 -246
  68. package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/spec.md +236 -236
  69. package/{content → stacks/blazor-azure}/.morph/examples/micro-saas/tasks.md +150 -150
  70. package/{content → stacks/blazor-azure}/.morph/examples/multi-agent/README.md +309 -309
  71. package/{content → stacks/blazor-azure}/.morph/examples/multi-agent/contracts.cs +433 -433
  72. package/{content → stacks/blazor-azure}/.morph/examples/multi-agent/spec.md +479 -479
  73. package/{content → stacks/blazor-azure}/.morph/examples/multi-agent/tasks.md +185 -185
  74. package/{content → stacks/blazor-azure}/.morph/examples/state-v3.json +188 -188
  75. package/{content → stacks/blazor-azure}/.morph/features/.gitkeep +25 -25
  76. package/{content → stacks/blazor-azure}/.morph/hooks/README.md +12 -12
  77. package/{content → stacks/blazor-azure}/.morph/hooks/pre-commit-all.sh +48 -48
  78. package/{content → stacks/blazor-azure}/.morph/hooks/pre-commit-specs.sh +49 -49
  79. package/{content → stacks/blazor-azure}/.morph/hooks/pre-commit-tests.sh +60 -60
  80. package/{content → stacks/blazor-azure}/.morph/project.md +160 -160
  81. package/{content → stacks/blazor-azure}/.morph/schemas/agent.schema.json +296 -296
  82. package/{content → stacks/blazor-azure}/.morph/specs/.gitkeep +20 -20
  83. package/{content → stacks/blazor-azure}/.morph/standards/agent-teams-workflow.md +2 -2
  84. package/{content → stacks/blazor-azure}/.morph/standards/coding.md +377 -377
  85. package/{content → stacks/blazor-azure}/.morph/standards/fluent-ui-setup.md +590 -590
  86. package/{content → stacks/blazor-azure}/.morph/standards/migration-guide.md +514 -514
  87. package/{content → stacks/blazor-azure}/.morph/standards/passkeys-auth.md +423 -423
  88. package/{content → stacks/blazor-azure}/.morph/standards/vector-search-rag.md +536 -536
  89. package/{content → stacks/blazor-azure}/.morph/state.json +17 -17
  90. package/{content → stacks/blazor-azure}/.morph/templates/FluentDesignTheme.cs +149 -149
  91. package/{content → stacks/blazor-azure}/.morph/templates/MudTheme.cs +281 -281
  92. package/{content → stacks/blazor-azure}/.morph/templates/component.razor +239 -239
  93. package/{content → stacks/blazor-azure}/.morph/templates/contracts.cs +217 -217
  94. package/{content → stacks/blazor-azure}/.morph/templates/design-system.css +226 -226
  95. package/{content → stacks/blazor-azure}/.morph/templates/infra/.dockerignore.example +89 -89
  96. package/{content → stacks/blazor-azure}/.morph/templates/infra/Dockerfile.example +82 -82
  97. package/{content → stacks/blazor-azure}/.morph/templates/infra/README.md +286 -286
  98. package/{content → stacks/blazor-azure}/.morph/templates/infra/app-insights.bicep +63 -63
  99. package/{content → stacks/blazor-azure}/.morph/templates/infra/app-service.bicep +164 -164
  100. package/{content → stacks/blazor-azure}/.morph/templates/infra/azure-pipelines-deploy.yml +480 -480
  101. package/{content → stacks/blazor-azure}/.morph/templates/infra/container-app-env.bicep +49 -49
  102. package/{content → stacks/blazor-azure}/.morph/templates/infra/container-app.bicep +156 -156
  103. package/{content → stacks/blazor-azure}/.morph/templates/infra/deploy.ps1 +229 -229
  104. package/{content → stacks/blazor-azure}/.morph/templates/infra/deploy.sh +208 -208
  105. package/{content → stacks/blazor-azure}/.morph/templates/infra/key-vault.bicep +91 -91
  106. package/{content → stacks/blazor-azure}/.morph/templates/infra/main.bicep +189 -189
  107. package/{content → stacks/blazor-azure}/.morph/templates/infra/parameters.dev.json +29 -29
  108. package/{content → stacks/blazor-azure}/.morph/templates/infra/parameters.prod.json +29 -29
  109. package/{content → stacks/blazor-azure}/.morph/templates/infra/parameters.staging.json +29 -29
  110. package/{content → stacks/blazor-azure}/.morph/templates/infra/sql-database.bicep +103 -103
  111. package/{content → stacks/blazor-azure}/.morph/templates/infra/storage.bicep +106 -106
  112. package/{content → stacks/blazor-azure}/.morph/templates/integrations/asaas-client.cs +387 -387
  113. package/{content → stacks/blazor-azure}/.morph/templates/integrations/asaas-webhook.cs +351 -351
  114. package/{content → stacks/blazor-azure}/.morph/templates/integrations/azure-identity-config.cs +288 -288
  115. package/{content → stacks/blazor-azure}/.morph/templates/integrations/clerk-config.cs +258 -258
  116. package/{content → stacks/blazor-azure}/.morph/templates/job.cs +171 -171
  117. package/{content → stacks/blazor-azure}/.morph/templates/migration.cs +83 -83
  118. package/{content → stacks/blazor-azure}/.morph/templates/repository.cs +141 -141
  119. package/{content → stacks/blazor-azure}/.morph/templates/saas/subscription.cs +347 -347
  120. package/{content → stacks/blazor-azure}/.morph/templates/saas/tenant.cs +338 -338
  121. package/{content → stacks/blazor-azure}/.morph/templates/service.cs +139 -139
  122. package/{content → stacks/blazor-azure}/.morph/templates/sprint-status.yaml +68 -68
  123. package/{content → stacks/blazor-azure}/.morph/templates/story.md +143 -143
  124. package/{content → stacks/blazor-azure}/.morph/templates/test.cs +239 -239
  125. package/{content → stacks/blazor-azure}/.morph/templates/ui-design-system.md +286 -286
  126. package/{content → stacks/blazor-azure}/.morph/templates/ui-flows.md +336 -336
  127. package/{content → stacks/blazor-azure}/.morph/templates/ui-mockups.md +133 -133
  128. package/{content → stacks/blazor-azure}/.morph/test-infra/example.bicep +59 -59
  129. package/{content → stacks/blazor-azure}/README.md +79 -79
  130. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +244 -0
  131. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +335 -0
  132. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +189 -0
  133. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +170 -0
  134. package/stacks/nextjs-supabase/.morph/config/agents.json +345 -0
  135. package/stacks/nextjs-supabase/.morph/config/config.template.json +92 -0
  136. package/stacks/nextjs-supabase/.morph/docs/easypanel-setup.md +169 -0
  137. package/stacks/nextjs-supabase/.morph/docs/supabase-mcp-setup.md +247 -0
  138. package/stacks/nextjs-supabase/.morph/examples/crud-nextjs-supabase/README.md +697 -0
  139. package/stacks/nextjs-supabase/.morph/examples/crud-nextjs-supabase/spec.md +85 -0
  140. package/stacks/nextjs-supabase/.morph/examples/crud-nextjs-supabase/tasks.md +86 -0
  141. package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/README.md +498 -0
  142. package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/decisions.md +121 -0
  143. package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/spec.md +138 -0
  144. package/stacks/nextjs-supabase/.morph/examples/saas-nextjs-supabase/tasks.md +162 -0
  145. package/stacks/nextjs-supabase/.morph/project.md +168 -0
  146. package/stacks/nextjs-supabase/.morph/standards/easypanel-deploy.md +191 -0
  147. package/stacks/nextjs-supabase/.morph/standards/nextjs-patterns.md +193 -0
  148. package/stacks/nextjs-supabase/.morph/standards/supabase-auth.md +171 -0
  149. package/stacks/nextjs-supabase/.morph/standards/supabase-pgvector.md +164 -0
  150. package/stacks/nextjs-supabase/.morph/standards/supabase-rls.md +179 -0
  151. package/stacks/nextjs-supabase/.morph/standards/supabase-storage.md +148 -0
  152. package/stacks/nextjs-supabase/.morph/templates/contracts.cs +173 -0
  153. package/stacks/nextjs-supabase/.morph/templates/contracts.ts +168 -0
  154. package/stacks/nextjs-supabase/.morph/templates/decisions.md +115 -0
  155. package/stacks/nextjs-supabase/.morph/templates/dockerfile-api.dockerfile +38 -0
  156. package/stacks/nextjs-supabase/.morph/templates/dockerfile-web.dockerfile +48 -0
  157. package/stacks/nextjs-supabase/.morph/templates/proposal.md +145 -0
  158. package/stacks/nextjs-supabase/.morph/templates/recap.md +134 -0
  159. package/stacks/nextjs-supabase/.morph/templates/rls-policy.sql +57 -0
  160. package/stacks/nextjs-supabase/.morph/templates/spec.md +231 -0
  161. package/stacks/nextjs-supabase/.morph/templates/supabase-migration.sql +100 -0
  162. package/stacks/nextjs-supabase/.morph/templates/tasks.md +257 -0
  163. package/stacks/nextjs-supabase/CLAUDE.md +149 -0
  164. package/stacks/nextjs-supabase/README.md +112 -0
  165. /package/{detectors → src/lib/detectors}/structure-detector.js +0 -0
  166. /package/{content → stacks/blazor-azure}/.claude/commands/morph-apply.md +0 -0
  167. /package/{content → stacks/blazor-azure}/.claude/commands/morph-preflight.md +0 -0
  168. /package/{content → stacks/blazor-azure}/.claude/commands/morph-proposal.md +0 -0
  169. /package/{content → stacks/blazor-azure}/.claude/commands/morph-status.md +0 -0
  170. /package/{content → stacks/blazor-azure}/.claude/skills/level-0-meta/README.md +0 -0
  171. /package/{content → stacks/blazor-azure}/.claude/skills/level-0-meta/code-review.md +0 -0
  172. /package/{content → stacks/blazor-azure}/.claude/skills/level-0-meta/morph-checklist.md +0 -0
  173. /package/{content → stacks/blazor-azure}/.claude/skills/level-0-meta/simulation-checklist.md +0 -0
  174. /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/README.md +0 -0
  175. /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/morph-replicate.md +0 -0
  176. /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-clarify.md +0 -0
  177. /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-design.md +0 -0
  178. /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-tasks.md +0 -0
  179. /package/{content → stacks/blazor-azure}/.claude/skills/level-1-workflows/phase-uiux.md +0 -0
  180. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/README.md +0 -0
  181. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -0
  182. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -0
  183. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -0
  184. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -0
  185. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -0
  186. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -0
  187. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -0
  188. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -0
  189. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -0
  190. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -0
  191. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -0
  192. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -0
  193. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -0
  194. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -0
  195. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -0
  196. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -0
  197. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -0
  198. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/integrations/resend-email.md +0 -0
  199. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -0
  200. /package/{content → stacks/blazor-azure}/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -0
  201. /package/{content → stacks/blazor-azure}/.claude/skills/level-3-technologies/README.md +0 -0
  202. /package/{content → stacks/blazor-azure}/.claude/skills/level-4-patterns/README.md +0 -0
  203. /package/{content → stacks/blazor-azure}/.morph/config/config.template.json +0 -0
  204. /package/{content → stacks/blazor-azure}/.morph/docs/workflows/design-impl.md +0 -0
  205. /package/{content → stacks/blazor-azure}/.morph/docs/workflows/fast-track.md +0 -0
  206. /package/{content → stacks/blazor-azure}/.morph/docs/workflows/full-morph.md +0 -0
  207. /package/{content → stacks/blazor-azure}/.morph/docs/workflows/standard.md +0 -0
  208. /package/{content → stacks/blazor-azure}/.morph/docs/workflows/ui-refresh.md +0 -0
  209. /package/{content → stacks/blazor-azure}/.morph/examples/scheduled-reports/decisions.md +0 -0
  210. /package/{content → stacks/blazor-azure}/.morph/examples/scheduled-reports/proposal.md +0 -0
  211. /package/{content → stacks/blazor-azure}/.morph/examples/scheduled-reports/spec.md +0 -0
  212. /package/{content → stacks/blazor-azure}/.morph/hooks/pre-commit-agents.sh +0 -0
  213. /package/{content → stacks/blazor-azure}/.morph/hooks/task-completed.js +0 -0
  214. /package/{content → stacks/blazor-azure}/.morph/hooks/teammate-idle.js +0 -0
  215. /package/{content → stacks/blazor-azure}/.morph/schemas/tasks.schema.json +0 -0
  216. /package/{content → stacks/blazor-azure}/.morph/standards/agent-framework-blazor-ui.md +0 -0
  217. /package/{content → stacks/blazor-azure}/.morph/standards/agent-framework-production.md +0 -0
  218. /package/{content → stacks/blazor-azure}/.morph/standards/agent-framework-setup.md +0 -0
  219. /package/{content → stacks/blazor-azure}/.morph/standards/agent-framework-workflows.md +0 -0
  220. /package/{content → stacks/blazor-azure}/.morph/standards/architecture.md +0 -0
  221. /package/{content → stacks/blazor-azure}/.morph/standards/azure.md +0 -0
  222. /package/{content → stacks/blazor-azure}/.morph/standards/dotnet10-migration.md +0 -0
  223. /package/{content → stacks/blazor-azure}/.morph/templates/CONTEXT-FEATURE.md +0 -0
  224. /package/{content → stacks/blazor-azure}/.morph/templates/CONTEXT.md +0 -0
  225. /package/{content → stacks/blazor-azure}/.morph/templates/agent.cs +0 -0
  226. /package/{content → stacks/blazor-azure}/.morph/templates/clarify-questions.md +0 -0
  227. /package/{content → stacks/blazor-azure}/.morph/templates/contracts/Commands.cs +0 -0
  228. /package/{content → stacks/blazor-azure}/.morph/templates/contracts/Entities.cs +0 -0
  229. /package/{content → stacks/blazor-azure}/.morph/templates/contracts/Queries.cs +0 -0
  230. /package/{content → stacks/blazor-azure}/.morph/templates/contracts/README.md +0 -0
  231. /package/{content → stacks/blazor-azure}/.morph/templates/decisions.md +0 -0
  232. /package/{content → stacks/blazor-azure}/.morph/templates/infra/deploy-checklist.md +0 -0
  233. /package/{content → stacks/blazor-azure}/.morph/templates/proposal.md +0 -0
  234. /package/{content → stacks/blazor-azure}/.morph/templates/recap.md +0 -0
  235. /package/{content → stacks/blazor-azure}/.morph/templates/simulation.md +0 -0
  236. /package/{content → stacks/blazor-azure}/.morph/templates/spec.md +0 -0
  237. /package/{content → stacks/blazor-azure}/.morph/templates/state.template.json +0 -0
  238. /package/{content → stacks/blazor-azure}/.morph/templates/tasks.md +0 -0
  239. /package/{content → stacks/blazor-azure}/.morph/templates/ui-components.md +0 -0
  240. /package/{content → stacks/blazor-azure}/CLAUDE.md +0 -0
@@ -1,302 +1,303 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * MORPH-SPEC Template Renderer
5
- *
6
- * Renders templates by replacing {{PLACEHOLDER}} with actual values.
7
- *
8
- * Usage:
9
- * node bin/render-template.js <template-path> <output-path> <variables-json>
10
- *
11
- * Example:
12
- * node bin/render-template.js \
13
- * content/.morph/templates/spec.md \
14
- * .morph/project/outputs/my-feature/spec.md \
15
- * '{"FEATURE_NAME":"my-feature","STACK":"Blazor","DATE":"2024-01-15"}'
16
- */
17
-
18
- import fs from 'fs';
19
- import path from 'path';
20
- import { fileURLToPath } from 'url';
21
-
22
- const __filename = fileURLToPath(import.meta.url);
23
- const __dirname = path.dirname(__filename);
24
-
25
- // ANSI color codes
26
- const colors = {
27
- green: '\x1b[32m',
28
- red: '\x1b[31m',
29
- yellow: '\x1b[33m',
30
- cyan: '\x1b[36m',
31
- reset: '\x1b[0m'
32
- };
33
-
34
- /**
35
- * Load config to get default values
36
- */
37
- function loadConfig() {
38
- const configPaths = [
39
- path.join(process.cwd(), '.morph/config/config.json'),
40
- path.join(process.cwd(), 'content/.morph/config/config.json'),
41
- path.join(__dirname, '../content/.morph/config/config.template.json')
42
- ];
43
-
44
- for (const configPath of configPaths) {
45
- if (fs.existsSync(configPath)) {
46
- try {
47
- const content = fs.readFileSync(configPath, 'utf-8');
48
- return JSON.parse(content);
49
- } catch (error) {
50
- // Continue to next path
51
- }
52
- }
53
- }
54
-
55
- return null;
56
- }
57
-
58
- /**
59
- * Get default variables from config and environment
60
- */
61
- function getDefaultVariables() {
62
- const config = loadConfig();
63
- const now = new Date();
64
-
65
- return {
66
- DATE: now.toISOString().split('T')[0],
67
- YEAR: now.getFullYear().toString(),
68
- AUTHOR: config?.project?.author || 'MORPH-SPEC',
69
- PROJECT_NAME: config?.project?.name || 'Project',
70
- STACK: config?.project?.stack || 'Blazor',
71
- NAMESPACE: config?.project?.namespace || 'Project'
72
- };
73
- }
74
-
75
- /**
76
- * Convert string to various case formats
77
- */
78
- function transformCase(str) {
79
- return {
80
- // kebab-case → PascalCase
81
- pascal: str
82
- .split('-')
83
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
84
- .join(''),
85
-
86
- // kebab-case → camelCase
87
- camel: str
88
- .split('-')
89
- .map((word, index) =>
90
- index === 0
91
- ? word.toLowerCase()
92
- : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
93
- )
94
- .join(''),
95
-
96
- // kebab-case → Title Case
97
- title: str
98
- .split('-')
99
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
100
- .join(' '),
101
-
102
- // kebab-case → UPPER_SNAKE_CASE
103
- upperSnake: str.toUpperCase().replace(/-/g, '_'),
104
-
105
- // kebab-case → lower_snake_case
106
- lowerSnake: str.toLowerCase().replace(/-/g, '_')
107
- };
108
- }
109
-
110
- /**
111
- * Render template by replacing placeholders
112
- */
113
- function renderTemplate(templatePath, variables) {
114
- if (!fs.existsSync(templatePath)) {
115
- throw new Error(`Template file not found: ${templatePath}`);
116
- }
117
-
118
- let content = fs.readFileSync(templatePath, 'utf-8');
119
-
120
- // Get default variables
121
- const defaults = getDefaultVariables();
122
-
123
- // Merge with provided variables (provided takes precedence)
124
- const allVariables = { ...defaults, ...variables };
125
-
126
- // Auto-generate case transformations for FEATURE_NAME if provided
127
- if (allVariables.FEATURE_NAME) {
128
- const featureName = allVariables.FEATURE_NAME;
129
- const transformations = transformCase(featureName);
130
-
131
- allVariables.FEATURE_NAME_PASCAL = transformations.pascal;
132
- allVariables.FEATURE_NAME_CAMEL = transformations.camel;
133
- allVariables.FEATURE_NAME_TITLE = transformations.title;
134
- allVariables.FEATURE_NAME_UPPER_SNAKE = transformations.upperSnake;
135
- allVariables.FEATURE_NAME_LOWER_SNAKE = transformations.lowerSnake;
136
-
137
- // Backward compatibility: FEATURE_TITLE maps to FEATURE_NAME_TITLE
138
- if (!allVariables.FEATURE_TITLE) {
139
- allVariables.FEATURE_TITLE = transformations.title;
140
- }
141
- }
142
-
143
- // Track which placeholders were replaced
144
- const replacedPlaceholders = new Set();
145
- const unreplacedPlaceholders = new Set();
146
-
147
- // Replace all placeholders
148
- for (const [key, value] of Object.entries(allVariables)) {
149
- const placeholder = `{{${key}}}`;
150
- if (content.includes(placeholder)) {
151
- content = content.replaceAll(placeholder, value);
152
- replacedPlaceholders.add(key);
153
- }
154
- }
155
-
156
- // Find unreplaced placeholders
157
- const placeholderRegex = /\{\{([A-Z_]+)\}\}/g;
158
- let match;
159
- while ((match = placeholderRegex.exec(content)) !== null) {
160
- unreplacedPlaceholders.add(match[1]);
161
- }
162
-
163
- return {
164
- content,
165
- replacedPlaceholders: Array.from(replacedPlaceholders),
166
- unreplacedPlaceholders: Array.from(unreplacedPlaceholders)
167
- };
168
- }
169
-
170
- /**
171
- * Print help message
172
- */
173
- function printHelp() {
174
- console.log(`
175
- ${colors.cyan}MORPH-SPEC Template Renderer${colors.reset}
176
-
177
- ${colors.green}Usage:${colors.reset}
178
- node bin/render-template.js <template-path> <output-path> <variables-json>
179
-
180
- ${colors.green}Arguments:${colors.reset}
181
- template-path Path to the template file (required)
182
- output-path Path where rendered output will be written (required)
183
- variables-json JSON object with variables to replace (required)
184
-
185
- ${colors.green}Standard Placeholders:${colors.reset}
186
- {{FEATURE_NAME}} - kebab-case feature name (e.g., "scheduled-reports")
187
- {{FEATURE_NAME_PASCAL}} - PascalCase (e.g., "ScheduledReports")
188
- {{FEATURE_NAME_CAMEL}} - camelCase (e.g., "scheduledReports")
189
- {{FEATURE_NAME_TITLE}} - Title Case (e.g., "Scheduled Reports")
190
- {{FEATURE_NAME_UPPER_SNAKE}} - UPPER_SNAKE_CASE (e.g., "SCHEDULED_REPORTS")
191
- {{FEATURE_NAME_LOWER_SNAKE}} - lower_snake_case (e.g., "scheduled_reports")
192
- {{FEATURE_TITLE}} - Alias for FEATURE_NAME_TITLE
193
- {{STACK}} - Project stack (e.g., "Blazor", "Next.js")
194
- {{DATE}} - Current date (YYYY-MM-DD)
195
- {{YEAR}} - Current year
196
- {{AUTHOR}} - Author name (from config.json)
197
- {{PROJECT_NAME}} - Project name (from config.json)
198
- {{NAMESPACE}} - C# namespace (from config.json)
199
-
200
- ${colors.green}Example:${colors.reset}
201
- node bin/render-template.js \\
202
- content/.morph/templates/spec.md \\
203
- .morph/project/outputs/scheduled-reports/spec.md \\
204
- '{"FEATURE_NAME":"scheduled-reports","STACK":"Blazor"}'
205
-
206
- ${colors.green}Flags:${colors.reset}
207
- --help, -h Show this help message
208
- --verbose, -v Show detailed replacement information
209
- --dry-run, -d Preview output without writing file
210
-
211
- ${colors.yellow}Note:${colors.reset} DATE, YEAR, AUTHOR, PROJECT_NAME, STACK, NAMESPACE are auto-populated from config.json
212
- `);
213
- }
214
-
215
- /**
216
- * Main CLI logic
217
- */
218
- function main() {
219
- const args = process.argv.slice(2);
220
-
221
- // Check for help flag
222
- if (args.includes('--help') || args.includes('-h')) {
223
- printHelp();
224
- process.exit(0);
225
- }
226
-
227
- // Parse flags
228
- const verbose = args.includes('--verbose') || args.includes('-v');
229
- const dryRun = args.includes('--dry-run') || args.includes('-d');
230
-
231
- // Filter out flags
232
- const flags = ['--verbose', '-v', '--dry-run', '-d', '--help', '-h'];
233
- const cleanArgs = args.filter(arg => !flags.includes(arg));
234
-
235
- // Validate arguments
236
- if (cleanArgs.length < 3) {
237
- console.error(`${colors.red}❌ Error: Missing required arguments${colors.reset}\n`);
238
- printHelp();
239
- process.exit(1);
240
- }
241
-
242
- const [templatePath, outputPath, variablesJson] = cleanArgs;
243
-
244
- // Parse variables JSON
245
- let variables;
246
- try {
247
- variables = JSON.parse(variablesJson);
248
- } catch (error) {
249
- console.error(`${colors.red} Error: Invalid JSON in variables argument${colors.reset}`);
250
- console.error(` ${error.message}\n`);
251
- process.exit(1);
252
- }
253
-
254
- try {
255
- // Render template
256
- const { content, replacedPlaceholders, unreplacedPlaceholders } = renderTemplate(
257
- templatePath,
258
- variables
259
- );
260
-
261
- // Dry run: just preview
262
- if (dryRun) {
263
- console.log(`${colors.cyan}📋 DRY RUN - Preview (file will NOT be written)${colors.reset}\n`);
264
- console.log(content);
265
- console.log(`\n${colors.cyan}═══════════════════════════════════════════${colors.reset}`);
266
- } else {
267
- // Create output directory if it doesn't exist
268
- const outputDir = path.dirname(outputPath);
269
- if (!fs.existsSync(outputDir)) {
270
- fs.mkdirSync(outputDir, { recursive: true });
271
- }
272
-
273
- // Write rendered content
274
- fs.writeFileSync(outputPath, content, 'utf-8');
275
- console.log(`${colors.green}✅ Template rendered successfully${colors.reset}`);
276
- console.log(` Output: ${outputPath}`);
277
- }
278
-
279
- // Verbose output
280
- if (verbose || unreplacedPlaceholders.length > 0) {
281
- console.log(`\n${colors.cyan}📊 Replacement Summary:${colors.reset}`);
282
- console.log(` ${colors.green}Replaced:${colors.reset} ${replacedPlaceholders.length} placeholders`);
283
-
284
- if (replacedPlaceholders.length > 0) {
285
- console.log(` ${replacedPlaceholders.map(p => `{{${p}}}`).join(', ')}`);
286
- }
287
-
288
- if (unreplacedPlaceholders.length > 0) {
289
- console.log(`\n ${colors.yellow}⚠️ Unreplaced:${colors.reset} ${unreplacedPlaceholders.length} placeholders`);
290
- console.log(` ${unreplacedPlaceholders.map(p => `{{${p}}}`).join(', ')}`);
291
- }
292
- }
293
-
294
- process.exit(0);
295
- } catch (error) {
296
- console.error(`${colors.red} Error: ${error.message}${colors.reset}`);
297
- process.exit(1);
298
- }
299
- }
300
-
301
- // Run CLI
302
- main();
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MORPH-SPEC Template Renderer
5
+ *
6
+ * Renders templates by replacing {{PLACEHOLDER}} with actual values.
7
+ *
8
+ * Usage:
9
+ * node bin/render-template.js <template-path> <output-path> <variables-json>
10
+ *
11
+ * Example:
12
+ * node bin/render-template.js \
13
+ * stacks/blazor-azure/.morph/templates/spec.md \
14
+ * .morph/project/outputs/my-feature/spec.md \
15
+ * '{"FEATURE_NAME":"my-feature","STACK":"Blazor","DATE":"2024-01-15"}'
16
+ */
17
+
18
+ import fs from 'fs';
19
+ import path from 'path';
20
+ import { fileURLToPath } from 'url';
21
+ import { resolveConfigDir } from '../src/lib/stack-resolver.js';
22
+
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = path.dirname(__filename);
25
+
26
+ // ANSI color codes
27
+ const colors = {
28
+ green: '\x1b[32m',
29
+ red: '\x1b[31m',
30
+ yellow: '\x1b[33m',
31
+ cyan: '\x1b[36m',
32
+ reset: '\x1b[0m'
33
+ };
34
+
35
+ /**
36
+ * Load config to get default values
37
+ */
38
+ function loadConfig() {
39
+ const configPaths = [
40
+ path.join(process.cwd(), '.morph/config/config.json'),
41
+ path.join(resolveConfigDir(process.cwd()), 'config.json'),
42
+ path.join(resolveConfigDir(path.join(__dirname, '..')), 'config.template.json')
43
+ ];
44
+
45
+ for (const configPath of configPaths) {
46
+ if (fs.existsSync(configPath)) {
47
+ try {
48
+ const content = fs.readFileSync(configPath, 'utf-8');
49
+ return JSON.parse(content);
50
+ } catch (error) {
51
+ // Continue to next path
52
+ }
53
+ }
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Get default variables from config and environment
61
+ */
62
+ function getDefaultVariables() {
63
+ const config = loadConfig();
64
+ const now = new Date();
65
+
66
+ return {
67
+ DATE: now.toISOString().split('T')[0],
68
+ YEAR: now.getFullYear().toString(),
69
+ AUTHOR: config?.project?.author || 'MORPH-SPEC',
70
+ PROJECT_NAME: config?.project?.name || 'Project',
71
+ STACK: config?.project?.stack || 'Blazor',
72
+ NAMESPACE: config?.project?.namespace || 'Project'
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Convert string to various case formats
78
+ */
79
+ function transformCase(str) {
80
+ return {
81
+ // kebab-case → PascalCase
82
+ pascal: str
83
+ .split('-')
84
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
85
+ .join(''),
86
+
87
+ // kebab-case → camelCase
88
+ camel: str
89
+ .split('-')
90
+ .map((word, index) =>
91
+ index === 0
92
+ ? word.toLowerCase()
93
+ : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
94
+ )
95
+ .join(''),
96
+
97
+ // kebab-case → Title Case
98
+ title: str
99
+ .split('-')
100
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
101
+ .join(' '),
102
+
103
+ // kebab-case → UPPER_SNAKE_CASE
104
+ upperSnake: str.toUpperCase().replace(/-/g, '_'),
105
+
106
+ // kebab-case → lower_snake_case
107
+ lowerSnake: str.toLowerCase().replace(/-/g, '_')
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Render template by replacing placeholders
113
+ */
114
+ function renderTemplate(templatePath, variables) {
115
+ if (!fs.existsSync(templatePath)) {
116
+ throw new Error(`Template file not found: ${templatePath}`);
117
+ }
118
+
119
+ let content = fs.readFileSync(templatePath, 'utf-8');
120
+
121
+ // Get default variables
122
+ const defaults = getDefaultVariables();
123
+
124
+ // Merge with provided variables (provided takes precedence)
125
+ const allVariables = { ...defaults, ...variables };
126
+
127
+ // Auto-generate case transformations for FEATURE_NAME if provided
128
+ if (allVariables.FEATURE_NAME) {
129
+ const featureName = allVariables.FEATURE_NAME;
130
+ const transformations = transformCase(featureName);
131
+
132
+ allVariables.FEATURE_NAME_PASCAL = transformations.pascal;
133
+ allVariables.FEATURE_NAME_CAMEL = transformations.camel;
134
+ allVariables.FEATURE_NAME_TITLE = transformations.title;
135
+ allVariables.FEATURE_NAME_UPPER_SNAKE = transformations.upperSnake;
136
+ allVariables.FEATURE_NAME_LOWER_SNAKE = transformations.lowerSnake;
137
+
138
+ // Backward compatibility: FEATURE_TITLE maps to FEATURE_NAME_TITLE
139
+ if (!allVariables.FEATURE_TITLE) {
140
+ allVariables.FEATURE_TITLE = transformations.title;
141
+ }
142
+ }
143
+
144
+ // Track which placeholders were replaced
145
+ const replacedPlaceholders = new Set();
146
+ const unreplacedPlaceholders = new Set();
147
+
148
+ // Replace all placeholders
149
+ for (const [key, value] of Object.entries(allVariables)) {
150
+ const placeholder = `{{${key}}}`;
151
+ if (content.includes(placeholder)) {
152
+ content = content.replaceAll(placeholder, value);
153
+ replacedPlaceholders.add(key);
154
+ }
155
+ }
156
+
157
+ // Find unreplaced placeholders
158
+ const placeholderRegex = /\{\{([A-Z_]+)\}\}/g;
159
+ let match;
160
+ while ((match = placeholderRegex.exec(content)) !== null) {
161
+ unreplacedPlaceholders.add(match[1]);
162
+ }
163
+
164
+ return {
165
+ content,
166
+ replacedPlaceholders: Array.from(replacedPlaceholders),
167
+ unreplacedPlaceholders: Array.from(unreplacedPlaceholders)
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Print help message
173
+ */
174
+ function printHelp() {
175
+ console.log(`
176
+ ${colors.cyan}MORPH-SPEC Template Renderer${colors.reset}
177
+
178
+ ${colors.green}Usage:${colors.reset}
179
+ node bin/render-template.js <template-path> <output-path> <variables-json>
180
+
181
+ ${colors.green}Arguments:${colors.reset}
182
+ template-path Path to the template file (required)
183
+ output-path Path where rendered output will be written (required)
184
+ variables-json JSON object with variables to replace (required)
185
+
186
+ ${colors.green}Standard Placeholders:${colors.reset}
187
+ {{FEATURE_NAME}} - kebab-case feature name (e.g., "scheduled-reports")
188
+ {{FEATURE_NAME_PASCAL}} - PascalCase (e.g., "ScheduledReports")
189
+ {{FEATURE_NAME_CAMEL}} - camelCase (e.g., "scheduledReports")
190
+ {{FEATURE_NAME_TITLE}} - Title Case (e.g., "Scheduled Reports")
191
+ {{FEATURE_NAME_UPPER_SNAKE}} - UPPER_SNAKE_CASE (e.g., "SCHEDULED_REPORTS")
192
+ {{FEATURE_NAME_LOWER_SNAKE}} - lower_snake_case (e.g., "scheduled_reports")
193
+ {{FEATURE_TITLE}} - Alias for FEATURE_NAME_TITLE
194
+ {{STACK}} - Project stack (e.g., "Blazor", "Next.js")
195
+ {{DATE}} - Current date (YYYY-MM-DD)
196
+ {{YEAR}} - Current year
197
+ {{AUTHOR}} - Author name (from config.json)
198
+ {{PROJECT_NAME}} - Project name (from config.json)
199
+ {{NAMESPACE}} - C# namespace (from config.json)
200
+
201
+ ${colors.green}Example:${colors.reset}
202
+ node bin/render-template.js \\
203
+ stacks/blazor-azure/.morph/templates/spec.md \\
204
+ .morph/project/outputs/scheduled-reports/spec.md \\
205
+ '{"FEATURE_NAME":"scheduled-reports","STACK":"Blazor"}'
206
+
207
+ ${colors.green}Flags:${colors.reset}
208
+ --help, -h Show this help message
209
+ --verbose, -v Show detailed replacement information
210
+ --dry-run, -d Preview output without writing file
211
+
212
+ ${colors.yellow}Note:${colors.reset} DATE, YEAR, AUTHOR, PROJECT_NAME, STACK, NAMESPACE are auto-populated from config.json
213
+ `);
214
+ }
215
+
216
+ /**
217
+ * Main CLI logic
218
+ */
219
+ function main() {
220
+ const args = process.argv.slice(2);
221
+
222
+ // Check for help flag
223
+ if (args.includes('--help') || args.includes('-h')) {
224
+ printHelp();
225
+ process.exit(0);
226
+ }
227
+
228
+ // Parse flags
229
+ const verbose = args.includes('--verbose') || args.includes('-v');
230
+ const dryRun = args.includes('--dry-run') || args.includes('-d');
231
+
232
+ // Filter out flags
233
+ const flags = ['--verbose', '-v', '--dry-run', '-d', '--help', '-h'];
234
+ const cleanArgs = args.filter(arg => !flags.includes(arg));
235
+
236
+ // Validate arguments
237
+ if (cleanArgs.length < 3) {
238
+ console.error(`${colors.red}❌ Error: Missing required arguments${colors.reset}\n`);
239
+ printHelp();
240
+ process.exit(1);
241
+ }
242
+
243
+ const [templatePath, outputPath, variablesJson] = cleanArgs;
244
+
245
+ // Parse variables JSON
246
+ let variables;
247
+ try {
248
+ variables = JSON.parse(variablesJson);
249
+ } catch (error) {
250
+ console.error(`${colors.red}❌ Error: Invalid JSON in variables argument${colors.reset}`);
251
+ console.error(` ${error.message}\n`);
252
+ process.exit(1);
253
+ }
254
+
255
+ try {
256
+ // Render template
257
+ const { content, replacedPlaceholders, unreplacedPlaceholders } = renderTemplate(
258
+ templatePath,
259
+ variables
260
+ );
261
+
262
+ // Dry run: just preview
263
+ if (dryRun) {
264
+ console.log(`${colors.cyan}📋 DRY RUN - Preview (file will NOT be written)${colors.reset}\n`);
265
+ console.log(content);
266
+ console.log(`\n${colors.cyan}═══════════════════════════════════════════${colors.reset}`);
267
+ } else {
268
+ // Create output directory if it doesn't exist
269
+ const outputDir = path.dirname(outputPath);
270
+ if (!fs.existsSync(outputDir)) {
271
+ fs.mkdirSync(outputDir, { recursive: true });
272
+ }
273
+
274
+ // Write rendered content
275
+ fs.writeFileSync(outputPath, content, 'utf-8');
276
+ console.log(`${colors.green}✅ Template rendered successfully${colors.reset}`);
277
+ console.log(` Output: ${outputPath}`);
278
+ }
279
+
280
+ // Verbose output
281
+ if (verbose || unreplacedPlaceholders.length > 0) {
282
+ console.log(`\n${colors.cyan}📊 Replacement Summary:${colors.reset}`);
283
+ console.log(` ${colors.green}Replaced:${colors.reset} ${replacedPlaceholders.length} placeholders`);
284
+
285
+ if (replacedPlaceholders.length > 0) {
286
+ console.log(` ${replacedPlaceholders.map(p => `{{${p}}}`).join(', ')}`);
287
+ }
288
+
289
+ if (unreplacedPlaceholders.length > 0) {
290
+ console.log(`\n ${colors.yellow}⚠️ Unreplaced:${colors.reset} ${unreplacedPlaceholders.length} placeholders`);
291
+ console.log(` ${unreplacedPlaceholders.map(p => `{{${p}}}`).join(', ')}`);
292
+ }
293
+ }
294
+
295
+ process.exit(0);
296
+ } catch (error) {
297
+ console.error(`${colors.red}❌ Error: ${error.message}${colors.reset}`);
298
+ process.exit(1);
299
+ }
300
+ }
301
+
302
+ // Run CLI
303
+ main();