@polymorphism-tech/morph-spec 4.12.0 → 4.14.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 (316) hide show
  1. package/CLAUDE.md +7 -7
  2. package/README.md +28 -34
  3. package/bin/morph-spec.js +41 -24
  4. package/bin/task-manager.js +962 -1341
  5. package/bin/validate.js +1 -1
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +14 -16
  8. package/docs/QUICKSTART.md +15 -15
  9. package/framework/CLAUDE.md +5 -2
  10. package/framework/agents/README.md +3 -4
  11. package/framework/agents/ai-agents/ai-system-architect.md +6 -10
  12. package/framework/agents/architecture/seo-growth-hacker.md +166 -326
  13. package/framework/agents/architecture/standards-architect.md +21 -31
  14. package/framework/agents/backend/dotnet-senior.md +160 -86
  15. package/framework/agents/backend/ef-modeler.md +2 -6
  16. package/framework/agents/backend/hangfire-orchestrator.md +8 -9
  17. package/framework/agents/backend/ms-agent-expert.md +1 -1
  18. package/framework/agents/frontend/nextjs-expert.md +198 -133
  19. package/framework/agents/frontend/ui-ux-designer.md +226 -267
  20. package/framework/agents/infrastructure/infra-architect.md +191 -27
  21. package/framework/agents/integrations/clerk-auth.md +19 -26
  22. package/framework/agents/integrations/hangfire-integration.md +1 -4
  23. package/framework/agents.json +1084 -2278
  24. package/framework/commands/morph-apply.md +4 -6
  25. package/framework/commands/morph-preflight.md +192 -269
  26. package/framework/commands/morph-proposal.md +210 -238
  27. package/framework/commands/morph-status.md +72 -92
  28. package/framework/commands/morph-troubleshoot.md +6 -13
  29. package/framework/hooks/README.md +1 -6
  30. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +4 -6
  31. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +12 -7
  32. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +2 -2
  33. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +2 -2
  34. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +5 -10
  35. package/framework/hooks/claude-code/session-start/inject-morph-context.js +63 -30
  36. package/framework/hooks/claude-code/session-start/resume-context.js +77 -0
  37. package/framework/hooks/claude-code/statusline.py +4 -5
  38. package/framework/hooks/claude-code/stop/validate-completion.js +4 -4
  39. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +8 -8
  40. package/framework/hooks/shared/compact-restore.js +28 -5
  41. package/framework/hooks/shared/phase-utils.js +4 -8
  42. package/framework/hooks/shared/state-reader.js +34 -3
  43. package/framework/phases.json +162 -200
  44. package/framework/rules/ai-agents-standards.md +15 -0
  45. package/framework/rules/asaas-standards.md +13 -0
  46. package/framework/rules/clerk-standards.md +13 -0
  47. package/framework/rules/csharp-standards.md +8 -4
  48. package/framework/rules/data-standards.md +18 -0
  49. package/framework/rules/dotnet-templates.md +87 -0
  50. package/framework/rules/frontend-standards.md +13 -13
  51. package/framework/rules/hangfire-standards.md +13 -0
  52. package/framework/rules/infrastructure-standards.md +27 -16
  53. package/framework/rules/morph-workflow.md +73 -13
  54. package/framework/rules/nextjs-standards.md +1 -0
  55. package/framework/rules/nextjs-templates.md +40 -0
  56. package/framework/rules/resend-standards.md +12 -0
  57. package/framework/rules/supabase-standards.md +29 -0
  58. package/framework/skills/README.md +6 -8
  59. package/framework/skills/level-0-meta/mcp-registry.json +21 -99
  60. package/framework/{standards/ai-agents/team-orchestration.md → skills/level-0-meta/morph-agent-teams/SKILL.md} +472 -479
  61. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +28 -1
  62. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  63. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +1 -5
  64. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +1 -1
  65. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +23 -41
  66. package/framework/skills/level-0-meta/morph-init/SKILL.md +18 -14
  67. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +225 -0
  68. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +20 -0
  69. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +4 -8
  70. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +15 -59
  71. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +7 -19
  72. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +8 -8
  73. package/framework/skills/level-1-workflows/README.md +1 -1
  74. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +49 -50
  75. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +57 -123
  76. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +5 -50
  77. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +1 -1
  78. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +127 -159
  79. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +1 -1
  80. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +4 -3
  81. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +2 -2
  82. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +1 -1
  83. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +17 -13
  84. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +22 -40
  85. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +143 -172
  86. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +5 -5
  87. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +57 -77
  88. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  89. package/framework/squad-templates/backend-only.json +6 -6
  90. package/framework/squad-templates/frontend-only.json +17 -8
  91. package/framework/squad-templates/full-stack.json +29 -24
  92. package/framework/standards/STANDARDS.json +1021 -2369
  93. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +200 -0
  94. package/framework/standards/backend/dotnet/result-pattern.md +213 -0
  95. package/framework/standards/backend/dotnet/scalar-ui.md +172 -0
  96. package/framework/standards/backend/dotnet/scrutor.md +158 -0
  97. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -0
  98. package/framework/standards/frontend/nextjs/motion-patterns.md +1 -1
  99. package/framework/standards/infrastructure/supabase/mcp-setup.md +83 -193
  100. package/framework/standards/integration/mcp/mcp-tools.md +91 -77
  101. package/framework/templates/IDE-SNIPPETS.md +2 -6
  102. package/framework/templates/README.md +709 -814
  103. package/framework/templates/REGISTRY.json +223 -762
  104. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -0
  105. package/framework/templates/code/dotnet/abstractions/error.cs +60 -0
  106. package/framework/templates/code/dotnet/abstractions/handler.cs +12 -0
  107. package/framework/templates/code/dotnet/abstractions/result.cs +74 -0
  108. package/framework/templates/code/dotnet/backend/repository.cs +148 -141
  109. package/framework/templates/code/dotnet/constants/api-tags.cs +6 -0
  110. package/framework/templates/code/dotnet/exceptions/exception-handler.cs +32 -0
  111. package/framework/templates/code/dotnet/extensions/database-extensions.cs +20 -0
  112. package/framework/templates/code/dotnet/extensions/handler-registration.cs +38 -0
  113. package/framework/templates/code/dotnet/extensions/health-checks.cs +24 -0
  114. package/framework/templates/code/dotnet/extensions/map-endpoints.cs +35 -0
  115. package/framework/templates/code/dotnet/extensions/result-extensions.cs +29 -0
  116. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -0
  117. package/framework/templates/code/dotnet/feature/entity.cs.hbs +8 -0
  118. package/framework/templates/code/dotnet/feature/errors.cs.hbs +8 -0
  119. package/framework/templates/code/dotnet/feature/handler.cs.hbs +24 -0
  120. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -0
  121. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -0
  122. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -0
  123. package/framework/templates/code/dotnet/jobs/job.cs +21 -17
  124. package/framework/templates/code/dotnet/pipelines/logging-decorator.cs +31 -0
  125. package/framework/templates/code/dotnet/pipelines/validation-decorator.cs +97 -0
  126. package/framework/templates/code/dotnet/test.cs +192 -106
  127. package/framework/templates/context/ARCHITECTURE.md +89 -0
  128. package/framework/templates/context/CONTEXT-FEATURE.md +2 -2
  129. package/framework/templates/context/CONTEXT.md +180 -181
  130. package/framework/templates/context/design-system.md +1 -5
  131. package/framework/templates/docs/onboarding.md +2 -3
  132. package/framework/templates/docs/proposal.md +171 -182
  133. package/framework/templates/docs/spec.md +1 -3
  134. package/framework/templates/docs/ui-components.md +0 -72
  135. package/framework/templates/examples/design-system-examples.md +4 -4
  136. package/framework/templates/feature/decisions.md +0 -66
  137. package/framework/templates/feature/recap.md +1 -2
  138. package/framework/templates/infrastructure/github/README.md +56 -593
  139. package/framework/templates/integrations/clerk-config.cs +0 -41
  140. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +2 -2
  141. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -2
  142. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +83 -126
  143. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +2 -2
  144. package/framework/templates/project/validate.js +2 -4
  145. package/framework/templates/ui/design-system.css +1 -1
  146. package/framework/workflows/README.md +8 -8
  147. package/framework/workflows/configs/fast-track.json +51 -0
  148. package/framework/workflows/configs/full.json +68 -0
  149. package/framework/workflows/configs/standard.json +7 -3
  150. package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +1 -1
  151. package/framework/workflows/docs/enforcement-pipeline.md +18 -19
  152. package/framework/workflows/docs/full-morph.md +69 -76
  153. package/framework/workflows/docs/standard.md +49 -44
  154. package/framework/workflows/docs/ui-refresh.md +1 -1
  155. package/package.json +3 -3
  156. package/src/commands/agents/dispatch-agents.js +55 -116
  157. package/src/commands/context/context-refresh.js +79 -0
  158. package/src/commands/phase/phase-reset.js +1 -1
  159. package/src/commands/project/doctor.js +10 -32
  160. package/src/commands/project/init.js +12 -0
  161. package/src/commands/project/status.js +6 -12
  162. package/src/commands/scaffold/scaffold.js +131 -0
  163. package/src/commands/scope/escalate.js +1 -1
  164. package/src/commands/state/advance-phase.js +15 -29
  165. package/src/commands/state/state.js +20 -5
  166. package/src/commands/tasks/task.js +0 -30
  167. package/src/commands/validation/validate-feature.js +3 -10
  168. package/src/core/paths/output-schema.js +12 -13
  169. package/src/core/state/phase-state-machine.js +211 -217
  170. package/src/core/state/state-manager.js +9 -9
  171. package/src/lib/agents/agents-manager.js +2 -2
  172. package/src/lib/agents/output-collector.js +1 -2
  173. package/src/lib/detectors/design-system-detector.js +4 -4
  174. package/src/lib/detectors/index.js +1 -11
  175. package/src/lib/generators/architecture-scanner.js +640 -0
  176. package/src/lib/generators/context-generator.js +3 -3
  177. package/src/lib/generators/design-system-generator.js +508 -587
  178. package/src/lib/generators/index.js +12 -10
  179. package/src/lib/generators/metadata-extractor.js +386 -387
  180. package/src/lib/generators/recap-generator.js +4 -4
  181. package/src/lib/generators/scaffold-generator.js +317 -0
  182. package/src/lib/generators/settings-generator.js +0 -1
  183. package/src/lib/phase-chain/phase-validator.js +64 -42
  184. package/src/lib/stack/stack-profile.js +2 -2
  185. package/src/lib/standards/digest-builder.js +72 -285
  186. package/src/lib/tasks/task-parser.js +11 -5
  187. package/src/lib/validators/architecture/architecture-validator.js +259 -387
  188. package/src/lib/validators/content/content-validator.js +287 -287
  189. package/src/lib/validators/css/css-validator.js +340 -352
  190. package/src/lib/validators/packages/package-validator.js +347 -360
  191. package/src/lib/validators/shared/issue-counter.js +14 -18
  192. package/src/lib/validators/validation-runner.js +3 -19
  193. package/src/scripts/setup-infra.js +1 -1
  194. package/src/utils/agents-installer.js +61 -10
  195. package/src/utils/file-copier.js +1 -0
  196. package/src/utils/hooks-installer.js +11 -19
  197. package/src/utils/skills-installer.js +178 -178
  198. package/framework/agents/architecture/prompt-engineer.md +0 -195
  199. package/framework/agents/backend/api-designer.md +0 -106
  200. package/framework/agents/frontend/blazor-builder.md +0 -229
  201. package/framework/agents/frontend/nextjs-ui-specialist.md +0 -218
  202. package/framework/agents/infrastructure/azure-architect.md +0 -150
  203. package/framework/agents/infrastructure/azure-deploy-specialist.md +0 -707
  204. package/framework/agents/infrastructure/bicep-architect.md +0 -132
  205. package/framework/agents/infrastructure/container-specialist.md +0 -137
  206. package/framework/agents/infrastructure/devops-engineer.md +0 -126
  207. package/framework/agents/integrations/azure-identity.md +0 -148
  208. package/framework/agents/quality/code-analyzer.md +0 -239
  209. package/framework/agents/quality/testing-specialist.md +0 -130
  210. package/framework/hooks/claude-code/notification/approval-reminder.js +0 -57
  211. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +0 -109
  212. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +0 -78
  213. package/framework/index/troubleshooting-index.json +0 -184
  214. package/framework/rules/testing-standards.md +0 -14
  215. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +0 -230
  216. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +0 -70
  217. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +0 -256
  218. package/framework/skills/level-1-workflows/morph-phase-clarify/references/clarifications-example.md +0 -117
  219. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +0 -269
  220. package/framework/standards/ai-agents/blazor-ui.md +0 -364
  221. package/framework/standards/architecture/ddd/aggregates.md +0 -120
  222. package/framework/standards/architecture/ddd/bounded-contexts.md +0 -105
  223. package/framework/standards/architecture/ddd/complexity-levels.md +0 -108
  224. package/framework/standards/architecture/ddd/entities.md +0 -99
  225. package/framework/standards/architecture/ddd/ubiquitous-language.md +0 -58
  226. package/framework/standards/architecture/ddd/value-objects.md +0 -124
  227. package/framework/standards/backend/api/minimal-api.md +0 -494
  228. package/framework/standards/backend/api/rest.md +0 -492
  229. package/framework/standards/backend/authentication/passkeys.md +0 -428
  230. package/framework/standards/backend/database/ef-core.md +0 -199
  231. package/framework/standards/backend/database/migrations.md +0 -393
  232. package/framework/standards/backend/database/repository-patterns.md +0 -528
  233. package/framework/standards/backend/dotnet/async.md +0 -366
  234. package/framework/standards/backend/dotnet/core.md +0 -117
  235. package/framework/standards/backend/dotnet/di.md +0 -439
  236. package/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
  237. package/framework/standards/context/analytics.md +0 -96
  238. package/framework/standards/context/bundles.md +0 -110
  239. package/framework/standards/context/priming.md +0 -78
  240. package/framework/standards/core/architecture.md +0 -185
  241. package/framework/standards/core/coding.md +0 -214
  242. package/framework/standards/core/git-branching-strategy.md +0 -403
  243. package/framework/standards/core/git.md +0 -185
  244. package/framework/standards/core/testing.md +0 -295
  245. package/framework/standards/data/nosql/blob-storage.md +0 -102
  246. package/framework/standards/data/vector-search/azure-ai-search.md +0 -121
  247. package/framework/standards/frontend/blazor/design-checklist.md +0 -222
  248. package/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
  249. package/framework/standards/frontend/blazor/fluent-ui.md +0 -137
  250. package/framework/standards/frontend/blazor/html-conversion.md +0 -184
  251. package/framework/standards/frontend/blazor/lifecycle.md +0 -195
  252. package/framework/standards/frontend/blazor/pitfalls.md +0 -198
  253. package/framework/standards/frontend/blazor/state.md +0 -191
  254. package/framework/standards/frontend/design-system/aesthetic-direction.md +0 -266
  255. package/framework/standards/frontend/design-system/design-trends.md +0 -171
  256. package/framework/standards/infrastructure/azure/azure.md +0 -624
  257. package/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
  258. package/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
  259. package/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
  260. package/framework/standards/infrastructure/azure/services/functions.md +0 -486
  261. package/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
  262. package/framework/standards/infrastructure/azure/services/storage.md +0 -407
  263. package/framework/standards/infrastructure/vercel/vercel-database.md +0 -106
  264. package/framework/standards/integration/api/graphql.md +0 -91
  265. package/framework/standards/integration/api/grpc.md +0 -114
  266. package/framework/standards/integration/api/rest-design.md +0 -95
  267. package/framework/standards/integration/event-driven/cqrs.md +0 -101
  268. package/framework/standards/integration/event-driven/event-sourcing.md +0 -124
  269. package/framework/standards/integration/event-driven/service-bus.md +0 -95
  270. package/framework/standards/observability/logging.md +0 -131
  271. package/framework/standards/observability/metrics.md +0 -121
  272. package/framework/standards/observability/monitoring.md +0 -114
  273. package/framework/standards/observability/tracing.md +0 -132
  274. package/framework/standards/workflows/parallel-execution.md +0 -112
  275. package/framework/standards/workflows/thread-management.md +0 -113
  276. package/framework/templates/code/dotnet/backend/service.cs +0 -139
  277. package/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
  278. package/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
  279. package/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
  280. package/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
  281. package/framework/templates/code/dotnet/contracts/contracts-level1.cs +0 -69
  282. package/framework/templates/code/dotnet/contracts/contracts-level2.cs +0 -86
  283. package/framework/templates/code/dotnet/contracts/contracts-level3.cs +0 -41
  284. package/framework/templates/code/dotnet/frontend/component.razor +0 -239
  285. package/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
  286. package/framework/templates/infrastructure/azure/README.md +0 -286
  287. package/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
  288. package/framework/templates/infrastructure/azure/app-service.bicep +0 -164
  289. package/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
  290. package/framework/templates/infrastructure/azure/container-app.bicep +0 -156
  291. package/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
  292. package/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
  293. package/framework/templates/infrastructure/azure/deploy.sh +0 -208
  294. package/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
  295. package/framework/templates/infrastructure/azure/main.bicep +0 -189
  296. package/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
  297. package/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
  298. package/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
  299. package/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
  300. package/framework/templates/infrastructure/azure/storage.bicep +0 -106
  301. package/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
  302. package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
  303. package/framework/templates/integrations/azure-identity-config.cs +0 -288
  304. package/framework/templates/project-structure/dotnet-ddd.md +0 -70
  305. package/framework/templates/saas/subscription.cs +0 -347
  306. package/framework/templates/saas/tenant.cs +0 -338
  307. package/framework/templates/ui/FluentDesignTheme.cs +0 -149
  308. package/framework/templates/ui/MudTheme.cs +0 -281
  309. package/framework/workflows/configs/nodejs-cli.json +0 -40
  310. package/src/commands/standards/context-budget.js +0 -89
  311. package/src/lib/standards/index.js +0 -7
  312. package/src/lib/standards/standards-context-injector.js +0 -302
  313. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +0 -316
  314. package/src/lib/validators/blazor/blazor-state-validator.js +0 -279
  315. package/src/lib/validators/blazor/blazor-validator.js +0 -369
  316. 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}}