@polymorphism-tech/morph-spec 4.12.0 → 4.13.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 (314) hide show
  1. package/README.md +21 -29
  2. package/bin/morph-spec.js +41 -24
  3. package/bin/task-manager.js +962 -1341
  4. package/bin/validate.js +1 -1
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +10 -11
  7. package/docs/QUICKSTART.md +15 -15
  8. package/framework/CLAUDE.md +5 -2
  9. package/framework/agents/README.md +3 -4
  10. package/framework/agents/ai-agents/ai-system-architect.md +6 -10
  11. package/framework/agents/architecture/seo-growth-hacker.md +166 -326
  12. package/framework/agents/architecture/standards-architect.md +21 -31
  13. package/framework/agents/backend/dotnet-senior.md +160 -86
  14. package/framework/agents/backend/ef-modeler.md +2 -6
  15. package/framework/agents/backend/hangfire-orchestrator.md +8 -9
  16. package/framework/agents/backend/ms-agent-expert.md +1 -1
  17. package/framework/agents/frontend/nextjs-expert.md +198 -133
  18. package/framework/agents/frontend/ui-ux-designer.md +226 -267
  19. package/framework/agents/infrastructure/infra-architect.md +191 -27
  20. package/framework/agents/integrations/clerk-auth.md +19 -26
  21. package/framework/agents/integrations/hangfire-integration.md +1 -4
  22. package/framework/agents.json +215 -1453
  23. package/framework/commands/morph-apply.md +4 -4
  24. package/framework/commands/morph-preflight.md +4 -26
  25. package/framework/commands/morph-proposal.md +19 -19
  26. package/framework/commands/morph-status.md +2 -2
  27. package/framework/commands/morph-troubleshoot.md +6 -13
  28. package/framework/hooks/README.md +1 -6
  29. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +4 -6
  30. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +12 -7
  31. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +2 -2
  32. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +2 -2
  33. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +5 -10
  34. package/framework/hooks/claude-code/session-start/inject-morph-context.js +63 -30
  35. package/framework/hooks/claude-code/session-start/resume-context.js +77 -0
  36. package/framework/hooks/claude-code/statusline.py +4 -5
  37. package/framework/hooks/claude-code/stop/validate-completion.js +4 -4
  38. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +8 -8
  39. package/framework/hooks/shared/compact-restore.js +28 -5
  40. package/framework/hooks/shared/phase-utils.js +4 -8
  41. package/framework/hooks/shared/state-reader.js +34 -3
  42. package/framework/phases.json +7 -45
  43. package/framework/rules/ai-agents-standards.md +15 -0
  44. package/framework/rules/asaas-standards.md +13 -0
  45. package/framework/rules/clerk-standards.md +13 -0
  46. package/framework/rules/csharp-standards.md +8 -4
  47. package/framework/rules/data-standards.md +18 -0
  48. package/framework/rules/dotnet-templates.md +87 -0
  49. package/framework/rules/frontend-standards.md +13 -13
  50. package/framework/rules/hangfire-standards.md +13 -0
  51. package/framework/rules/infrastructure-standards.md +27 -16
  52. package/framework/rules/morph-workflow.md +73 -13
  53. package/framework/rules/nextjs-standards.md +1 -0
  54. package/framework/rules/nextjs-templates.md +40 -0
  55. package/framework/rules/resend-standards.md +12 -0
  56. package/framework/rules/supabase-standards.md +29 -0
  57. package/framework/skills/README.md +6 -8
  58. package/framework/skills/level-0-meta/mcp-registry.json +21 -99
  59. package/framework/{standards/ai-agents/team-orchestration.md → skills/level-0-meta/morph-agent-teams/SKILL.md} +472 -479
  60. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +28 -1
  61. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  62. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +1 -5
  63. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +1 -1
  64. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +23 -41
  65. package/framework/skills/level-0-meta/morph-init/SKILL.md +18 -14
  66. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +225 -0
  67. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +20 -0
  68. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +4 -8
  69. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +15 -59
  70. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +7 -19
  71. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +8 -8
  72. package/framework/skills/level-1-workflows/README.md +1 -1
  73. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +49 -50
  74. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +58 -124
  75. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +5 -50
  76. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +1 -1
  77. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +127 -159
  78. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +4 -3
  79. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +2 -2
  80. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +1 -1
  81. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +17 -13
  82. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +22 -40
  83. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -2
  84. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +5 -5
  85. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +57 -77
  86. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  87. package/framework/squad-templates/backend-only.json +6 -6
  88. package/framework/squad-templates/frontend-only.json +17 -8
  89. package/framework/squad-templates/full-stack.json +29 -24
  90. package/framework/standards/STANDARDS.json +1021 -2369
  91. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +200 -0
  92. package/framework/standards/backend/dotnet/result-pattern.md +213 -0
  93. package/framework/standards/backend/dotnet/scalar-ui.md +172 -0
  94. package/framework/standards/backend/dotnet/scrutor.md +158 -0
  95. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -0
  96. package/framework/standards/frontend/nextjs/motion-patterns.md +1 -1
  97. package/framework/standards/infrastructure/supabase/mcp-setup.md +83 -193
  98. package/framework/standards/integration/mcp/mcp-tools.md +91 -77
  99. package/framework/templates/IDE-SNIPPETS.md +2 -6
  100. package/framework/templates/README.md +709 -814
  101. package/framework/templates/REGISTRY.json +223 -762
  102. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -0
  103. package/framework/templates/code/dotnet/abstractions/error.cs +60 -0
  104. package/framework/templates/code/dotnet/abstractions/handler.cs +12 -0
  105. package/framework/templates/code/dotnet/abstractions/result.cs +74 -0
  106. package/framework/templates/code/dotnet/backend/repository.cs +148 -141
  107. package/framework/templates/code/dotnet/constants/api-tags.cs +6 -0
  108. package/framework/templates/code/dotnet/exceptions/exception-handler.cs +32 -0
  109. package/framework/templates/code/dotnet/extensions/database-extensions.cs +20 -0
  110. package/framework/templates/code/dotnet/extensions/handler-registration.cs +38 -0
  111. package/framework/templates/code/dotnet/extensions/health-checks.cs +24 -0
  112. package/framework/templates/code/dotnet/extensions/map-endpoints.cs +35 -0
  113. package/framework/templates/code/dotnet/extensions/result-extensions.cs +29 -0
  114. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -0
  115. package/framework/templates/code/dotnet/feature/entity.cs.hbs +8 -0
  116. package/framework/templates/code/dotnet/feature/errors.cs.hbs +8 -0
  117. package/framework/templates/code/dotnet/feature/handler.cs.hbs +24 -0
  118. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -0
  119. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -0
  120. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -0
  121. package/framework/templates/code/dotnet/jobs/job.cs +21 -17
  122. package/framework/templates/code/dotnet/pipelines/logging-decorator.cs +31 -0
  123. package/framework/templates/code/dotnet/pipelines/validation-decorator.cs +97 -0
  124. package/framework/templates/code/dotnet/test.cs +192 -106
  125. package/framework/templates/context/ARCHITECTURE.md +89 -0
  126. package/framework/templates/context/CONTEXT-FEATURE.md +2 -2
  127. package/framework/templates/context/CONTEXT.md +180 -181
  128. package/framework/templates/context/design-system.md +1 -5
  129. package/framework/templates/docs/onboarding.md +2 -3
  130. package/framework/templates/docs/proposal.md +171 -182
  131. package/framework/templates/docs/spec.md +1 -3
  132. package/framework/templates/docs/ui-components.md +0 -72
  133. package/framework/templates/examples/design-system-examples.md +4 -4
  134. package/framework/templates/feature/decisions.md +0 -66
  135. package/framework/templates/feature/recap.md +1 -2
  136. package/framework/templates/infrastructure/github/README.md +56 -593
  137. package/framework/templates/integrations/clerk-config.cs +0 -41
  138. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +2 -2
  139. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -2
  140. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +83 -126
  141. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +2 -2
  142. package/framework/templates/project/validate.js +2 -4
  143. package/framework/templates/ui/design-system.css +1 -1
  144. package/framework/workflows/README.md +8 -8
  145. package/framework/workflows/configs/fast-track.json +51 -0
  146. package/framework/workflows/configs/full.json +68 -0
  147. package/framework/workflows/configs/standard.json +7 -3
  148. package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +1 -1
  149. package/framework/workflows/docs/enforcement-pipeline.md +18 -19
  150. package/framework/workflows/docs/full-morph.md +69 -76
  151. package/framework/workflows/docs/standard.md +49 -44
  152. package/framework/workflows/docs/ui-refresh.md +1 -1
  153. package/package.json +3 -3
  154. package/src/commands/agents/dispatch-agents.js +55 -116
  155. package/src/commands/context/context-refresh.js +79 -0
  156. package/src/commands/phase/phase-reset.js +1 -1
  157. package/src/commands/project/doctor.js +10 -32
  158. package/src/commands/project/init.js +12 -0
  159. package/src/commands/project/status.js +6 -12
  160. package/src/commands/scaffold/scaffold.js +131 -0
  161. package/src/commands/scope/escalate.js +1 -1
  162. package/src/commands/state/advance-phase.js +15 -29
  163. package/src/commands/state/state.js +20 -5
  164. package/src/commands/tasks/task.js +0 -30
  165. package/src/commands/validation/validate-feature.js +3 -10
  166. package/src/core/paths/output-schema.js +12 -13
  167. package/src/core/state/phase-state-machine.js +211 -217
  168. package/src/core/state/state-manager.js +9 -9
  169. package/src/lib/agents/agents-manager.js +2 -2
  170. package/src/lib/agents/output-collector.js +1 -2
  171. package/src/lib/detectors/design-system-detector.js +4 -4
  172. package/src/lib/detectors/index.js +1 -11
  173. package/src/lib/generators/architecture-scanner.js +640 -0
  174. package/src/lib/generators/context-generator.js +3 -3
  175. package/src/lib/generators/design-system-generator.js +508 -587
  176. package/src/lib/generators/index.js +12 -10
  177. package/src/lib/generators/metadata-extractor.js +386 -387
  178. package/src/lib/generators/recap-generator.js +4 -4
  179. package/src/lib/generators/scaffold-generator.js +317 -0
  180. package/src/lib/generators/settings-generator.js +0 -1
  181. package/src/lib/phase-chain/phase-validator.js +64 -42
  182. package/src/lib/stack/stack-profile.js +2 -2
  183. package/src/lib/standards/digest-builder.js +72 -285
  184. package/src/lib/tasks/task-parser.js +11 -5
  185. package/src/lib/validators/architecture/architecture-validator.js +259 -387
  186. package/src/lib/validators/content/content-validator.js +287 -287
  187. package/src/lib/validators/css/css-validator.js +340 -352
  188. package/src/lib/validators/packages/package-validator.js +347 -360
  189. package/src/lib/validators/shared/issue-counter.js +14 -18
  190. package/src/lib/validators/validation-runner.js +3 -19
  191. package/src/scripts/setup-infra.js +1 -1
  192. package/src/utils/agents-installer.js +24 -3
  193. package/src/utils/file-copier.js +1 -0
  194. package/src/utils/hooks-installer.js +11 -19
  195. package/src/utils/skills-installer.js +178 -178
  196. package/framework/agents/architecture/prompt-engineer.md +0 -195
  197. package/framework/agents/backend/api-designer.md +0 -106
  198. package/framework/agents/frontend/blazor-builder.md +0 -229
  199. package/framework/agents/frontend/nextjs-ui-specialist.md +0 -218
  200. package/framework/agents/infrastructure/azure-architect.md +0 -150
  201. package/framework/agents/infrastructure/azure-deploy-specialist.md +0 -707
  202. package/framework/agents/infrastructure/bicep-architect.md +0 -132
  203. package/framework/agents/infrastructure/container-specialist.md +0 -137
  204. package/framework/agents/infrastructure/devops-engineer.md +0 -126
  205. package/framework/agents/integrations/azure-identity.md +0 -148
  206. package/framework/agents/quality/code-analyzer.md +0 -239
  207. package/framework/agents/quality/testing-specialist.md +0 -130
  208. package/framework/hooks/claude-code/notification/approval-reminder.js +0 -57
  209. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +0 -109
  210. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +0 -78
  211. package/framework/index/troubleshooting-index.json +0 -184
  212. package/framework/rules/testing-standards.md +0 -14
  213. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +0 -230
  214. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +0 -70
  215. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +0 -256
  216. package/framework/skills/level-1-workflows/morph-phase-clarify/references/clarifications-example.md +0 -117
  217. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +0 -269
  218. package/framework/standards/ai-agents/blazor-ui.md +0 -364
  219. package/framework/standards/architecture/ddd/aggregates.md +0 -120
  220. package/framework/standards/architecture/ddd/bounded-contexts.md +0 -105
  221. package/framework/standards/architecture/ddd/complexity-levels.md +0 -108
  222. package/framework/standards/architecture/ddd/entities.md +0 -99
  223. package/framework/standards/architecture/ddd/ubiquitous-language.md +0 -58
  224. package/framework/standards/architecture/ddd/value-objects.md +0 -124
  225. package/framework/standards/backend/api/minimal-api.md +0 -494
  226. package/framework/standards/backend/api/rest.md +0 -492
  227. package/framework/standards/backend/authentication/passkeys.md +0 -428
  228. package/framework/standards/backend/database/ef-core.md +0 -199
  229. package/framework/standards/backend/database/migrations.md +0 -393
  230. package/framework/standards/backend/database/repository-patterns.md +0 -528
  231. package/framework/standards/backend/dotnet/async.md +0 -366
  232. package/framework/standards/backend/dotnet/core.md +0 -117
  233. package/framework/standards/backend/dotnet/di.md +0 -439
  234. package/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
  235. package/framework/standards/context/analytics.md +0 -96
  236. package/framework/standards/context/bundles.md +0 -110
  237. package/framework/standards/context/priming.md +0 -78
  238. package/framework/standards/core/architecture.md +0 -185
  239. package/framework/standards/core/coding.md +0 -214
  240. package/framework/standards/core/git-branching-strategy.md +0 -403
  241. package/framework/standards/core/git.md +0 -185
  242. package/framework/standards/core/testing.md +0 -295
  243. package/framework/standards/data/nosql/blob-storage.md +0 -102
  244. package/framework/standards/data/vector-search/azure-ai-search.md +0 -121
  245. package/framework/standards/frontend/blazor/design-checklist.md +0 -222
  246. package/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
  247. package/framework/standards/frontend/blazor/fluent-ui.md +0 -137
  248. package/framework/standards/frontend/blazor/html-conversion.md +0 -184
  249. package/framework/standards/frontend/blazor/lifecycle.md +0 -195
  250. package/framework/standards/frontend/blazor/pitfalls.md +0 -198
  251. package/framework/standards/frontend/blazor/state.md +0 -191
  252. package/framework/standards/frontend/design-system/aesthetic-direction.md +0 -266
  253. package/framework/standards/frontend/design-system/design-trends.md +0 -171
  254. package/framework/standards/infrastructure/azure/azure.md +0 -624
  255. package/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
  256. package/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
  257. package/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
  258. package/framework/standards/infrastructure/azure/services/functions.md +0 -486
  259. package/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
  260. package/framework/standards/infrastructure/azure/services/storage.md +0 -407
  261. package/framework/standards/infrastructure/vercel/vercel-database.md +0 -106
  262. package/framework/standards/integration/api/graphql.md +0 -91
  263. package/framework/standards/integration/api/grpc.md +0 -114
  264. package/framework/standards/integration/api/rest-design.md +0 -95
  265. package/framework/standards/integration/event-driven/cqrs.md +0 -101
  266. package/framework/standards/integration/event-driven/event-sourcing.md +0 -124
  267. package/framework/standards/integration/event-driven/service-bus.md +0 -95
  268. package/framework/standards/observability/logging.md +0 -131
  269. package/framework/standards/observability/metrics.md +0 -121
  270. package/framework/standards/observability/monitoring.md +0 -114
  271. package/framework/standards/observability/tracing.md +0 -132
  272. package/framework/standards/workflows/parallel-execution.md +0 -112
  273. package/framework/standards/workflows/thread-management.md +0 -113
  274. package/framework/templates/code/dotnet/backend/service.cs +0 -139
  275. package/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
  276. package/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
  277. package/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
  278. package/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
  279. package/framework/templates/code/dotnet/contracts/contracts-level1.cs +0 -69
  280. package/framework/templates/code/dotnet/contracts/contracts-level2.cs +0 -86
  281. package/framework/templates/code/dotnet/contracts/contracts-level3.cs +0 -41
  282. package/framework/templates/code/dotnet/frontend/component.razor +0 -239
  283. package/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
  284. package/framework/templates/infrastructure/azure/README.md +0 -286
  285. package/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
  286. package/framework/templates/infrastructure/azure/app-service.bicep +0 -164
  287. package/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
  288. package/framework/templates/infrastructure/azure/container-app.bicep +0 -156
  289. package/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
  290. package/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
  291. package/framework/templates/infrastructure/azure/deploy.sh +0 -208
  292. package/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
  293. package/framework/templates/infrastructure/azure/main.bicep +0 -189
  294. package/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
  295. package/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
  296. package/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
  297. package/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
  298. package/framework/templates/infrastructure/azure/storage.bicep +0 -106
  299. package/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
  300. package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
  301. package/framework/templates/integrations/azure-identity-config.cs +0 -288
  302. package/framework/templates/project-structure/dotnet-ddd.md +0 -70
  303. package/framework/templates/saas/subscription.cs +0 -347
  304. package/framework/templates/saas/tenant.cs +0 -338
  305. package/framework/templates/ui/FluentDesignTheme.cs +0 -149
  306. package/framework/templates/ui/MudTheme.cs +0 -281
  307. package/framework/workflows/configs/nodejs-cli.json +0 -40
  308. package/src/commands/standards/context-budget.js +0 -89
  309. package/src/lib/standards/index.js +0 -7
  310. package/src/lib/standards/standards-context-injector.js +0 -302
  311. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +0 -316
  312. package/src/lib/validators/blazor/blazor-state-validator.js +0 -279
  313. package/src/lib/validators/blazor/blazor-validator.js +0 -369
  314. package/src/lib/validators/blazor/index.js +0 -9
@@ -1,238 +1,324 @@
1
1
  // ============================================================
2
- // TEST TEMPLATE
2
+ // TEST TEMPLATE (VSA Pattern — Handler + Validator)
3
3
  // Generated by MORPH Framework
4
4
  // ============================================================
5
5
 
6
6
  using FluentAssertions;
7
+ using FluentValidation;
8
+ using FluentValidation.Results;
7
9
  using Microsoft.Extensions.Logging;
8
10
  using NSubstitute;
11
+ using {{pascalCase PROJECT_NAME}}.Repository;
9
12
  using Xunit;
10
13
 
11
- namespace {{NAMESPACE}}.Tests.Unit.Features.{{pascalCase FEATURE_NAME}};
14
+ namespace {{pascalCase PROJECT_NAME}}.Tests.Unit.Features.{{pascalCase FEATURE_NAME}};
15
+
16
+ // ============================================================
17
+ // Handler Tests
18
+ // ============================================================
12
19
 
13
20
  /// <summary>
14
- /// Unit tests for {{pascalCase FEATURE_NAME}}Service.
21
+ /// Unit tests for {{pascalCase FEATURE_NAME}} handlers.
22
+ /// Tests handlers directly with mocked IRepository and IUnitOfWork.
15
23
  /// </summary>
16
- public class {{pascalCase FEATURE_NAME}}ServiceTests
24
+ public class {{pascalCase FEATURE_NAME}}HandlerTests
17
25
  {
18
- private readonly I{{pascalCase FEATURE_NAME}}Repository _repository;
19
- private readonly ILogger<{{pascalCase FEATURE_NAME}}Service> _logger;
20
- private readonly {{pascalCase FEATURE_NAME}}Service _sut;
26
+ private readonly IRepository<{{pascalCase FEATURE_NAME}}> _repository;
27
+ private readonly IUnitOfWork _unitOfWork;
28
+ private readonly ILogger<Create{{pascalCase FEATURE_NAME}}.Handler> _logger;
21
29
 
22
- public {{pascalCase FEATURE_NAME}}ServiceTests()
30
+ public {{pascalCase FEATURE_NAME}}HandlerTests()
23
31
  {
24
- _repository = Substitute.For<I{{pascalCase FEATURE_NAME}}Repository>();
25
- _logger = Substitute.For<ILogger<{{pascalCase FEATURE_NAME}}Service>>();
26
- _sut = new {{pascalCase FEATURE_NAME}}Service(_repository, _logger);
32
+ _repository = Substitute.For<IRepository<{{pascalCase FEATURE_NAME}}>>();
33
+ _unitOfWork = Substitute.For<IUnitOfWork>();
34
+ _logger = Substitute.For<ILogger<Create{{pascalCase FEATURE_NAME}}.Handler>>();
27
35
  }
28
36
 
29
- #region GetByIdAsync Tests
37
+ #region Create Handler Tests
30
38
 
31
39
  [Fact]
32
- public async Task GetByIdAsync_WithValidId_ReturnsDto()
40
+ public async Task Create_HandleAsync_WithValidCommand_ReturnsSuccessResult()
33
41
  {
34
42
  // Arrange
35
- var id = 1;
36
- var entity = CreateTestEntity(id, "Test {{pascalCase FEATURE_NAME}}");
37
- _repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
38
- .Returns(entity);
43
+ var command = new Create{{pascalCase FEATURE_NAME}}.Command("Test {{pascalCase FEATURE_NAME}}");
44
+ var handler = new Create{{pascalCase FEATURE_NAME}}.Handler(_repository, _unitOfWork, _logger);
45
+
46
+ _repository.AddAsync(Arg.Any<{{pascalCase FEATURE_NAME}}>(), Arg.Any<CancellationToken>())
47
+ .Returns(callInfo => callInfo.Arg<{{pascalCase FEATURE_NAME}}>());
48
+
49
+ _unitOfWork.CommitAsync(Arg.Any<CancellationToken>())
50
+ .Returns(1);
39
51
 
40
52
  // Act
41
- var result = await _sut.GetByIdAsync(id);
53
+ var result = await handler.HandleAsync(command, CancellationToken.None);
42
54
 
43
55
  // Assert
44
- result.Should().NotBeNull();
45
- result!.Id.Should().Be(id);
46
- result.Name.Should().Be("Test {{pascalCase FEATURE_NAME}}");
56
+ result.IsSuccess.Should().BeTrue();
57
+ result.Value.Should().NotBeNull();
58
+ result.Value.Name.Should().Be("Test {{pascalCase FEATURE_NAME}}");
59
+
60
+ await _repository.Received(1).AddAsync(Arg.Any<{{pascalCase FEATURE_NAME}}>(), Arg.Any<CancellationToken>());
61
+ await _unitOfWork.Received(1).CommitAsync(Arg.Any<CancellationToken>());
47
62
  }
48
63
 
49
64
  [Fact]
50
- public async Task GetByIdAsync_WithInvalidId_ReturnsNull()
65
+ public async Task Create_HandleAsync_WhenCommitFails_ReturnsFailureResult()
51
66
  {
52
67
  // Arrange
53
- var id = 999;
54
- _repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
55
- .Returns((Domain.Entities.{{pascalCase FEATURE_NAME}}?)null);
68
+ var command = new Create{{pascalCase FEATURE_NAME}}.Command("Test {{pascalCase FEATURE_NAME}}");
69
+ var handler = new Create{{pascalCase FEATURE_NAME}}.Handler(_repository, _unitOfWork, _logger);
70
+
71
+ _repository.AddAsync(Arg.Any<{{pascalCase FEATURE_NAME}}>(), Arg.Any<CancellationToken>())
72
+ .Returns(callInfo => callInfo.Arg<{{pascalCase FEATURE_NAME}}>());
73
+
74
+ _unitOfWork.CommitAsync(Arg.Any<CancellationToken>())
75
+ .ThrowsAsync(new InvalidOperationException("Database error"));
56
76
 
57
77
  // Act
58
- var result = await _sut.GetByIdAsync(id);
78
+ var act = async () => await handler.HandleAsync(command, CancellationToken.None);
59
79
 
60
80
  // Assert
61
- result.Should().BeNull();
81
+ await act.Should().ThrowAsync<InvalidOperationException>()
82
+ .WithMessage("Database error");
62
83
  }
63
84
 
64
85
  #endregion
65
86
 
66
- #region GetAllAsync Tests
87
+ #region GetById Handler Tests
67
88
 
68
89
  [Fact]
69
- public async Task GetAllAsync_ReturnsAllItems()
90
+ public async Task GetById_HandleAsync_WithValidId_ReturnsSuccessResult()
70
91
  {
71
92
  // Arrange
72
- var entities = new List<Domain.Entities.{{pascalCase FEATURE_NAME}}>
73
- {
74
- CreateTestEntity(1, "First"),
75
- CreateTestEntity(2, "Second"),
76
- CreateTestEntity(3, "Third")
77
- };
78
- _repository.GetAllAsync(Arg.Any<CancellationToken>())
79
- .Returns(entities);
93
+ var query = new GetById{{pascalCase FEATURE_NAME}}.Query(1);
94
+ var entity = CreateTestEntity(1, "Test {{pascalCase FEATURE_NAME}}");
95
+ var handler = new GetById{{pascalCase FEATURE_NAME}}.Handler(_repository);
96
+
97
+ _repository.GetByIdAsync(1, Arg.Any<CancellationToken>())
98
+ .Returns(entity);
80
99
 
81
100
  // Act
82
- var result = await _sut.GetAllAsync();
101
+ var result = await handler.HandleAsync(query, CancellationToken.None);
83
102
 
84
103
  // Assert
85
- result.Should().HaveCount(3);
86
- result[0].Name.Should().Be("First");
104
+ result.IsSuccess.Should().BeTrue();
105
+ result.Value.Should().NotBeNull();
106
+ result.Value!.Name.Should().Be("Test {{pascalCase FEATURE_NAME}}");
87
107
  }
88
108
 
89
109
  [Fact]
90
- public async Task GetAllAsync_WithNoItems_ReturnsEmptyList()
110
+ public async Task GetById_HandleAsync_WithInvalidId_ReturnsFailureResult()
91
111
  {
92
112
  // Arrange
93
- _repository.GetAllAsync(Arg.Any<CancellationToken>())
94
- .Returns(new List<Domain.Entities.{{pascalCase FEATURE_NAME}}>());
113
+ var query = new GetById{{pascalCase FEATURE_NAME}}.Query(999);
114
+ var handler = new GetById{{pascalCase FEATURE_NAME}}.Handler(_repository);
115
+
116
+ _repository.GetByIdAsync(999, Arg.Any<CancellationToken>())
117
+ .Returns(({{pascalCase FEATURE_NAME}}?)null);
95
118
 
96
119
  // Act
97
- var result = await _sut.GetAllAsync();
120
+ var result = await handler.HandleAsync(query, CancellationToken.None);
98
121
 
99
122
  // Assert
100
- result.Should().BeEmpty();
123
+ result.IsSuccess.Should().BeFalse();
101
124
  }
102
125
 
103
126
  #endregion
104
127
 
105
- #region CreateAsync Tests
128
+ #region Update Handler Tests
106
129
 
107
130
  [Fact]
108
- public async Task CreateAsync_WithValidRequest_CreatesAndReturnsDto()
131
+ public async Task Update_HandleAsync_WithValidCommand_ReturnsSuccessResult()
109
132
  {
110
133
  // Arrange
111
- var request = new Create{{pascalCase FEATURE_NAME}}Request("New {{pascalCase FEATURE_NAME}}");
134
+ var command = new Update{{pascalCase FEATURE_NAME}}.Command(1, "Updated Name");
135
+ var entity = CreateTestEntity(1, "Original Name");
136
+ var handler = new Update{{pascalCase FEATURE_NAME}}.Handler(_repository, _unitOfWork);
137
+
138
+ _repository.GetByIdAsync(1, Arg.Any<CancellationToken>())
139
+ .Returns(entity);
112
140
 
113
- _repository
114
- .When(x => x.AddAsync(Arg.Any<Domain.Entities.{{pascalCase FEATURE_NAME}}>(), Arg.Any<CancellationToken>()))
115
- .Do(x =>
116
- {
117
- // Simulate ID assignment
118
- var entity = x.Arg<Domain.Entities.{{pascalCase FEATURE_NAME}}>();
119
- // entity.Id would be set by EF Core
120
- });
141
+ _unitOfWork.CommitAsync(Arg.Any<CancellationToken>())
142
+ .Returns(1);
121
143
 
122
144
  // Act
123
- var result = await _sut.CreateAsync(request);
145
+ var result = await handler.HandleAsync(command, CancellationToken.None);
124
146
 
125
147
  // Assert
126
- result.Should().NotBeNull();
127
- result.Name.Should().Be("New {{pascalCase FEATURE_NAME}}");
148
+ result.IsSuccess.Should().BeTrue();
128
149
 
129
- await _repository.Received(1).AddAsync(Arg.Any<Domain.Entities.{{pascalCase FEATURE_NAME}}>(), Arg.Any<CancellationToken>());
130
- await _repository.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
150
+ await _repository.Received(1).UpdateAsync(Arg.Any<{{pascalCase FEATURE_NAME}}>(), Arg.Any<CancellationToken>());
151
+ await _unitOfWork.Received(1).CommitAsync(Arg.Any<CancellationToken>());
131
152
  }
132
153
 
133
- [Theory]
134
- [InlineData("")]
135
- [InlineData(" ")]
136
- [InlineData(null)]
137
- public async Task CreateAsync_WithInvalidName_ThrowsValidationException(string? name)
154
+ [Fact]
155
+ public async Task Update_HandleAsync_WithNonExistentId_ReturnsFailureResult()
138
156
  {
139
157
  // Arrange
140
- var request = new Create{{pascalCase FEATURE_NAME}}Request(name!);
158
+ var command = new Update{{pascalCase FEATURE_NAME}}.Command(999, "Updated");
159
+ var handler = new Update{{pascalCase FEATURE_NAME}}.Handler(_repository, _unitOfWork);
160
+
161
+ _repository.GetByIdAsync(999, Arg.Any<CancellationToken>())
162
+ .Returns(({{pascalCase FEATURE_NAME}}?)null);
141
163
 
142
164
  // Act
143
- var act = async () => await _sut.CreateAsync(request);
165
+ var result = await handler.HandleAsync(command, CancellationToken.None);
144
166
 
145
167
  // Assert
146
- await act.Should().ThrowAsync<ValidationException>();
168
+ result.IsSuccess.Should().BeFalse();
169
+ await _unitOfWork.DidNotReceive().CommitAsync(Arg.Any<CancellationToken>());
147
170
  }
148
171
 
149
172
  #endregion
150
173
 
151
- #region UpdateAsync Tests
174
+ #region Delete Handler Tests
152
175
 
153
176
  [Fact]
154
- public async Task UpdateAsync_WithValidRequest_UpdatesEntity()
177
+ public async Task Delete_HandleAsync_WithValidId_ReturnsSuccessResult()
155
178
  {
156
179
  // Arrange
157
- var id = 1;
158
- var entity = CreateTestEntity(id, "Original");
159
- var request = new Update{{pascalCase FEATURE_NAME}}Request("Updated");
180
+ var command = new Delete{{pascalCase FEATURE_NAME}}.Command(1);
181
+ var entity = CreateTestEntity(1, "To Delete");
182
+ var handler = new Delete{{pascalCase FEATURE_NAME}}.Handler(_repository, _unitOfWork);
160
183
 
161
- _repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
184
+ _repository.GetByIdAsync(1, Arg.Any<CancellationToken>())
162
185
  .Returns(entity);
163
186
 
187
+ _unitOfWork.CommitAsync(Arg.Any<CancellationToken>())
188
+ .Returns(1);
189
+
164
190
  // Act
165
- await _sut.UpdateAsync(id, request);
191
+ var result = await handler.HandleAsync(command, CancellationToken.None);
166
192
 
167
193
  // Assert
168
- _repository.Received(1).Update(Arg.Any<Domain.Entities.{{pascalCase FEATURE_NAME}}>());
169
- await _repository.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
194
+ result.IsSuccess.Should().BeTrue();
195
+
196
+ await _repository.Received(1).DeleteAsync(entity, Arg.Any<CancellationToken>());
197
+ await _unitOfWork.Received(1).CommitAsync(Arg.Any<CancellationToken>());
170
198
  }
171
199
 
172
200
  [Fact]
173
- public async Task UpdateAsync_WithNonExistentId_ThrowsNotFoundException()
201
+ public async Task Delete_HandleAsync_WithNonExistentId_ReturnsFailureResult()
174
202
  {
175
203
  // Arrange
176
- var id = 999;
177
- var request = new Update{{pascalCase FEATURE_NAME}}Request("Updated");
204
+ var command = new Delete{{pascalCase FEATURE_NAME}}.Command(999);
205
+ var handler = new Delete{{pascalCase FEATURE_NAME}}.Handler(_repository, _unitOfWork);
178
206
 
179
- _repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
180
- .Returns((Domain.Entities.{{pascalCase FEATURE_NAME}}?)null);
207
+ _repository.GetByIdAsync(999, Arg.Any<CancellationToken>())
208
+ .Returns(({{pascalCase FEATURE_NAME}}?)null);
181
209
 
182
210
  // Act
183
- var act = async () => await _sut.UpdateAsync(id, request);
211
+ var result = await handler.HandleAsync(command, CancellationToken.None);
184
212
 
185
213
  // Assert
186
- await act.Should().ThrowAsync<{{pascalCase FEATURE_NAME}}NotFoundException>();
214
+ result.IsSuccess.Should().BeFalse();
215
+ await _unitOfWork.DidNotReceive().CommitAsync(Arg.Any<CancellationToken>());
216
+ }
217
+
218
+ #endregion
219
+
220
+ #region Helper Methods
221
+
222
+ private static {{pascalCase FEATURE_NAME}} CreateTestEntity(int id, string name)
223
+ {
224
+ return {{pascalCase FEATURE_NAME}}.Create(name);
225
+ // Note: In real tests, use reflection or a test factory to set the Id
187
226
  }
188
227
 
189
228
  #endregion
229
+ }
190
230
 
191
- #region DeleteAsync Tests
231
+ // ============================================================
232
+ // Validator Tests
233
+ // ============================================================
234
+
235
+ /// <summary>
236
+ /// Unit tests for {{pascalCase FEATURE_NAME}} validators.
237
+ /// Tests FluentValidation rules using ValidateAsync.
238
+ /// </summary>
239
+ public class {{pascalCase FEATURE_NAME}}ValidatorTests
240
+ {
241
+ #region Create Validator Tests
192
242
 
193
243
  [Fact]
194
- public async Task DeleteAsync_WithValidId_DeletesEntity()
244
+ public async Task CreateValidator_WithValidCommand_PassesValidation()
195
245
  {
196
246
  // Arrange
197
- var id = 1;
198
- var entity = CreateTestEntity(id, "To Delete");
247
+ var validator = new Create{{pascalCase FEATURE_NAME}}.Validator();
248
+ var command = new Create{{pascalCase FEATURE_NAME}}.Command("Valid Name");
199
249
 
200
- _repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
201
- .Returns(entity);
250
+ // Act
251
+ ValidationResult result = await validator.ValidateAsync(command);
252
+
253
+ // Assert
254
+ result.IsValid.Should().BeTrue();
255
+ result.Errors.Should().BeEmpty();
256
+ }
257
+
258
+ [Theory]
259
+ [InlineData("")]
260
+ [InlineData(" ")]
261
+ [InlineData(null)]
262
+ public async Task CreateValidator_WithInvalidName_FailsValidation(string? name)
263
+ {
264
+ // Arrange
265
+ var validator = new Create{{pascalCase FEATURE_NAME}}.Validator();
266
+ var command = new Create{{pascalCase FEATURE_NAME}}.Command(name!);
202
267
 
203
268
  // Act
204
- await _sut.DeleteAsync(id);
269
+ ValidationResult result = await validator.ValidateAsync(command);
205
270
 
206
271
  // Assert
207
- _repository.Received(1).Remove(entity);
208
- await _repository.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
272
+ result.IsValid.Should().BeFalse();
273
+ result.Errors.Should().Contain(e => e.PropertyName == nameof(Create{{pascalCase FEATURE_NAME}}.Command.Name));
209
274
  }
210
275
 
211
276
  [Fact]
212
- public async Task DeleteAsync_WithNonExistentId_ThrowsNotFoundException()
277
+ public async Task CreateValidator_WithNameExceedingMaxLength_FailsValidation()
213
278
  {
214
279
  // Arrange
215
- var id = 999;
216
-
217
- _repository.GetByIdAsync(id, Arg.Any<CancellationToken>())
218
- .Returns((Domain.Entities.{{pascalCase FEATURE_NAME}}?)null);
280
+ var validator = new Create{{pascalCase FEATURE_NAME}}.Validator();
281
+ var command = new Create{{pascalCase FEATURE_NAME}}.Command(new string('A', 201));
219
282
 
220
283
  // Act
221
- var act = async () => await _sut.DeleteAsync(id);
284
+ ValidationResult result = await validator.ValidateAsync(command);
222
285
 
223
286
  // Assert
224
- await act.Should().ThrowAsync<{{pascalCase FEATURE_NAME}}NotFoundException>();
287
+ result.IsValid.Should().BeFalse();
288
+ result.Errors.Should().Contain(e => e.PropertyName == nameof(Create{{pascalCase FEATURE_NAME}}.Command.Name));
225
289
  }
226
290
 
227
291
  #endregion
228
292
 
229
- #region Helper Methods
293
+ #region Update Validator Tests
294
+
295
+ [Fact]
296
+ public async Task UpdateValidator_WithValidCommand_PassesValidation()
297
+ {
298
+ // Arrange
299
+ var validator = new Update{{pascalCase FEATURE_NAME}}.Validator();
300
+ var command = new Update{{pascalCase FEATURE_NAME}}.Command(1, "Updated Name");
301
+
302
+ // Act
303
+ ValidationResult result = await validator.ValidateAsync(command);
304
+
305
+ // Assert
306
+ result.IsValid.Should().BeTrue();
307
+ }
230
308
 
231
- private static Domain.Entities.{{pascalCase FEATURE_NAME}} CreateTestEntity(int id, string name)
309
+ [Fact]
310
+ public async Task UpdateValidator_WithInvalidId_FailsValidation()
232
311
  {
233
- return Domain.Entities.{{pascalCase FEATURE_NAME}}.Create(name);
234
- // Note: In real tests, you might need reflection to set the Id
235
- // or use a factory method that accepts an id for testing
312
+ // Arrange
313
+ var validator = new Update{{pascalCase FEATURE_NAME}}.Validator();
314
+ var command = new Update{{pascalCase FEATURE_NAME}}.Command(0, "Updated Name");
315
+
316
+ // Act
317
+ ValidationResult result = await validator.ValidateAsync(command);
318
+
319
+ // Assert
320
+ result.IsValid.Should().BeFalse();
321
+ result.Errors.Should().Contain(e => e.PropertyName == nameof(Update{{pascalCase FEATURE_NAME}}.Command.Id));
236
322
  }
237
323
 
238
324
  #endregion
@@ -0,0 +1,89 @@
1
+ # {{projectName}} — Architecture Context
2
+
3
+ > Auto-generated by `morph-spec context refresh`
4
+ > Last updated: {{timestamp}}
5
+
6
+ {{#if hasDotnet}}
7
+ ## .NET Entities ({{entityCount}})
8
+
9
+ | Entity | Type | Path |
10
+ |--------|------|------|
11
+ {{#each entities}}
12
+ | `{{name}}` | {{type}} | `{{path}}` |
13
+ {{/each}}
14
+
15
+ ## API Endpoints ({{endpointCount}})
16
+
17
+ | Endpoint | Method | Route | Path |
18
+ |----------|--------|-------|------|
19
+ {{#each endpoints}}
20
+ | `{{name}}` | {{method}} | `{{route}}` | `{{path}}` |
21
+ {{/each}}
22
+ {{/if}}
23
+
24
+ {{#if hasNextjs}}
25
+ ## Next.js Pages ({{pageCount}})
26
+
27
+ | Route | Path |
28
+ |-------|------|
29
+ {{#each pages}}
30
+ | `{{route}}` | `{{path}}` |
31
+ {{/each}}
32
+
33
+ {{#if layouts.length}}
34
+ ### Layouts
35
+
36
+ {{#each layouts}}
37
+ - `{{path}}`
38
+ {{/each}}
39
+ {{/if}}
40
+
41
+ {{#if componentCount}}
42
+ ## Components ({{componentCount}})
43
+
44
+ | Component | Path |
45
+ |-----------|------|
46
+ {{#each components}}
47
+ | `{{name}}` | `{{path}}` |
48
+ {{/each}}
49
+ {{/if}}
50
+ {{/if}}
51
+
52
+ {{#if hasIntegrations}}
53
+ ## Integrations
54
+
55
+ | Service | Type | Configured |
56
+ |---------|------|------------|
57
+ {{#each integrations}}
58
+ | {{name}} | {{type}} | {{#if configured}}Yes{{else}}No{{/if}} |
59
+ {{/each}}
60
+ {{/if}}
61
+
62
+ {{#if hasTables}}
63
+ ## Database Tables ({{tableCount}})
64
+
65
+ | Table | Source Migration |
66
+ |-------|----------------|
67
+ {{#each tables}}
68
+ | `{{table}}` | `{{migration}}` |
69
+ {{/each}}
70
+ {{/if}}
71
+
72
+ {{#if hasDotnetPackages}}
73
+ ## .NET Packages
74
+
75
+ {{#each dotnetPackages}}
76
+ - `{{name}}` {{version}}
77
+ {{/each}}
78
+ {{/if}}
79
+
80
+ {{#if hasNpmPackages}}
81
+ ## NPM Packages
82
+
83
+ {{#each npmPackages}}
84
+ - `{{name}}` {{version}}
85
+ {{/each}}
86
+ {{/if}}
87
+
88
+ ---
89
+ *Generated by morph-spec v{{morphVersion}}*
@@ -230,8 +230,8 @@ No validation runs yet. Validators run automatically via checkpoints.
230
230
 
231
231
  ### Continue Implementation
232
232
  ```bash
233
- # Start next task
234
- morph-spec task next {{FEATURE_NAME}}
233
+ # Start a task (dispatch gate + state tracking)
234
+ morph-spec task start {{FEATURE_NAME}} <task-id>
235
235
 
236
236
  # Complete current task (runs validators)
237
237
  morph-spec task done {{FEATURE_NAME}} {{CURRENT_TASK_ID}}