@polymorphism-tech/morph-spec 4.11.1 → 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 (317) 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 +226 -1454
  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/agent-complete.js +122 -0
  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/dispatch-helpers.js +17 -0
  42. package/framework/hooks/shared/phase-utils.js +4 -8
  43. package/framework/hooks/shared/state-reader.js +34 -3
  44. package/framework/phases.json +7 -45
  45. package/framework/rules/ai-agents-standards.md +15 -0
  46. package/framework/rules/asaas-standards.md +13 -0
  47. package/framework/rules/clerk-standards.md +13 -0
  48. package/framework/rules/csharp-standards.md +8 -4
  49. package/framework/rules/data-standards.md +18 -0
  50. package/framework/rules/dotnet-templates.md +87 -0
  51. package/framework/rules/frontend-standards.md +13 -13
  52. package/framework/rules/hangfire-standards.md +13 -0
  53. package/framework/rules/infrastructure-standards.md +27 -16
  54. package/framework/rules/morph-workflow.md +73 -13
  55. package/framework/rules/nextjs-standards.md +1 -0
  56. package/framework/rules/nextjs-templates.md +40 -0
  57. package/framework/rules/resend-standards.md +12 -0
  58. package/framework/rules/supabase-standards.md +29 -0
  59. package/framework/skills/README.md +6 -8
  60. package/framework/skills/level-0-meta/mcp-registry.json +21 -99
  61. package/framework/{standards/ai-agents/team-orchestration.md → skills/level-0-meta/morph-agent-teams/SKILL.md} +472 -479
  62. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +28 -1
  63. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  64. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +1 -5
  65. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +1 -1
  66. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +23 -41
  67. package/framework/skills/level-0-meta/morph-init/SKILL.md +18 -14
  68. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +225 -0
  69. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +20 -0
  70. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +4 -8
  71. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +15 -59
  72. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +7 -19
  73. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +8 -8
  74. package/framework/skills/level-1-workflows/README.md +1 -1
  75. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +49 -50
  76. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +58 -124
  77. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +5 -50
  78. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +1 -1
  79. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +127 -159
  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 +0 -2
  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 +275 -61
  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 -2338
  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 +342 -211
  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/mcp/mcp-setup.js +28 -6
  159. package/src/commands/phase/phase-reset.js +1 -1
  160. package/src/commands/project/doctor.js +10 -32
  161. package/src/commands/project/init.js +32 -1
  162. package/src/commands/project/status.js +6 -12
  163. package/src/commands/scaffold/scaffold.js +131 -0
  164. package/src/commands/scope/escalate.js +1 -1
  165. package/src/commands/state/advance-phase.js +15 -29
  166. package/src/commands/state/state.js +20 -5
  167. package/src/commands/tasks/task.js +0 -30
  168. package/src/commands/validation/validate-feature.js +3 -10
  169. package/src/core/paths/output-schema.js +12 -13
  170. package/src/core/state/phase-state-machine.js +211 -217
  171. package/src/core/state/state-manager.js +9 -9
  172. package/src/lib/agents/agents-manager.js +2 -2
  173. package/src/lib/agents/output-collector.js +1 -2
  174. package/src/lib/detectors/design-system-detector.js +4 -4
  175. package/src/lib/detectors/index.js +1 -11
  176. package/src/lib/generators/architecture-scanner.js +640 -0
  177. package/src/lib/generators/context-generator.js +3 -3
  178. package/src/lib/generators/design-system-generator.js +508 -298
  179. package/src/lib/generators/index.js +12 -10
  180. package/src/lib/generators/metadata-extractor.js +386 -387
  181. package/src/lib/generators/recap-generator.js +4 -4
  182. package/src/lib/generators/scaffold-generator.js +317 -0
  183. package/src/lib/generators/settings-generator.js +0 -1
  184. package/src/lib/phase-chain/phase-validator.js +64 -42
  185. package/src/lib/stack/stack-profile.js +2 -2
  186. package/src/lib/standards/digest-builder.js +72 -285
  187. package/src/lib/tasks/task-parser.js +11 -5
  188. package/src/lib/validators/architecture/architecture-validator.js +259 -387
  189. package/src/lib/validators/content/content-validator.js +287 -287
  190. package/src/lib/validators/css/css-validator.js +340 -352
  191. package/src/lib/validators/design-system/design-system-validator.js +11 -5
  192. package/src/lib/validators/packages/package-validator.js +347 -360
  193. package/src/lib/validators/shared/issue-counter.js +14 -18
  194. package/src/lib/validators/validation-runner.js +3 -19
  195. package/src/scripts/setup-infra.js +1 -1
  196. package/src/utils/agents-installer.js +24 -3
  197. package/src/utils/file-copier.js +1 -0
  198. package/src/utils/hooks-installer.js +21 -19
  199. package/src/utils/skills-installer.js +178 -178
  200. package/framework/agents/architecture/prompt-engineer.md +0 -195
  201. package/framework/agents/backend/api-designer.md +0 -106
  202. package/framework/agents/frontend/blazor-builder.md +0 -229
  203. package/framework/agents/frontend/nextjs-ui-specialist.md +0 -218
  204. package/framework/agents/infrastructure/azure-architect.md +0 -150
  205. package/framework/agents/infrastructure/azure-deploy-specialist.md +0 -707
  206. package/framework/agents/infrastructure/bicep-architect.md +0 -132
  207. package/framework/agents/infrastructure/container-specialist.md +0 -137
  208. package/framework/agents/infrastructure/devops-engineer.md +0 -126
  209. package/framework/agents/integrations/azure-identity.md +0 -148
  210. package/framework/agents/quality/code-analyzer.md +0 -239
  211. package/framework/agents/quality/testing-specialist.md +0 -130
  212. package/framework/hooks/claude-code/notification/approval-reminder.js +0 -57
  213. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +0 -109
  214. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +0 -78
  215. package/framework/index/troubleshooting-index.json +0 -184
  216. package/framework/rules/testing-standards.md +0 -14
  217. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +0 -230
  218. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +0 -70
  219. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +0 -256
  220. package/framework/skills/level-1-workflows/morph-phase-clarify/references/clarifications-example.md +0 -117
  221. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +0 -269
  222. package/framework/standards/ai-agents/blazor-ui.md +0 -364
  223. package/framework/standards/architecture/ddd/aggregates.md +0 -120
  224. package/framework/standards/architecture/ddd/bounded-contexts.md +0 -105
  225. package/framework/standards/architecture/ddd/complexity-levels.md +0 -108
  226. package/framework/standards/architecture/ddd/entities.md +0 -99
  227. package/framework/standards/architecture/ddd/ubiquitous-language.md +0 -58
  228. package/framework/standards/architecture/ddd/value-objects.md +0 -124
  229. package/framework/standards/backend/api/minimal-api.md +0 -494
  230. package/framework/standards/backend/api/rest.md +0 -492
  231. package/framework/standards/backend/authentication/passkeys.md +0 -428
  232. package/framework/standards/backend/database/ef-core.md +0 -199
  233. package/framework/standards/backend/database/migrations.md +0 -393
  234. package/framework/standards/backend/database/repository-patterns.md +0 -528
  235. package/framework/standards/backend/dotnet/async.md +0 -366
  236. package/framework/standards/backend/dotnet/core.md +0 -117
  237. package/framework/standards/backend/dotnet/di.md +0 -439
  238. package/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
  239. package/framework/standards/context/analytics.md +0 -96
  240. package/framework/standards/context/bundles.md +0 -110
  241. package/framework/standards/context/priming.md +0 -78
  242. package/framework/standards/core/architecture.md +0 -185
  243. package/framework/standards/core/coding.md +0 -214
  244. package/framework/standards/core/git-branching-strategy.md +0 -403
  245. package/framework/standards/core/git.md +0 -185
  246. package/framework/standards/core/testing.md +0 -295
  247. package/framework/standards/data/nosql/blob-storage.md +0 -102
  248. package/framework/standards/data/vector-search/azure-ai-search.md +0 -121
  249. package/framework/standards/frontend/blazor/design-checklist.md +0 -222
  250. package/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
  251. package/framework/standards/frontend/blazor/fluent-ui.md +0 -137
  252. package/framework/standards/frontend/blazor/html-conversion.md +0 -184
  253. package/framework/standards/frontend/blazor/lifecycle.md +0 -195
  254. package/framework/standards/frontend/blazor/pitfalls.md +0 -198
  255. package/framework/standards/frontend/blazor/state.md +0 -191
  256. package/framework/standards/frontend/design-system/aesthetic-direction.md +0 -213
  257. package/framework/standards/infrastructure/azure/azure.md +0 -624
  258. package/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
  259. package/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
  260. package/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
  261. package/framework/standards/infrastructure/azure/services/functions.md +0 -486
  262. package/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
  263. package/framework/standards/infrastructure/azure/services/storage.md +0 -407
  264. package/framework/standards/infrastructure/vercel/vercel-database.md +0 -106
  265. package/framework/standards/integration/api/graphql.md +0 -91
  266. package/framework/standards/integration/api/grpc.md +0 -114
  267. package/framework/standards/integration/api/rest-design.md +0 -95
  268. package/framework/standards/integration/event-driven/cqrs.md +0 -101
  269. package/framework/standards/integration/event-driven/event-sourcing.md +0 -124
  270. package/framework/standards/integration/event-driven/service-bus.md +0 -95
  271. package/framework/standards/observability/logging.md +0 -131
  272. package/framework/standards/observability/metrics.md +0 -121
  273. package/framework/standards/observability/monitoring.md +0 -114
  274. package/framework/standards/observability/tracing.md +0 -132
  275. package/framework/standards/workflows/parallel-execution.md +0 -112
  276. package/framework/standards/workflows/thread-management.md +0 -113
  277. package/framework/templates/code/dotnet/backend/service.cs +0 -139
  278. package/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
  279. package/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
  280. package/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
  281. package/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
  282. package/framework/templates/code/dotnet/contracts/contracts-level1.cs +0 -69
  283. package/framework/templates/code/dotnet/contracts/contracts-level2.cs +0 -86
  284. package/framework/templates/code/dotnet/contracts/contracts-level3.cs +0 -41
  285. package/framework/templates/code/dotnet/frontend/component.razor +0 -239
  286. package/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
  287. package/framework/templates/infrastructure/azure/README.md +0 -286
  288. package/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
  289. package/framework/templates/infrastructure/azure/app-service.bicep +0 -164
  290. package/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
  291. package/framework/templates/infrastructure/azure/container-app.bicep +0 -156
  292. package/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
  293. package/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
  294. package/framework/templates/infrastructure/azure/deploy.sh +0 -208
  295. package/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
  296. package/framework/templates/infrastructure/azure/main.bicep +0 -189
  297. package/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
  298. package/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
  299. package/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
  300. package/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
  301. package/framework/templates/infrastructure/azure/storage.bicep +0 -106
  302. package/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
  303. package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
  304. package/framework/templates/integrations/azure-identity-config.cs +0 -288
  305. package/framework/templates/project-structure/dotnet-ddd.md +0 -70
  306. package/framework/templates/saas/subscription.cs +0 -347
  307. package/framework/templates/saas/tenant.cs +0 -338
  308. package/framework/templates/ui/FluentDesignTheme.cs +0 -149
  309. package/framework/templates/ui/MudTheme.cs +0 -281
  310. package/framework/workflows/configs/nodejs-cli.json +0 -40
  311. package/src/commands/standards/context-budget.js +0 -89
  312. package/src/lib/standards/index.js +0 -7
  313. package/src/lib/standards/standards-context-injector.js +0 -302
  314. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +0 -316
  315. package/src/lib/validators/blazor/blazor-state-validator.js +0 -279
  316. package/src/lib/validators/blazor/blazor-validator.js +0 -369
  317. 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}}