@polymorphism-tech/morph-spec 4.10.9 → 4.10.10

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/CLAUDE.md +3 -3
  2. package/LICENSE +71 -71
  3. package/README.md +2 -2
  4. package/bin/morph-spec.js +22 -0
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/agents/README.md +14 -14
  9. package/framework/agents/architecture/standards-architect.md +159 -159
  10. package/framework/agents/infrastructure/azure-architect.md +149 -149
  11. package/framework/agents/infrastructure/infra-architect.md +48 -48
  12. package/framework/hooks/README.md +204 -204
  13. package/framework/hooks/shared/phase-utils.js +139 -139
  14. package/framework/rules/csharp-standards.md +13 -13
  15. package/framework/rules/frontend-standards.md +13 -13
  16. package/framework/rules/infrastructure-standards.md +16 -16
  17. package/framework/rules/testing-standards.md +14 -14
  18. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +254 -254
  19. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +1 -1
  20. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
  21. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +1 -1
  22. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +38 -0
  23. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +1 -1
  24. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
  25. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +1 -1
  26. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +14 -0
  27. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
  28. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +1 -1
  29. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  30. package/framework/standards/STANDARDS.json +2338 -2041
  31. package/framework/standards/frontend/nextjs/nextjs-patterns.md +17 -17
  32. package/framework/standards/infrastructure/supabase/supabase-custom-jwt.md +206 -0
  33. package/framework/templates/.idea/morph-templates.xml +92 -0
  34. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  35. package/framework/templates/README.md +814 -814
  36. package/framework/templates/code/dotnet/contracts/Commands.cs +74 -74
  37. package/framework/templates/code/dotnet/contracts/Entities.cs +25 -25
  38. package/framework/templates/code/dotnet/contracts/Queries.cs +74 -74
  39. package/framework/templates/code/dotnet/contracts/README.md +74 -74
  40. package/framework/templates/code/dotnet/contracts/api-contracts.cs +173 -173
  41. package/framework/templates/code/dotnet/frontend/component.razor +239 -239
  42. package/framework/templates/code/dotnet/jobs/agent.cs +163 -163
  43. package/framework/templates/code/dotnet/jobs/job.cs +171 -171
  44. package/framework/templates/code/dotnet/test.cs +239 -239
  45. package/framework/templates/code/sql/rls-policy.sql +57 -57
  46. package/framework/templates/code/sql/supabase-migration.sql +100 -100
  47. package/framework/templates/code/sql/supabase-migration.template.sql +113 -113
  48. package/framework/templates/code/typescript/contracts.ts +168 -168
  49. package/framework/templates/context/CONTEXT.md +181 -181
  50. package/framework/templates/docs/proposal.md +182 -182
  51. package/framework/templates/docs/user-stories.md +34 -34
  52. package/framework/templates/examples/design-system-examples.md +357 -357
  53. package/framework/templates/examples/spec-examples.md +90 -90
  54. package/framework/templates/feature/decisions.md +187 -187
  55. package/framework/templates/feature/recap.md +146 -146
  56. package/framework/templates/feature/tasks.md +199 -199
  57. package/framework/templates/infrastructure/azure/README.md +286 -286
  58. package/framework/templates/infrastructure/azure/deploy-checklist.md +426 -426
  59. package/framework/templates/infrastructure/azure/parameters.dev.json +29 -29
  60. package/framework/templates/infrastructure/azure/parameters.prod.json +29 -29
  61. package/framework/templates/infrastructure/azure/parameters.staging.json +29 -29
  62. package/framework/templates/infrastructure/docker/docker-compose.template.yml +67 -67
  63. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  64. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  65. package/framework/templates/infrastructure/docker/easypanel.template.json +54 -54
  66. package/package.json +1 -1
  67. package/src/commands/agents/dispatch-agents.js +17 -0
  68. package/src/commands/agents/index.js +13 -13
  69. package/src/commands/agents/project-metrics.js +74 -0
  70. package/src/commands/project/install-plugin-cmd.js +28 -28
  71. package/src/commands/project/setup-infra-cmd.js +12 -12
  72. package/src/commands/standards/context-budget.js +89 -0
  73. package/src/commands/tasks/index.js +4 -4
  74. package/src/commands/templates/index.js +5 -5
  75. package/src/commands/utils/index.js +3 -3
  76. package/src/commands/validation/index.js +4 -4
  77. package/src/core/state/phase-state-machine.js +217 -217
  78. package/src/lib/agents/metrics-aggregator.js +56 -0
  79. package/src/lib/generators/design-system-generator.js +298 -298
  80. package/src/lib/generators/index.js +10 -10
  81. package/src/lib/generators/metadata-extractor.js +387 -387
  82. package/src/lib/stack-filter.js +65 -65
  83. package/src/lib/standards/digest-builder.js +57 -3
  84. package/src/lib/standards/index.js +7 -7
  85. package/src/lib/standards/standards-context-injector.js +302 -302
  86. package/src/lib/validators/architecture/architecture-validator.js +387 -387
  87. package/src/lib/validators/architecture/index.js +7 -7
  88. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +316 -316
  89. package/src/lib/validators/blazor/blazor-state-validator.js +279 -279
  90. package/src/lib/validators/blazor/blazor-validator.js +369 -369
  91. package/src/lib/validators/blazor/index.js +9 -9
  92. package/src/lib/validators/content/content-validator.js +287 -287
  93. package/src/lib/validators/content/index.js +7 -7
  94. package/src/lib/validators/contracts/index.js +7 -7
  95. package/src/lib/validators/css/css-validator.js +352 -352
  96. package/src/lib/validators/css/index.js +7 -7
  97. package/src/lib/validators/design-system/design-system-validator.js +231 -231
  98. package/src/lib/validators/design-system/index.js +7 -7
  99. package/src/lib/validators/nextjs/index.js +6 -6
  100. package/src/lib/validators/nextjs/next-component-validator.js +183 -183
  101. package/src/lib/validators/packages/index.js +7 -7
  102. package/src/lib/validators/packages/package-validator.js +360 -360
  103. package/src/lib/validators/shared/index.js +12 -12
  104. package/src/lib/validators/shared/issue-counter.js +18 -18
  105. package/src/lib/validators/shared/result-formatter.js +124 -124
  106. package/src/lib/validators/ui/index.js +7 -7
  107. package/src/lib/validators/ui/ui-contrast-validator.js +422 -422
  108. package/src/scripts/global-install.js +58 -58
  109. package/src/scripts/install-plugin.js +126 -126
  110. package/src/utils/claude-md-injector.js +90 -90
  111. package/src/utils/file-copier.js +200 -200
  112. package/src/utils/logger.js +32 -32
  113. package/src/utils/skills-installer.js +178 -178
  114. package/src/utils/version-checker.js +175 -175
@@ -196,20 +196,20 @@ Components are copied to `components/ui/` -- NOT an npm dependency, your code to
196
196
  | `useEffect` for data fetching | React Query `useQuery` | No caching, race conditions |
197
197
  | Form validation on submit only | Zod resolver + react-hook-form | Delayed error feedback |
198
198
  | shadcn as npm package | `npx shadcn@latest add` | Copy-paste system, not a dependency |
199
-
200
- ## TypeScript Strict Mode (Required)
201
-
202
- Always enable `"strict": true` in `tsconfig.json`. This is non-negotiable for
203
- agent-assisted development because agents rely on compiler errors to self-correct.
204
- Without strict mode, type/null errors only surface at runtime where agents cannot observe them.
205
-
206
- ```json
207
- {
208
- "compilerOptions": {
209
- "strict": true,
210
- "noUncheckedIndexedAccess": true
211
- }
212
- }
213
- ```
214
-
215
- Strict mode enables: `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`.
199
+
200
+ ## TypeScript Strict Mode (Required)
201
+
202
+ Always enable `"strict": true` in `tsconfig.json`. This is non-negotiable for
203
+ agent-assisted development because agents rely on compiler errors to self-correct.
204
+ Without strict mode, type/null errors only surface at runtime where agents cannot observe them.
205
+
206
+ ```json
207
+ {
208
+ "compilerOptions": {
209
+ "strict": true,
210
+ "noUncheckedIndexedAccess": true
211
+ }
212
+ }
213
+ ```
214
+
215
+ Strict mode enables: `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`.
@@ -0,0 +1,206 @@
1
+ # Supabase Custom JWT Standard
2
+
3
+ > **Scope:** dotnet, nextjs
4
+ > **Layer:** 2 (on keyword)
5
+ > **Keywords:** JWT, custom JWT, JWT signing, token creation, JWT claims, custom claims
6
+ > **Load When:** custom JWT patterns detected (e.g., custom claims, manual token creation)
7
+
8
+ ## JWT Token Creation
9
+
10
+ ### Node.js (jose)
11
+
12
+ ```ts
13
+ import { SignJWT } from "jose";
14
+
15
+ const secret = new TextEncoder().encode(process.env.SUPABASE_JWT_SECRET!);
16
+
17
+ export async function createCustomJWT(userId: string, claims: Record<string, unknown>) {
18
+ return new SignJWT({
19
+ sub: userId,
20
+ role: "authenticated",
21
+ app_metadata: claims,
22
+ aud: "authenticated",
23
+ })
24
+ .setProtectedHeader({ alg: "HS256", typ: "JWT" })
25
+ .setIssuedAt()
26
+ .setExpirationTime("1h")
27
+ .sign(secret);
28
+ }
29
+ ```
30
+
31
+ ### .NET (System.IdentityModel.Tokens.Jwt)
32
+
33
+ ```csharp
34
+ using System.IdentityModel.Tokens.Jwt;
35
+ using System.Security.Claims;
36
+ using Microsoft.IdentityModel.Tokens;
37
+
38
+ public static string CreateCustomJWT(string userId, Dictionary<string, string> appMetadata)
39
+ {
40
+ var key = new SymmetricSecurityKey(
41
+ Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SUPABASE_JWT_SECRET")!));
42
+
43
+ var claims = new List<Claim>
44
+ {
45
+ new(JwtRegisteredClaimNames.Sub, userId),
46
+ new("role", "authenticated"),
47
+ new("aud", "authenticated"),
48
+ };
49
+
50
+ // Add app_metadata claims
51
+ foreach (var (k, v) in appMetadata)
52
+ claims.Add(new Claim($"app_metadata.{k}", v));
53
+
54
+ var token = new JwtSecurityToken(
55
+ claims: claims,
56
+ expires: DateTime.UtcNow.AddHours(1),
57
+ signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
58
+
59
+ return new JwtSecurityTokenHandler().WriteToken(token);
60
+ }
61
+ ```
62
+
63
+ ## Custom Claims Structure
64
+
65
+ ### Required Claims
66
+
67
+ | Claim | Description | Example |
68
+ |-------|-------------|---------|
69
+ | `sub` | User ID (UUID) | `"a1b2c3d4-..."` |
70
+ | `role` | Supabase role | `"authenticated"` |
71
+ | `aud` | Audience | `"authenticated"` |
72
+ | `exp` | Expiration (epoch) | `1710000000` |
73
+
74
+ ### Recommended App Metadata Claims
75
+
76
+ | Claim | Purpose | Used By |
77
+ |-------|---------|---------|
78
+ | `tenant_id` | Multi-tenant RLS filtering | RLS policies |
79
+ | `org_id` | Organization scoping | RLS policies |
80
+ | `permissions` | Fine-grained access | Application logic |
81
+
82
+ ### Structure
83
+
84
+ ```json
85
+ {
86
+ "sub": "user-uuid",
87
+ "role": "authenticated",
88
+ "aud": "authenticated",
89
+ "iat": 1710000000,
90
+ "exp": 1710003600,
91
+ "app_metadata": {
92
+ "tenant_id": "tenant-uuid",
93
+ "org_id": "org-uuid",
94
+ "permissions": ["read", "write"]
95
+ }
96
+ }
97
+ ```
98
+
99
+ **RLS Integration:** Always include `tenant_id` in `app_metadata` for RLS policies. Access in SQL via:
100
+
101
+ ```sql
102
+ -- In RLS policy
103
+ (auth.jwt() -> 'app_metadata' ->> 'tenant_id')::uuid = tenant_id
104
+ ```
105
+
106
+ Cross-ref: [supabase-rls.md](supabase-rls.md)
107
+
108
+ ## JWT Refresh Patterns
109
+
110
+ ### Token Rotation (Recommended)
111
+
112
+ 1. Issue short-lived access tokens (1h max)
113
+ 2. Use refresh tokens with rotation enabled
114
+ 3. Invalidate old refresh token on each rotation
115
+ 4. Store refresh tokens server-side only — never expose to client JS
116
+
117
+ ### Node.js Refresh Flow
118
+
119
+ ```ts
120
+ import { createClient } from "@supabase/supabase-js";
121
+
122
+ const supabase = createClient(url, anonKey);
123
+
124
+ // On auth state change, handle token refresh
125
+ supabase.auth.onAuthStateChange((event, session) => {
126
+ if (event === "TOKEN_REFRESHED" && session) {
127
+ // Update server-side session cookie
128
+ updateSessionCookie(session.access_token, session.refresh_token);
129
+ }
130
+ });
131
+ ```
132
+
133
+ ### .NET Refresh Flow
134
+
135
+ ```csharp
136
+ // Validate and refresh on each request via middleware
137
+ public async Task InvokeAsync(HttpContext context)
138
+ {
139
+ var token = context.Request.Headers.Authorization.ToString().Replace("Bearer ", "");
140
+
141
+ var handler = new JwtSecurityTokenHandler();
142
+ var jwt = handler.ReadJwtToken(token);
143
+
144
+ // If token expires within 5 minutes, trigger refresh
145
+ if (jwt.ValidTo < DateTime.UtcNow.AddMinutes(5))
146
+ {
147
+ var newToken = await RefreshTokenAsync(jwt.Subject);
148
+ context.Response.Headers.Append("X-New-Token", newToken);
149
+ }
150
+
151
+ await _next(context);
152
+ }
153
+ ```
154
+
155
+ ## Validation Setup
156
+
157
+ ### Environment Variables
158
+
159
+ ```env
160
+ # Required — never commit to source control
161
+ SUPABASE_JWT_SECRET=your-jwt-secret-from-supabase-dashboard
162
+ ```
163
+
164
+ - **Always** store JWT secret in environment variables or a secrets manager
165
+ - **Never** hardcode secrets in source code
166
+ - **Use HS256** for shared secrets (default Supabase setup)
167
+ - **Use RS256** for asymmetric keys (advanced: separate sign/verify keys)
168
+
169
+ ### Validation in .NET
170
+
171
+ ```csharp
172
+ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
173
+ .AddJwtBearer(options =>
174
+ {
175
+ options.TokenValidationParameters = new TokenValidationParameters
176
+ {
177
+ ValidateIssuerSigningKey = true,
178
+ IssuerSigningKey = new SymmetricSecurityKey(
179
+ Encoding.UTF8.GetBytes(builder.Configuration["Supabase:JwtSecret"]!)),
180
+ ValidateIssuer = false,
181
+ ValidateAudience = true,
182
+ ValidAudience = "authenticated",
183
+ ClockSkew = TimeSpan.FromSeconds(30),
184
+ };
185
+ });
186
+ ```
187
+
188
+ ### Validation in Node.js
189
+
190
+ ```ts
191
+ import { jwtVerify } from "jose";
192
+
193
+ const secret = new TextEncoder().encode(process.env.SUPABASE_JWT_SECRET!);
194
+
195
+ export async function verifyJWT(token: string) {
196
+ const { payload } = await jwtVerify(token, secret, {
197
+ audience: "authenticated",
198
+ });
199
+ return payload;
200
+ }
201
+ ```
202
+
203
+ ## Cross-References
204
+
205
+ - [Supabase Auth](supabase-auth.md) — authentication flows and client setup
206
+ - [Supabase RLS](supabase-rls.md) — row-level security policies using JWT claims
@@ -0,0 +1,92 @@
1
+ <templateSet group="MORPH Templates">
2
+ <template name="morph-pascal" value="{{pascalCase $FEATURE_NAME$}}" description="Convert to PascalCase (UserAuth)" toReformat="false" toShortenFQNames="true">
3
+ <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
4
+ <context>
5
+ <option name="OTHER" value="true" />
6
+ </context>
7
+ </template>
8
+
9
+ <template name="morph-camel" value="{{camelCase $FEATURE_NAME$}}" description="Convert to camelCase (userAuth)" toReformat="false" toShortenFQNames="true">
10
+ <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
11
+ <context>
12
+ <option name="OTHER" value="true" />
13
+ </context>
14
+ </template>
15
+
16
+ <template name="morph-snake" value="{{snakeCase $FEATURE_NAME$}}" description="Convert to snake_case (user_auth)" toReformat="false" toShortenFQNames="true">
17
+ <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
18
+ <context>
19
+ <option name="OTHER" value="true" />
20
+ </context>
21
+ </template>
22
+
23
+ <template name="morph-title" value="{{titleCase $FEATURE_NAME$}}" description="Convert to Title Case (User Auth)" toReformat="false" toShortenFQNames="true">
24
+ <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
25
+ <context>
26
+ <option name="OTHER" value="true" />
27
+ </context>
28
+ </template>
29
+
30
+ <template name="morph-kebab" value="{{kebabCase $FEATURE_NAME$}}" description="Convert to kebab-case (user-auth)" toReformat="false" toShortenFQNames="true">
31
+ <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
32
+ <context>
33
+ <option name="OTHER" value="true" />
34
+ </context>
35
+ </template>
36
+
37
+ <template name="morph-if" value="{{#if $CONDITION$}}&#10; $END$&#10;{{/if}}" description="Handlebars if block" toReformat="false" toShortenFQNames="true">
38
+ <variable name="CONDITION" expression="" defaultValue="&quot;condition&quot;" alwaysStopAt="true" />
39
+ <context>
40
+ <option name="OTHER" value="true" />
41
+ </context>
42
+ </template>
43
+
44
+ <template name="morph-if-else" value="{{#if $CONDITION$}}&#10; $TRUE_BRANCH$&#10;{{else}}&#10; $FALSE_BRANCH$&#10;{{/if}}" description="Handlebars if-else block" toReformat="false" toShortenFQNames="true">
45
+ <variable name="CONDITION" expression="" defaultValue="&quot;condition&quot;" alwaysStopAt="true" />
46
+ <variable name="TRUE_BRANCH" expression="" defaultValue="&quot;&quot;" alwaysStopAt="true" />
47
+ <variable name="FALSE_BRANCH" expression="" defaultValue="&quot;&quot;" alwaysStopAt="true" />
48
+ <context>
49
+ <option name="OTHER" value="true" />
50
+ </context>
51
+ </template>
52
+
53
+ <template name="morph-each" value="{{#each $ITEMS$}}&#10; {{this.$PROPERTY$}}&#10;{{/each}}" description="Handlebars each loop" toReformat="false" toShortenFQNames="true">
54
+ <variable name="ITEMS" expression="" defaultValue="&quot;items&quot;" alwaysStopAt="true" />
55
+ <variable name="PROPERTY" expression="" defaultValue="&quot;name&quot;" alwaysStopAt="true" />
56
+ <context>
57
+ <option name="OTHER" value="true" />
58
+ </context>
59
+ </template>
60
+
61
+ <template name="morph-service" value="namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}}.Services;&#10;&#10;public class {{pascalCase FEATURE_NAME}}Service(&#10; I{{pascalCase FEATURE_NAME}}Repository repository,&#10; ILogger&lt;{{pascalCase FEATURE_NAME}}Service&gt; logger) : I{{pascalCase FEATURE_NAME}}Service&#10;{&#10; public async Task&lt;{{pascalCase FEATURE_NAME}}Dto?&gt; GetByIdAsync(int id, CancellationToken cancellationToken = default)&#10; {&#10; $END$&#10; }&#10;}" description="MORPH .NET Service class template" toReformat="false" toShortenFQNames="true">
62
+ <context>
63
+ <option name="C_SHARP" value="true" />
64
+ </context>
65
+ </template>
66
+
67
+ <template name="morph-repository" value="public class {{pascalCase FEATURE_NAME}}Repository(AppDbContext context) : I{{pascalCase FEATURE_NAME}}Repository&#10;{&#10; private readonly DbSet&lt;{{pascalCase FEATURE_NAME}}&gt; _dbSet = context.Set&lt;{{pascalCase FEATURE_NAME}}&gt;();&#10;&#10; public async Task&lt;{{pascalCase FEATURE_NAME}}?&gt; GetByIdAsync(int id, CancellationToken cancellationToken = default)&#10; {&#10; $END$&#10; }&#10;}" description="MORPH .NET Repository class template" toReformat="false" toShortenFQNames="true">
68
+ <context>
69
+ <option name="C_SHARP" value="true" />
70
+ </context>
71
+ </template>
72
+
73
+ <template name="morph-component" value="@page &quot;/{{kebabCase FEATURE_NAME}}&quot;&#10;@inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service&#10;@inject ILogger&lt;{{pascalCase FEATURE_NAME}}Page&gt; Logger&#10;&#10;&lt;PageTitle&gt;{{pascalCase FEATURE_NAME}}&lt;/PageTitle&gt;&#10;&#10;&lt;div class=&quot;container-fluid&quot;&gt;&#10; $END$&#10;&lt;/div&gt;&#10;&#10;@code {&#10;&#10;}" description="MORPH Blazor component template" toReformat="false" toShortenFQNames="true">
74
+ <context>
75
+ <option name="HTML" value="true" />
76
+ </context>
77
+ </template>
78
+
79
+ <template name="morph-migration" value="public partial class Add{{pascalCase FEATURE_NAME}} : Migration&#10;{&#10; protected override void Up(MigrationBuilder migrationBuilder)&#10; {&#10; migrationBuilder.CreateTable(&#10; name: &quot;{{pascalCase FEATURE_NAME}}s&quot;,&#10; columns: table =&gt; new&#10; {&#10; Id = table.Column&lt;int&gt;(type: &quot;int&quot;, nullable: false)&#10; .Annotation(&quot;SqlServer:Identity&quot;, &quot;1, 1&quot;),&#10; $END$&#10; },&#10; constraints: table =&gt;&#10; {&#10; table.PrimaryKey(&quot;PK_{{pascalCase FEATURE_NAME}}s&quot;, x =&gt; x.Id);&#10; });&#10; }&#10;&#10; protected override void Down(MigrationBuilder migrationBuilder)&#10; {&#10; migrationBuilder.DropTable(name: &quot;{{pascalCase FEATURE_NAME}}s&quot;);&#10; }&#10;}" description="MORPH EF Core migration template" toReformat="false" toShortenFQNames="true">
80
+ <context>
81
+ <option name="C_SHARP" value="true" />
82
+ </context>
83
+ </template>
84
+
85
+ <template name="morph-ts-contract" value="export interface {{pascalCase FEATURE_NAME}} {&#10; id: number;&#10; $FIELD1$&#10;}&#10;&#10;export interface Create{{pascalCase FEATURE_NAME}}Request {&#10; $FIELD2$&#10;}&#10;&#10;export interface Update{{pascalCase FEATURE_NAME}}Request {&#10; $END$&#10;}" description="MORPH TypeScript contract template" toReformat="false" toShortenFQNames="true">
86
+ <variable name="FIELD1" expression="" defaultValue="&quot;&quot;" alwaysStopAt="true" />
87
+ <variable name="FIELD2" expression="" defaultValue="&quot;&quot;" alwaysStopAt="true" />
88
+ <context>
89
+ <option name="TypeScript" value="true" />
90
+ </context>
91
+ </template>
92
+ </templateSet>
@@ -0,0 +1,186 @@
1
+ {
2
+ "MORPH Template - PascalCase": {
3
+ "prefix": "morph-pascal",
4
+ "body": ["{{pascalCase ${1:FEATURE_NAME}}}"],
5
+ "description": "Convert to PascalCase (UserAuth)"
6
+ },
7
+ "MORPH Template - camelCase": {
8
+ "prefix": "morph-camel",
9
+ "body": ["{{camelCase ${1:FEATURE_NAME}}}"],
10
+ "description": "Convert to camelCase (userAuth)"
11
+ },
12
+ "MORPH Template - snake_case": {
13
+ "prefix": "morph-snake",
14
+ "body": ["{{snakeCase ${1:FEATURE_NAME}}}"],
15
+ "description": "Convert to snake_case (user_auth)"
16
+ },
17
+ "MORPH Template - Title Case": {
18
+ "prefix": "morph-title",
19
+ "body": ["{{titleCase ${1:FEATURE_NAME}}}"],
20
+ "description": "Convert to Title Case (User Auth)"
21
+ },
22
+ "MORPH Template - kebab-case": {
23
+ "prefix": "morph-kebab",
24
+ "body": ["{{kebabCase ${1:FEATURE_NAME}}}"],
25
+ "description": "Convert to kebab-case (user-auth)"
26
+ },
27
+ "MORPH Template - UPPER_SNAKE_CASE": {
28
+ "prefix": "morph-upper-snake",
29
+ "body": ["{{upperSnakeCase ${1:FEATURE_NAME}}}"],
30
+ "description": "Convert to UPPER_SNAKE_CASE (USER_AUTH)"
31
+ },
32
+ "MORPH Template - Conditional If": {
33
+ "prefix": "morph-if",
34
+ "body": [
35
+ "{{#if ${1:condition}}}",
36
+ " $2",
37
+ "{{/if}}"
38
+ ],
39
+ "description": "Handlebars if block"
40
+ },
41
+ "MORPH Template - Conditional If-Else": {
42
+ "prefix": "morph-if-else",
43
+ "body": [
44
+ "{{#if ${1:condition}}}",
45
+ " $2",
46
+ "{{else}}",
47
+ " $3",
48
+ "{{/if}}"
49
+ ],
50
+ "description": "Handlebars if-else block"
51
+ },
52
+ "MORPH Template - Each Loop": {
53
+ "prefix": "morph-each",
54
+ "body": [
55
+ "{{#each ${1:items}}}",
56
+ " {{this.$2}}",
57
+ "{{/each}}"
58
+ ],
59
+ "description": "Handlebars each loop"
60
+ },
61
+ "MORPH Template - Unless": {
62
+ "prefix": "morph-unless",
63
+ "body": [
64
+ "{{#unless ${1:condition}}}",
65
+ " $2",
66
+ "{{/unless}}"
67
+ ],
68
+ "description": "Handlebars unless block"
69
+ },
70
+ "MORPH Template - .NET Service Class": {
71
+ "prefix": "morph-service",
72
+ "body": [
73
+ "namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}}.Services;",
74
+ "",
75
+ "public class {{pascalCase FEATURE_NAME}}Service(",
76
+ " I{{pascalCase FEATURE_NAME}}Repository repository,",
77
+ " ILogger<{{pascalCase FEATURE_NAME}}Service> logger) : I{{pascalCase FEATURE_NAME}}Service",
78
+ "{",
79
+ " public async Task<{{pascalCase FEATURE_NAME}}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default)",
80
+ " {",
81
+ " $1",
82
+ " }",
83
+ "}"
84
+ ],
85
+ "description": "MORPH .NET Service class template"
86
+ },
87
+ "MORPH Template - .NET Repository Class": {
88
+ "prefix": "morph-repository",
89
+ "body": [
90
+ "public class {{pascalCase FEATURE_NAME}}Repository(AppDbContext context) : I{{pascalCase FEATURE_NAME}}Repository",
91
+ "{",
92
+ " private readonly DbSet<{{pascalCase FEATURE_NAME}}> _dbSet = context.Set<{{pascalCase FEATURE_NAME}}>();",
93
+ "",
94
+ " public async Task<{{pascalCase FEATURE_NAME}}?> GetByIdAsync(int id, CancellationToken cancellationToken = default)",
95
+ " {",
96
+ " $1",
97
+ " }",
98
+ "}"
99
+ ],
100
+ "description": "MORPH .NET Repository class template"
101
+ },
102
+ "MORPH Template - Blazor Component": {
103
+ "prefix": "morph-component",
104
+ "body": [
105
+ "@page \"/{{kebabCase FEATURE_NAME}}\"",
106
+ "@inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service",
107
+ "@inject ILogger<{{pascalCase FEATURE_NAME}}Page> Logger",
108
+ "",
109
+ "<PageTitle>{{pascalCase FEATURE_NAME}}</PageTitle>",
110
+ "",
111
+ "<div class=\"container-fluid\">",
112
+ " $1",
113
+ "</div>",
114
+ "",
115
+ "@code {",
116
+ " $2",
117
+ "}"
118
+ ],
119
+ "description": "MORPH Blazor component template"
120
+ },
121
+ "MORPH Template - EF Core Migration": {
122
+ "prefix": "morph-migration",
123
+ "body": [
124
+ "public partial class Add{{pascalCase FEATURE_NAME}} : Migration",
125
+ "{",
126
+ " protected override void Up(MigrationBuilder migrationBuilder)",
127
+ " {",
128
+ " migrationBuilder.CreateTable(",
129
+ " name: \"{{pascalCase FEATURE_NAME}}s\",",
130
+ " columns: table => new",
131
+ " {",
132
+ " Id = table.Column<int>(type: \"int\", nullable: false)",
133
+ " .Annotation(\"SqlServer:Identity\", \"1, 1\"),",
134
+ " $1",
135
+ " },",
136
+ " constraints: table =>",
137
+ " {",
138
+ " table.PrimaryKey(\"PK_{{pascalCase FEATURE_NAME}}s\", x => x.Id);",
139
+ " });",
140
+ " }",
141
+ "",
142
+ " protected override void Down(MigrationBuilder migrationBuilder)",
143
+ " {",
144
+ " migrationBuilder.DropTable(name: \"{{pascalCase FEATURE_NAME}}s\");",
145
+ " }",
146
+ "}"
147
+ ],
148
+ "description": "MORPH EF Core migration template"
149
+ },
150
+ "MORPH Template - Hangfire Job": {
151
+ "prefix": "morph-job",
152
+ "body": [
153
+ "public class {{pascalCase FEATURE_NAME}}ProcessorJob(",
154
+ " I{{pascalCase FEATURE_NAME}}Service service,",
155
+ " ILogger<{{pascalCase FEATURE_NAME}}ProcessorJob> logger) : I{{pascalCase FEATURE_NAME}}ProcessorJob",
156
+ "{",
157
+ " [AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]",
158
+ " [Queue(\"default\")]",
159
+ " [JobDisplayName(\"{{pascalCase FEATURE_NAME}} Processing - ID: {0}\")]",
160
+ " public async Task ExecuteAsync(int id, CancellationToken cancellationToken)",
161
+ " {",
162
+ " $1",
163
+ " }",
164
+ "}"
165
+ ],
166
+ "description": "MORPH Hangfire job template"
167
+ },
168
+ "MORPH Template - TypeScript Contract": {
169
+ "prefix": "morph-ts-contract",
170
+ "body": [
171
+ "export interface {{pascalCase FEATURE_NAME}} {",
172
+ " id: number;",
173
+ " $1",
174
+ "}",
175
+ "",
176
+ "export interface Create{{pascalCase FEATURE_NAME}}Request {",
177
+ " $2",
178
+ "}",
179
+ "",
180
+ "export interface Update{{pascalCase FEATURE_NAME}}Request {",
181
+ " $3",
182
+ "}"
183
+ ],
184
+ "description": "MORPH TypeScript contract template"
185
+ }
186
+ }