@polymorphism-tech/morph-spec 4.5.0 → 4.7.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 (292) hide show
  1. package/CLAUDE.md +77 -56
  2. package/README.md +394 -700
  3. package/docs/ARCHITECTURE.md +331 -0
  4. package/docs/CHEATSHEET.md +221 -0
  5. package/docs/COMMAND-FLOWS.md +368 -0
  6. package/docs/QUICKSTART.md +212 -0
  7. package/docs/examples/order-management/contracts.cs +84 -0
  8. package/docs/examples/order-management/proposal.md +24 -0
  9. package/docs/examples/order-management/spec.md +162 -0
  10. package/docs/plans/2026-02-23-ddd-architecture-refactor.md +1153 -0
  11. package/docs/plans/2026-02-23-ddd-nextsteps.md +682 -0
  12. package/docs/plans/2026-02-23-infra-architect-refactor.md +437 -0
  13. package/docs/plans/2026-02-23-nextjs-code-review-design.md +156 -0
  14. package/docs/plans/2026-02-23-nextjs-code-review-impl.md +1254 -0
  15. package/docs/plans/2026-02-23-nextjs-standards-design.md +149 -0
  16. package/docs/plans/2026-02-23-nextjs-standards-impl.md +1846 -0
  17. package/framework/{skills/level-2-domains → agents}/README.md +14 -14
  18. package/framework/{skills/level-2-domains → agents}/ai-agents/ai-system-architect.md +1 -4
  19. package/framework/{skills/level-2-domains → agents}/architecture/po-pm-advisor.md +1 -2
  20. package/framework/{skills/level-2-domains → agents}/architecture/prompt-engineer.md +1 -2
  21. package/framework/{skills/level-2-domains → agents}/architecture/seo-growth-hacker.md +1 -2
  22. package/framework/{skills/level-2-domains → agents}/architecture/standards-architect.md +159 -162
  23. package/framework/agents/backend/api-designer.md +103 -0
  24. package/framework/{skills/level-2-domains → agents}/backend/dotnet-senior.md +1 -2
  25. package/framework/agents/backend/ef-modeler.md +119 -0
  26. package/framework/{skills/level-2-domains → agents}/backend/hangfire-orchestrator.md +1 -4
  27. package/framework/{skills/level-2-domains → agents}/backend/ms-agent-expert.md +1 -4
  28. package/framework/{skills/level-2-domains → agents}/frontend/blazor-builder.md +1 -4
  29. package/framework/agents/frontend/nextjs-expert.md +118 -0
  30. package/framework/{skills/level-2-domains → agents}/frontend/ui-ux-designer.md +1 -2
  31. package/framework/{skills/level-2-domains → agents}/infrastructure/azure-architect.md +147 -148
  32. package/framework/{skills/level-2-domains → agents}/infrastructure/azure-deploy-specialist.md +1 -2
  33. package/framework/{skills/level-2-domains → agents}/infrastructure/bicep-architect.md +1 -4
  34. package/framework/{skills/level-2-domains → agents}/infrastructure/container-specialist.md +1 -4
  35. package/framework/{skills/level-2-domains → agents}/infrastructure/devops-engineer.md +1 -4
  36. package/framework/agents/infrastructure/infra-architect.md +45 -0
  37. package/framework/{skills/level-2-domains → agents}/integrations/asaas-financial.md +1 -4
  38. package/framework/{skills/level-2-domains → agents}/integrations/azure-identity.md +1 -4
  39. package/framework/{skills/level-2-domains → agents}/integrations/clerk-auth.md +1 -4
  40. package/framework/{skills/level-2-domains → agents}/integrations/hangfire-integration.md +1 -2
  41. package/framework/{skills/level-2-domains → agents}/integrations/resend-email.md +1 -4
  42. package/framework/{skills/level-2-domains → agents}/quality/code-analyzer.md +1 -4
  43. package/framework/{skills/level-2-domains → agents}/quality/testing-specialist.md +1 -4
  44. package/framework/agents.json +1145 -278
  45. package/framework/hooks/claude-code/statusline.py +384 -85
  46. package/framework/hooks/shared/phase-utils.js +129 -129
  47. package/framework/rules/frontend-standards.md +0 -3
  48. package/framework/rules/nextjs-standards.md +17 -0
  49. package/framework/skills/README.md +66 -0
  50. package/framework/skills/level-0-meta/{brainstorming.md → brainstorming/SKILL.md} +3 -1
  51. package/framework/skills/level-0-meta/brainstorming/references/proposal-example.md +138 -0
  52. package/framework/skills/level-0-meta/{code-review.md → code-review/SKILL.md} +3 -2
  53. package/framework/skills/level-0-meta/code-review/references/review-example.md +164 -0
  54. package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +121 -0
  55. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +147 -0
  56. package/framework/skills/level-0-meta/code-review-nextjs/references/review-example-nextjs.md +254 -0
  57. package/framework/skills/level-0-meta/{morph-checklist.md → morph-checklist/SKILL.md} +2 -5
  58. package/framework/skills/{level-1-workflows/morph-replicate.md → level-0-meta/morph-replicate/SKILL.md} +6 -7
  59. package/framework/skills/level-0-meta/{simulation-checklist.md → simulation-checklist/SKILL.md} +3 -6
  60. package/framework/skills/level-0-meta/{tool-usage-guide.md → tool-usage-guide/SKILL.md} +4 -5
  61. package/framework/skills/level-0-meta/{verification-before-completion.md → verification-before-completion/SKILL.md} +3 -1
  62. package/framework/skills/level-0-meta/verification-before-completion/scripts/check-phase-outputs.mjs +110 -0
  63. package/framework/skills/level-1-workflows/{phase-clarify.md → phase-clarify/SKILL.md} +3 -3
  64. package/framework/skills/level-1-workflows/phase-clarify/references/clarifications-example.md +117 -0
  65. package/framework/skills/level-1-workflows/{phase-codebase-analysis.md → phase-codebase-analysis/SKILL.md} +2 -3
  66. package/framework/skills/level-1-workflows/{phase-design.md → phase-design/SKILL.md} +46 -182
  67. package/framework/skills/level-1-workflows/phase-design/references/spec-example.md +253 -0
  68. package/framework/skills/level-1-workflows/{phase-implement.md → phase-implement/SKILL.md} +3 -3
  69. package/framework/skills/level-1-workflows/phase-implement/references/recap-example.md +132 -0
  70. package/framework/skills/level-1-workflows/{phase-setup.md → phase-setup/SKILL.md} +2 -3
  71. package/framework/skills/level-1-workflows/{phase-tasks.md → phase-tasks/SKILL.md} +42 -3
  72. package/framework/skills/level-1-workflows/phase-tasks/references/tasks-example.md +231 -0
  73. package/framework/skills/level-1-workflows/phase-tasks/scripts/validate-tasks.mjs +112 -0
  74. package/framework/skills/level-1-workflows/{phase-uiux.md → phase-uiux/SKILL.md} +2 -3
  75. package/framework/standards/STANDARDS.json +121 -0
  76. package/framework/standards/architecture/ddd/bounded-contexts.md +105 -0
  77. package/framework/standards/architecture/ddd/complexity-levels.md +108 -0
  78. package/framework/standards/architecture/ddd/ubiquitous-language.md +58 -0
  79. package/framework/standards/frontend/nextjs/app-router.md +123 -0
  80. package/framework/standards/frontend/nextjs/components.md +132 -0
  81. package/framework/standards/frontend/nextjs/data-fetching.md +126 -0
  82. package/framework/standards/frontend/nextjs/forms.md +128 -0
  83. package/framework/standards/frontend/nextjs/naming-conventions.md +67 -0
  84. package/framework/standards/frontend/nextjs/project-structure.md +102 -0
  85. package/framework/standards/frontend/nextjs/state-management.md +72 -0
  86. package/framework/standards/frontend/nextjs/testing.md +111 -0
  87. package/framework/templates/REGISTRY.json +538 -142
  88. package/framework/templates/code/dotnet/contracts/contracts-level1.cs +69 -0
  89. package/framework/templates/code/dotnet/contracts/contracts-level2.cs +86 -0
  90. package/framework/templates/code/dotnet/contracts/contracts-level3.cs +41 -0
  91. package/framework/templates/docs/spec.md +49 -0
  92. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -0
  93. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -0
  94. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -0
  95. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -0
  96. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -0
  97. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -0
  98. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -0
  99. package/framework/templates/project-structure/dotnet-ddd.md +70 -0
  100. package/framework/workflows/docs/enforcement-pipeline.md +2 -1
  101. package/package.json +1 -1
  102. package/scripts/scan-nextjs.mjs +169 -0
  103. package/src/commands/project/doctor.js +52 -1
  104. package/src/commands/project/init.js +19 -65
  105. package/src/commands/project/update.js +7 -63
  106. package/src/lib/detectors/claude-config-detector.js +1 -3
  107. package/src/lib/standards/standards-context-injector.js +5 -0
  108. package/src/lib/validators/nextjs/index.js +6 -0
  109. package/src/lib/validators/nextjs/next-component-validator.js +181 -0
  110. package/src/lib/validators/validation-runner.js +5 -0
  111. package/src/utils/agents-installer.js +16 -4
  112. package/src/utils/skills-installer.js +59 -15
  113. package/.morph/.morphversion +0 -5
  114. package/.morph/analytics/threads-log.jsonl +0 -44
  115. package/.morph/config/config.json +0 -8
  116. package/.morph/context/README.md +0 -17
  117. package/.morph/framework/agents.json +0 -948
  118. package/.morph/framework/standards/STANDARDS.json +0 -812
  119. package/.morph/framework/standards/ai-agents/blazor-ui.md +0 -364
  120. package/.morph/framework/standards/ai-agents/production.md +0 -415
  121. package/.morph/framework/standards/ai-agents/setup.md +0 -418
  122. package/.morph/framework/standards/ai-agents/team-orchestration.md +0 -479
  123. package/.morph/framework/standards/ai-agents/workflows.md +0 -354
  124. package/.morph/framework/standards/architecture/ddd/aggregates.md +0 -120
  125. package/.morph/framework/standards/architecture/ddd/entities.md +0 -99
  126. package/.morph/framework/standards/architecture/ddd/value-objects.md +0 -124
  127. package/.morph/framework/standards/backend/api/minimal-api.md +0 -494
  128. package/.morph/framework/standards/backend/api/rest.md +0 -492
  129. package/.morph/framework/standards/backend/api/validation.md +0 -88
  130. package/.morph/framework/standards/backend/authentication/passkeys.md +0 -428
  131. package/.morph/framework/standards/backend/database/ef-core.md +0 -199
  132. package/.morph/framework/standards/backend/database/migrations.md +0 -393
  133. package/.morph/framework/standards/backend/database/postgresql/database.md +0 -352
  134. package/.morph/framework/standards/backend/database/repository-patterns.md +0 -528
  135. package/.morph/framework/standards/backend/database/vector-search-rag.md +0 -541
  136. package/.morph/framework/standards/backend/dotnet/async.md +0 -366
  137. package/.morph/framework/standards/backend/dotnet/core.md +0 -117
  138. package/.morph/framework/standards/backend/dotnet/di.md +0 -439
  139. package/.morph/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
  140. package/.morph/framework/standards/backend/integrations/asaas/asaas-api.md +0 -216
  141. package/.morph/framework/standards/backend/integrations/clerk/clerk-auth.md +0 -290
  142. package/.morph/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +0 -350
  143. package/.morph/framework/standards/backend/integrations/resend/resend-email.md +0 -385
  144. package/.morph/framework/standards/context/analytics.md +0 -96
  145. package/.morph/framework/standards/context/bundles.md +0 -110
  146. package/.morph/framework/standards/context/priming.md +0 -78
  147. package/.morph/framework/standards/core/architecture.md +0 -185
  148. package/.morph/framework/standards/core/coding.md +0 -214
  149. package/.morph/framework/standards/core/git-branching-strategy.md +0 -403
  150. package/.morph/framework/standards/core/git.md +0 -185
  151. package/.morph/framework/standards/core/testing.md +0 -295
  152. package/.morph/framework/standards/data/nosql/blob-storage.md +0 -102
  153. package/.morph/framework/standards/data/nosql/cache/redis.md +0 -97
  154. package/.morph/framework/standards/data/nosql/cosmos-db.md +0 -118
  155. package/.morph/framework/standards/data/vector-search/azure-ai-search.md +0 -121
  156. package/.morph/framework/standards/data/vector-search/rag-chunking.md +0 -104
  157. package/.morph/framework/standards/frontend/blazor/design-checklist.md +0 -222
  158. package/.morph/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
  159. package/.morph/framework/standards/frontend/blazor/fluent-ui.md +0 -137
  160. package/.morph/framework/standards/frontend/blazor/html-conversion.md +0 -184
  161. package/.morph/framework/standards/frontend/blazor/lifecycle.md +0 -195
  162. package/.morph/framework/standards/frontend/blazor/pitfalls.md +0 -198
  163. package/.morph/framework/standards/frontend/blazor/state.md +0 -191
  164. package/.morph/framework/standards/frontend/design-system/animations.md +0 -151
  165. package/.morph/framework/standards/frontend/design-system/naming.md +0 -64
  166. package/.morph/framework/standards/frontend/nextjs/nextjs-patterns.md +0 -198
  167. package/.morph/framework/standards/infrastructure/azure/azure.md +0 -624
  168. package/.morph/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
  169. package/.morph/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
  170. package/.morph/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
  171. package/.morph/framework/standards/infrastructure/azure/services/functions.md +0 -486
  172. package/.morph/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
  173. package/.morph/framework/standards/infrastructure/azure/services/storage.md +0 -407
  174. package/.morph/framework/standards/infrastructure/docker/easypanel-deploy.md +0 -196
  175. package/.morph/framework/standards/infrastructure/supabase/mcp-setup.md +0 -252
  176. package/.morph/framework/standards/infrastructure/supabase/supabase-auth.md +0 -176
  177. package/.morph/framework/standards/infrastructure/supabase/supabase-pgvector.md +0 -169
  178. package/.morph/framework/standards/infrastructure/supabase/supabase-rls.md +0 -184
  179. package/.morph/framework/standards/infrastructure/supabase/supabase-storage.md +0 -153
  180. package/.morph/framework/standards/integration/api/graphql.md +0 -91
  181. package/.morph/framework/standards/integration/api/grpc.md +0 -114
  182. package/.morph/framework/standards/integration/api/rest-design.md +0 -95
  183. package/.morph/framework/standards/integration/event-driven/cqrs.md +0 -101
  184. package/.morph/framework/standards/integration/event-driven/event-sourcing.md +0 -124
  185. package/.morph/framework/standards/integration/event-driven/service-bus.md +0 -95
  186. package/.morph/framework/standards/integration/mcp/mcp-tools.md +0 -384
  187. package/.morph/framework/standards/observability/logging.md +0 -131
  188. package/.morph/framework/standards/observability/metrics.md +0 -121
  189. package/.morph/framework/standards/observability/monitoring.md +0 -114
  190. package/.morph/framework/standards/observability/tracing.md +0 -132
  191. package/.morph/framework/standards/workflows/parallel-execution.md +0 -112
  192. package/.morph/framework/standards/workflows/thread-management.md +0 -113
  193. package/.morph/framework/templates/.idea/morph-templates.xml +0 -92
  194. package/.morph/framework/templates/.vscode/morph-templates.code-snippets +0 -186
  195. package/.morph/framework/templates/IDE-SNIPPETS.md +0 -266
  196. package/.morph/framework/templates/README.md +0 -814
  197. package/.morph/framework/templates/REGISTRY.json +0 -1492
  198. package/.morph/framework/templates/code/dotnet/backend/repository.cs +0 -141
  199. package/.morph/framework/templates/code/dotnet/backend/service.cs +0 -139
  200. package/.morph/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
  201. package/.morph/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
  202. package/.morph/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
  203. package/.morph/framework/templates/code/dotnet/contracts/README.md +0 -74
  204. package/.morph/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
  205. package/.morph/framework/templates/code/dotnet/contracts/contracts.cs +0 -217
  206. package/.morph/framework/templates/code/dotnet/contracts/contracts.cs.hbs +0 -172
  207. package/.morph/framework/templates/code/dotnet/database/migration.cs +0 -83
  208. package/.morph/framework/templates/code/dotnet/frontend/component.razor +0 -239
  209. package/.morph/framework/templates/code/dotnet/jobs/agent.cs +0 -163
  210. package/.morph/framework/templates/code/dotnet/jobs/job.cs +0 -171
  211. package/.morph/framework/templates/code/dotnet/test.cs +0 -239
  212. package/.morph/framework/templates/code/sql/rls-policy.sql +0 -57
  213. package/.morph/framework/templates/code/sql/supabase-migration.sql +0 -100
  214. package/.morph/framework/templates/code/sql/supabase-migration.template.sql +0 -113
  215. package/.morph/framework/templates/code/typescript/contracts.ts +0 -168
  216. package/.morph/framework/templates/context/CONTEXT-FEATURE.md +0 -276
  217. package/.morph/framework/templates/context/CONTEXT.md +0 -181
  218. package/.morph/framework/templates/docs/clarifications.md +0 -253
  219. package/.morph/framework/templates/docs/onboarding.md +0 -123
  220. package/.morph/framework/templates/docs/proposal.md +0 -182
  221. package/.morph/framework/templates/docs/schema-analysis.md +0 -119
  222. package/.morph/framework/templates/docs/spec.md +0 -149
  223. package/.morph/framework/templates/docs/ui-components.md +0 -124
  224. package/.morph/framework/templates/docs/ui-design-system.md +0 -76
  225. package/.morph/framework/templates/docs/ui-flows.md +0 -167
  226. package/.morph/framework/templates/docs/ui-mockups.md +0 -98
  227. package/.morph/framework/templates/examples/design-system-examples.md +0 -357
  228. package/.morph/framework/templates/examples/spec-examples.md +0 -90
  229. package/.morph/framework/templates/feature/decisions.md +0 -187
  230. package/.morph/framework/templates/feature/recap.md +0 -146
  231. package/.morph/framework/templates/feature/tasks.md +0 -199
  232. package/.morph/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
  233. package/.morph/framework/templates/infrastructure/azure/README.md +0 -286
  234. package/.morph/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
  235. package/.morph/framework/templates/infrastructure/azure/app-service.bicep +0 -164
  236. package/.morph/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
  237. package/.morph/framework/templates/infrastructure/azure/container-app.bicep +0 -156
  238. package/.morph/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
  239. package/.morph/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
  240. package/.morph/framework/templates/infrastructure/azure/deploy.sh +0 -208
  241. package/.morph/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
  242. package/.morph/framework/templates/infrastructure/azure/main.bicep +0 -189
  243. package/.morph/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
  244. package/.morph/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
  245. package/.morph/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
  246. package/.morph/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
  247. package/.morph/framework/templates/infrastructure/azure/storage.bicep +0 -106
  248. package/.morph/framework/templates/infrastructure/docker/Dockerfile.template +0 -58
  249. package/.morph/framework/templates/infrastructure/docker/docker-compose.template.yml +0 -67
  250. package/.morph/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +0 -38
  251. package/.morph/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +0 -48
  252. package/.morph/framework/templates/infrastructure/docker/easypanel.template.json +0 -54
  253. package/.morph/framework/templates/infrastructure/github/README.md +0 -593
  254. package/.morph/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
  255. package/.morph/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +0 -45
  256. package/.morph/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +0 -27
  257. package/.morph/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
  258. package/.morph/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +0 -31
  259. package/.morph/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +0 -59
  260. package/.morph/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +0 -39
  261. package/.morph/framework/templates/integrations/asaas-client.cs +0 -387
  262. package/.morph/framework/templates/integrations/asaas-webhook.cs +0 -351
  263. package/.morph/framework/templates/integrations/azure-identity-config.cs +0 -288
  264. package/.morph/framework/templates/integrations/clerk-config.cs +0 -258
  265. package/.morph/framework/templates/meta-prompts/fusion/fusion-agent.md +0 -76
  266. package/.morph/framework/templates/meta-prompts/fusion/fusion-aggregator.md +0 -100
  267. package/.morph/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  268. package/.morph/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  269. package/.morph/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  270. package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
  271. package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
  272. package/.morph/framework/templates/meta-prompts/squad-leaders/backend-squad.md +0 -90
  273. package/.morph/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +0 -126
  274. package/.morph/framework/templates/meta-prompts/squad-leaders/squad-leader.md +0 -43
  275. package/.morph/framework/templates/meta-prompts/validators/checkpoint-validator.md +0 -107
  276. package/.morph/framework/templates/meta-prompts/validators/pre-commit-validator.md +0 -95
  277. package/.morph/framework/templates/saas/subscription.cs +0 -347
  278. package/.morph/framework/templates/saas/tenant.cs +0 -338
  279. package/.morph/framework/templates/state.template.json +0 -17
  280. package/.morph/framework/templates/ui/FluentDesignTheme.cs +0 -149
  281. package/.morph/framework/templates/ui/MudTheme.cs +0 -281
  282. package/.morph/framework/templates/ui/design-system.css +0 -226
  283. package/.morph/logs/tool-failures.log +0 -51
  284. package/.morph/memory/pre-compact-2026-02-22T17-01-01-658Z.json +0 -16
  285. package/.morph/state.json +0 -48
  286. package/framework/skills/level-2-domains/backend/api-designer.md +0 -66
  287. package/framework/skills/level-2-domains/backend/ef-modeler.md +0 -65
  288. package/framework/skills/level-2-domains/frontend/nextjs-expert.md +0 -161
  289. package/framework/skills/level-3-technologies/README.md +0 -7
  290. package/framework/skills/level-4-patterns/README.md +0 -7
  291. package/framework/templates/code/dotnet/contracts/contracts.cs +0 -217
  292. package/framework/templates/code/dotnet/contracts/contracts.cs.hbs +0 -172
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scan-nextjs.mjs - CLI scan for Next.js violations.
4
+ * Usage: node scripts/scan-nextjs.mjs [path] [--json]
5
+ * Exit: 0=clean, 1=errors, 2=scan failed
6
+ */
7
+
8
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
9
+ import { join, resolve } from 'node:path';
10
+ import { validateNextComponent } from '../src/lib/validators/nextjs/next-component-validator.js';
11
+
12
+ const args = process.argv.slice(2);
13
+ const jsonMode = args.includes('--json');
14
+ const pathArg = args.find(a => !a.startsWith('--'));
15
+ const targetPath = pathArg ? resolve(pathArg) : resolve('src');
16
+
17
+ function checkUseEffectFetch(content, filePath) {
18
+ const issues = [];
19
+ if (/useEffect\s*\(\s*(?:async\s+)?\(\s*\)\s*=>/.test(content) && /\bfetch\s*\(/.test(content)) {
20
+ issues.push({
21
+ type: 'error',
22
+ message: "useEffect used for data fetching — use Server Components or TanStack Query (useQuery) instead",
23
+ suggestion: "Replace with a Server Component fetch or TanStack Query useQuery hook",
24
+ file: filePath,
25
+ });
26
+ }
27
+ return issues;
28
+ }
29
+
30
+ function checkDefaultExport(content, filePath) {
31
+ const issues = [];
32
+ const fileName = filePath.split('/').pop() ?? '';
33
+ const SPECIAL = ['page.tsx', 'layout.tsx', 'loading.tsx', 'error.tsx', 'not-found.tsx'];
34
+ if (!SPECIAL.includes(fileName) && /^export\s+default\s+/m.test(content) && fileName.endsWith('.tsx')) {
35
+ issues.push({
36
+ type: 'warning',
37
+ message: `Default export in '${fileName}' — prefer named exports for easier refactoring`,
38
+ suggestion: "Change to: export function ComponentName() {}",
39
+ file: filePath,
40
+ });
41
+ }
42
+ return issues;
43
+ }
44
+
45
+ function findTsxFiles(dir) {
46
+ const results = [];
47
+ let entries;
48
+ try {
49
+ entries = readdirSync(dir, { withFileTypes: true });
50
+ } catch {
51
+ return results;
52
+ }
53
+ for (const entry of entries) {
54
+ const fullPath = join(dir, entry.name);
55
+ if (['node_modules', '.next', 'dist', '.git'].includes(entry.name)) continue;
56
+ if (entry.isDirectory()) {
57
+ results.push(...findTsxFiles(fullPath));
58
+ } else if (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts')) {
59
+ results.push(fullPath);
60
+ }
61
+ }
62
+ return results;
63
+ }
64
+ function main() {
65
+ if (!existsSync(targetPath)) {
66
+ const msg = `Error: Path does not exist: ${targetPath}`;
67
+ if (jsonMode) {
68
+ console.log(JSON.stringify({ error: msg, exitCode: 2 }));
69
+ } else {
70
+ process.stderr.write(msg + '\n');
71
+ }
72
+ process.exit(2);
73
+ }
74
+
75
+ let files;
76
+ try {
77
+ files = findTsxFiles(targetPath);
78
+ } catch (err) {
79
+ const msg = `Error scanning directory: ${err.message}`;
80
+ if (jsonMode) {
81
+ console.log(JSON.stringify({ error: msg, exitCode: 2 }));
82
+ } else {
83
+ process.stderr.write(msg + '\n');
84
+ }
85
+ process.exit(2);
86
+ }
87
+
88
+ const normalizedTarget = targetPath.replace(/\\/g, '/');
89
+
90
+ const allIssues = [];
91
+ for (const filePath of files) {
92
+ let content;
93
+ try {
94
+ content = readFileSync(filePath, 'utf8');
95
+ } catch {
96
+ continue;
97
+ }
98
+ const normalizedFile = filePath.replace(/\\/g, '/');
99
+ const relPath = normalizedFile.startsWith(normalizedTarget)
100
+ ? normalizedFile.slice(normalizedTarget.length).replace(/^\//, '')
101
+ : normalizedFile;
102
+
103
+ const issues = [
104
+ ...validateNextComponent(content, relPath),
105
+ ...checkUseEffectFetch(content, relPath),
106
+ ...checkDefaultExport(content, relPath),
107
+ ];
108
+ allIssues.push(...issues);
109
+ }
110
+
111
+ const errors = allIssues.filter(i => i.type === 'error');
112
+ const warnings = allIssues.filter(i => i.type === 'warning');
113
+
114
+ if (jsonMode) {
115
+ const out = JSON.stringify({
116
+ files: files.length,
117
+ issues: allIssues.map(i => ({
118
+ severity: i.type,
119
+ message: i.message,
120
+ file: i.file,
121
+ line: i.line ?? null,
122
+ suggestion: i.suggestion ?? null,
123
+ })),
124
+ errors: errors.length,
125
+ warnings: warnings.length,
126
+ exitCode: errors.length > 0 ? 1 : 0,
127
+ }, null, 2);
128
+ process.stdout.write(out + '\n');
129
+ process.exit(errors.length > 0 ? 1 : 0);
130
+ }
131
+
132
+ const displayPath = targetPath.replace(process.cwd().replace(/\\/g, '/'), '.').replace(/\\/g, '/');
133
+ console.log(`🔍 Scanning ${displayPath} (${files.length} file${files.length !== 1 ? "s" : ""})...`);
134
+ console.log("");
135
+
136
+ if (allIssues.length === 0) {
137
+ console.log(`✅ No issues found — ${files.length} file${files.length !== 1 ? "s" : ""} scanned`);
138
+ console.log(`Summary: ${files.length} files scanned | 0 issues (0 critical, 0 high, 0 medium)`);
139
+ process.exit(0);
140
+ }
141
+
142
+ if (errors.length > 0) {
143
+ console.log(`CRITICAL (${errors.length})`);
144
+ for (const issue of errors) {
145
+ const loc = issue.line ? `:${issue.line}` : "";
146
+ console.log(` ${issue.file}${loc} — ${issue.message}`);
147
+ if (issue.suggestion) console.log(` → ${issue.suggestion}`);
148
+ }
149
+ console.log('');
150
+ }
151
+
152
+ if (warnings.length > 0) {
153
+ console.log(`HIGH/MEDIUM (${warnings.length})`);
154
+ for (const issue of warnings) {
155
+ console.log(` ${issue.file} — ${issue.message}`);
156
+ }
157
+ console.log('');
158
+ }
159
+
160
+ const cleanFiles = files.length - new Set(allIssues.map(i => i.file)).size;
161
+ if (cleanFiles > 0) {
162
+ console.log(`✅ No issues found in ${cleanFiles} file${cleanFiles !== 1 ? "s" : ""}`);
163
+ }
164
+
165
+ console.log(`Summary: ${files.length} files scanned | ${allIssues.length} issue${allIssues.length !== 1 ? "s" : ""} (${errors.length} critical, ${warnings.length} high/medium)`);
166
+ process.exit(errors.length > 0 ? 1 : 0);
167
+ }
168
+
169
+ main();
@@ -411,7 +411,58 @@ export async function doctorCommand(options = {}) {
411
411
  // Check agents.json
412
412
  const agentsPath = join(morphPath, 'framework', 'agents.json');
413
413
  if (await pathExists(agentsPath)) {
414
- checks.push({ name: 'agents.json', status: 'ok' });
414
+ try {
415
+ const agentsData = JSON.parse(await fs.readFile(agentsPath, 'utf8'));
416
+ const agentEntries = Object.entries(agentsData.agents || {})
417
+ .filter(([k]) => !k.startsWith('_comment'));
418
+ const actualCount = agentEntries.length;
419
+ const declaredCount = agentsData.total_agents;
420
+
421
+ // Check 1: total_agents matches actual count
422
+ if (actualCount !== declaredCount) {
423
+ checks.push({
424
+ name: 'agents.json count',
425
+ status: 'warn',
426
+ msg: `total_agents declares ${declaredCount} but found ${actualCount} agents`
427
+ });
428
+ hasWarnings = true;
429
+ } else {
430
+ checks.push({ name: 'agents.json count', status: 'ok', msg: `${actualCount} agents` });
431
+ }
432
+
433
+ // Check 2: required tier-2 agents present
434
+ const requiredTier2 = ['standards-architect', 'dotnet-senior', 'infra-architect', 'domain-architect'];
435
+ const missingTier2 = requiredTier2.filter(id => !agentsData.agents[id]);
436
+ if (missingTier2.length > 0) {
437
+ checks.push({
438
+ name: 'agents.json required agents',
439
+ status: 'error',
440
+ msg: `Missing required agents: ${missingTier2.join(', ')}`
441
+ });
442
+ hasErrors = true;
443
+ } else {
444
+ checks.push({ name: 'agents.json required agents', status: 'ok' });
445
+ }
446
+
447
+ // Check 3: tier counts sum matches total
448
+ const tierCounts = [1, 2, 3, 4].map(t =>
449
+ agentEntries.filter(([, v]) => v.tier === t).length
450
+ );
451
+ const tierSum = tierCounts.reduce((a, b) => a + b, 0);
452
+ if (tierSum !== actualCount) {
453
+ checks.push({
454
+ name: 'agents.json tier counts',
455
+ status: 'warn',
456
+ msg: `Tier counts sum to ${tierSum} but total is ${actualCount}`
457
+ });
458
+ hasWarnings = true;
459
+ } else {
460
+ checks.push({ name: 'agents.json tier counts', status: 'ok' });
461
+ }
462
+ } catch {
463
+ checks.push({ name: 'agents.json', status: 'error', msg: 'invalid JSON' });
464
+ hasErrors = true;
465
+ }
415
466
  } else {
416
467
  checks.push({ name: 'agents.json', status: 'missing' });
417
468
  hasErrors = true;
@@ -4,7 +4,6 @@ import ora from 'ora';
4
4
  import chalk from 'chalk';
5
5
  import { logger } from '../../utils/logger.js';
6
6
  import {
7
- getContentDir,
8
7
  copyDirectory,
9
8
  copyFile,
10
9
  pathExists,
@@ -13,9 +12,7 @@ import {
13
12
  ensureDir,
14
13
  writeFile,
15
14
  readFile,
16
- updateGitignore,
17
- createSymlink,
18
- createDirectoryLink
15
+ updateGitignore
19
16
  } from '../../utils/file-copier.js';
20
17
  import { saveProjectMorphVersion, getInstalledCLIVersion } from '../../utils/version-checker.js';
21
18
  import { installClaudeHooks, installGlobalStatusline } from '../../utils/claude-settings-manager.js';
@@ -122,7 +119,6 @@ Run \`morph-spec detect\` to analyze your project.
122
119
  await writeFile(contextReadme, readmeContent);
123
120
 
124
121
  // 5. Copy framework templates (project-local overrides start from framework defaults)
125
- const contentDir = getContentDir();
126
122
  const frameworkTemplatesSrc = join(import.meta.dirname, '..', '..', '..', 'framework', 'templates');
127
123
  const templatesDest = join(frameworkDestDir, 'templates');
128
124
  let templatesCopied = false;
@@ -151,14 +147,12 @@ Run \`morph-spec detect\` to analyze your project.
151
147
  await copyFile(agentsSrc, agentsDest);
152
148
  }
153
149
 
154
- // 8. Copy .claude commands and create symlinks for skills
150
+ // 8. Copy .claude commands and install skills
155
151
  // Source: framework/ (canonical for all stacks)
156
152
  spinner.text = 'Setting up Claude Code integration...';
157
153
  const frameworkDir = join(import.meta.dirname, '..', '..', '..', 'framework');
158
154
  const claudeDest = join(targetPath, '.claude');
159
155
 
160
- let symlinkCount = 0;
161
- let copyCount = 0;
162
156
  let commandsCopied = false;
163
157
 
164
158
  // Copy commands directory (slash commands): framework/commands/ → .claude/commands/
@@ -171,55 +165,6 @@ Run \`morph-spec detect\` to analyze your project.
171
165
  logger.warn(' ⚠ framework/commands/ source missing — commands not installed');
172
166
  }
173
167
 
174
- // Create directory links for skill categories (or copy if linking fails)
175
- // Source: framework/skills/ → .claude/skills/
176
- {
177
- const skillsSrc = join(frameworkDir, 'skills');
178
- const skillsDest = join(claudeDest, 'skills');
179
-
180
- if (await pathExists(skillsSrc)) {
181
- await ensureDir(skillsDest);
182
-
183
- const entries = await fs.readdir(skillsSrc, { withFileTypes: true });
184
-
185
- let linkedCategories = 0;
186
- let copiedCategories = 0;
187
- let totalSkillFiles = 0;
188
-
189
- // Link category directories (specialists/, infra/, checklists/, etc.)
190
- for (const entry of entries) {
191
- if (entry.isDirectory()) {
192
- const categorySrc = join(skillsSrc, entry.name);
193
- const categoryDest = join(skillsDest, entry.name);
194
-
195
- // Count .md files for reporting
196
- const categoryEntries = await fs.readdir(categorySrc, { withFileTypes: true });
197
- const mdFiles = categoryEntries.filter(e => e.isFile() && e.name.endsWith('.md'));
198
- totalSkillFiles += mdFiles.length;
199
-
200
- const result = await createDirectoryLink(categorySrc, categoryDest);
201
- if (result === 'copy') {
202
- copiedCategories++;
203
- } else {
204
- linkedCategories++;
205
- }
206
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
207
- // Handle .md files in skills root
208
- totalSkillFiles++;
209
- const result = await createSymlink(join(skillsSrc, entry.name), join(skillsDest, entry.name), 'file');
210
- if (result === 'symlink') {
211
- linkedCategories++;
212
- } else {
213
- copiedCategories++;
214
- }
215
- }
216
- }
217
-
218
- symlinkCount = linkedCategories > 0 ? totalSkillFiles : 0;
219
- copyCount = linkedCategories === 0 ? totalSkillFiles : 0;
220
- }
221
- }
222
-
223
168
  // 9a. Install path-scoped rules to .claude/rules/ (native Claude Code pattern)
224
169
  spinner.text = 'Installing path-scoped rules to .claude/rules/...';
225
170
  const rulesSrc = join(frameworkDir, 'rules');
@@ -236,7 +181,8 @@ Run \`morph-spec detect\` to analyze your project.
236
181
 
237
182
  // 9b2. Install tier-1/2 agents as native Claude Code subagents in .claude/agents/
238
183
  spinner.text = 'Installing native subagents to .claude/agents/...';
239
- await installAgents(targetPath, frameworkDir);
184
+ const detectedStackForAgents = config.project.stack ?? null;
185
+ await installAgents(targetPath, frameworkDir, { projectStack: detectedStackForAgents });
240
186
 
241
187
  // 9b2b. Install level-2 domain agents as native Claude Code subagents in .claude/agents/
242
188
  await installDomainAgents(targetPath, frameworkDir);
@@ -341,6 +287,19 @@ Run \`morph-spec detect\` to analyze your project.
341
287
 
342
288
  if (structure?.stack && structure.stack !== 'unknown') {
343
289
  detectedStack = structure.stack;
290
+
291
+ // Persist detected stack to config.json
292
+ if (structure?.stack && structure.stack !== 'unknown') {
293
+ config.project.stack = structure.stack;
294
+ }
295
+ if (structure?.architecture && structure.architecture !== 'unknown') {
296
+ config.project.architecture = structure.architecture;
297
+ }
298
+ if (structure?.uiLibrary) {
299
+ config.project.uiLibrary = structure.uiLibrary;
300
+ }
301
+ await writeJson(configPath, config);
302
+
344
303
  spinner.stop();
345
304
  logger.blank();
346
305
  logger.header('Existing Project Detected');
@@ -542,13 +501,8 @@ Run \`morph-spec detect\` to analyze your project.
542
501
  }
543
502
  }
544
503
 
545
- if (symlinkCount > 0) {
546
- const linkType = process.platform === 'win32' ? 'junction-linked' : 'symlinked';
547
- logger.dim(` ✓ .claude/skills/ (${symlinkCount} ${linkType})`);
548
- } else if (copyCount > 0) {
549
- logger.dim(` ✓ .claude/skills/ (${copyCount} copied)`);
550
- logger.warn(` ⚠ Directory links not supported (copied instead). Skills won't auto-update.`);
551
- }
504
+ logger.dim(' ✓ .claude/skills/ (installed as skill directories)');
505
+ logger.dim(' .claude/agents/ (native subagents installed)');
552
506
 
553
507
  logger.blank();
554
508
 
@@ -14,8 +14,6 @@ import {
14
14
  copyFile,
15
15
  pathExists,
16
16
  ensureDir,
17
- createDirectoryLink,
18
- createSymlink,
19
17
  readJson,
20
18
  writeJson
21
19
  } from '../../utils/file-copier.js';
@@ -239,8 +237,6 @@ export async function updateCommand(options) {
239
237
  const frameworkDir = join(__dirname, '..', '..', '..', 'framework');
240
238
  const claudeDest = join(targetPath, '.claude');
241
239
 
242
- let symlinkCount = 0;
243
- let copyCount = 0;
244
240
  let commandsCopied = false;
245
241
 
246
242
  // Copy commands directory (slash commands): framework/commands/ → .claude/commands/
@@ -253,62 +249,18 @@ export async function updateCommand(options) {
253
249
  logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
254
250
  }
255
251
 
256
- // Create directory links for skill categories (or copy if linking fails)
257
- // Source: framework/skills/ → .claude/skills/
258
- {
259
- const skillsSrc = join(frameworkDir, 'skills');
260
- const skillsDest = join(claudeDest, 'skills');
261
-
262
- if (await pathExists(skillsSrc)) {
263
- await ensureDir(skillsDest);
264
-
265
- const entries = await fs.readdir(skillsSrc, { withFileTypes: true });
266
-
267
- let linkedCategories = 0;
268
- let copiedCategories = 0;
269
- let totalSkillFiles = 0;
270
-
271
- // Link category directories (specialists/, infra/, checklists/, etc.)
272
- for (const entry of entries) {
273
- if (entry.isDirectory()) {
274
- const categorySrc = join(skillsSrc, entry.name);
275
- const categoryDest = join(skillsDest, entry.name);
276
-
277
- // Count .md files for reporting
278
- const categoryEntries = await fs.readdir(categorySrc, { withFileTypes: true });
279
- const mdFiles = categoryEntries.filter(e => e.isFile() && e.name.endsWith('.md'));
280
- totalSkillFiles += mdFiles.length;
281
-
282
- const result = await createDirectoryLink(categorySrc, categoryDest);
283
- if (result === 'copy') {
284
- copiedCategories++;
285
- } else {
286
- linkedCategories++;
287
- }
288
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
289
- // Handle .md files in skills root
290
- totalSkillFiles++;
291
- const result = await createSymlink(join(skillsSrc, entry.name), join(skillsDest, entry.name), 'file');
292
- if (result === 'symlink') {
293
- linkedCategories++;
294
- } else {
295
- copiedCategories++;
296
- }
297
- }
298
- }
299
-
300
- symlinkCount = linkedCategories > 0 ? totalSkillFiles : 0;
301
- copyCount = linkedCategories === 0 ? totalSkillFiles : 0;
302
- }
303
- }
304
-
305
252
  // Sync morph skills to .claude/skills/ for native Claude Code discovery
306
253
  updateSpinner.text = 'Syncing morph skills to .claude/skills/...';
307
254
  await installSkills(targetPath);
308
255
 
309
256
  // Sync native subagents to .claude/agents/
310
257
  updateSpinner.text = 'Syncing agents to .claude/agents/...';
311
- await installAgents(targetPath, frameworkDir);
258
+ let projectStack = null;
259
+ try {
260
+ const cfg = await readJson(join(morphPath, 'config', 'config.json'));
261
+ projectStack = cfg?.project?.stack ?? null;
262
+ } catch { /* ignore — config may not exist yet */ }
263
+ await installAgents(targetPath, frameworkDir, { projectStack });
312
264
  await installDomainAgents(targetPath, frameworkDir);
313
265
 
314
266
  // Sync path-scoped rules to .claude/rules/
@@ -365,16 +317,8 @@ export async function updateCommand(options) {
365
317
  logger.dim(' ✓ .morph/framework/agents.json');
366
318
  if (commandsCopied) logger.dim(' ✓ .claude/commands/');
367
319
 
368
- if (symlinkCount > 0) {
369
- const linkType = process.platform === 'win32' ? 'junction-linked' : 'symlinked';
370
- logger.dim(` ✓ .claude/skills/ (${symlinkCount} ${linkType})`);
371
- } else if (copyCount > 0) {
372
- logger.dim(` ✓ .claude/skills/ (${copyCount} copied)`);
373
- logger.warn(` ⚠ Directory links not supported (copied instead). Skills won't auto-update.`);
374
- }
375
-
376
320
  logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
377
- logger.dim(' ✓ .claude/skills/ (flat .md for /skill-name discovery)');
321
+ logger.dim(' ✓ .claude/skills/ (skills installed as directories)');
378
322
  logger.dim(' ✓ .claude/agents/ (native subagents refreshed)');
379
323
  logger.dim(' ✓ .claude/rules/ (path-scoped rules synced)');
380
324
  logger.dim(' ✓ .claude/CLAUDE.md (runtime quick reference)');
@@ -144,9 +144,7 @@ function detectCustomSkills(globalClaudeDir, projectClaudeDir) {
144
144
  for (const file of walkMdFiles(projectSkills)) {
145
145
  const name = extractSkillName(file);
146
146
  // Skip morph-spec framework skills (they're managed by init)
147
- if (file.includes('level-0-meta') || file.includes('level-1-workflows') ||
148
- file.includes('level-2-domains') || file.includes('level-3-technologies') ||
149
- file.includes('level-4-patterns')) {
147
+ if (file.includes('level-0-meta') || file.includes('level-1-workflows')) {
150
148
  continue;
151
149
  }
152
150
  skills.push({
@@ -44,6 +44,11 @@ const AGENT_STANDARDS_MAP = {
44
44
  'backend/database/ef-core',
45
45
  'backend/api/rest'
46
46
  ],
47
+ 'infra-architect': [
48
+ 'core/architecture',
49
+ 'core/coding'
50
+ ],
51
+ // Tier 3: Specialists - Infrastructure Squad
47
52
  'azure-architect': [
48
53
  'infrastructure/azure/azure', // Stack-specific
49
54
  'core/architecture',
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Next.js Validators
3
+ *
4
+ * Validators specific to Next.js App Router patterns.
5
+ */
6
+ export { validateNextComponent, validateNextComponentFiles } from './next-component-validator.js';
@@ -0,0 +1,181 @@
1
+ import { glob } from 'glob';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ /**
6
+ * Next.js Component Validator
7
+ *
8
+ * Validates .tsx files for:
9
+ * 1. Unnecessary 'use client' directives (no interactivity)
10
+ * 2. Missing 'use client' when React hooks are used
11
+ * 3. File naming convention (kebab-case)
12
+ *
13
+ * @module next-component-validator
14
+ */
15
+
16
+ // ============================================
17
+ // CONSTANTS
18
+ // ============================================
19
+
20
+ /** Hooks/patterns that require 'use client' */
21
+ const CLIENT_HOOKS = ['useState', 'useEffect', 'useRef', 'useReducer', 'useCallback', 'useMemo', 'useContext'];
22
+
23
+ /** Event handler patterns that indicate interactivity (hooks are checked via CLIENT_HOOKS) */
24
+ const EVENT_HANDLER_PATTERNS = [
25
+ /\bonClick\b/,
26
+ /\bonChange\b/,
27
+ /\bonSubmit\b/,
28
+ /\bonFocus\b/,
29
+ /\bonBlur\b/,
30
+ /\bonKeyDown\b/,
31
+ ];
32
+
33
+ /** Next.js special files exempt from naming checks */
34
+ const NEXTJS_SPECIAL_FILES = ['page.tsx', 'layout.tsx', 'loading.tsx', 'error.tsx', 'not-found.tsx', 'route.ts', 'middleware.ts'];
35
+
36
+ // ============================================
37
+ // MAIN VALIDATION FUNCTION
38
+ // ============================================
39
+
40
+ /**
41
+ * @typedef {Object} ValidationIssue
42
+ * @property {'error' | 'warning' | 'info'} type
43
+ * @property {string} message
44
+ * @property {string} [suggestion]
45
+ * @property {string} file
46
+ * @property {number} [line]
47
+ */
48
+
49
+ /**
50
+ * Validates a Next.js component file.
51
+ *
52
+ * @param {string} content - File content
53
+ * @param {string} filePath - Relative file path
54
+ * @returns {ValidationIssue[]}
55
+ */
56
+ export function validateNextComponent(content, filePath) {
57
+ const issues = [];
58
+
59
+ // Only validate .tsx and .ts files
60
+ if (!filePath.endsWith('.tsx') && !filePath.endsWith('.ts')) {
61
+ return issues;
62
+ }
63
+
64
+ // Skip non-component utility files (no JSX, no hooks)
65
+ const hasJsx = /(?<![a-zA-Z0-9])<[A-Z][a-zA-Z]*[\s/>]|(?<![a-zA-Z0-9])<[a-z][a-z]+[\s>/]/.test(content);
66
+ const hasHooks = CLIENT_HOOKS.some(hook => content.includes(hook));
67
+
68
+ if (!hasJsx && !hasHooks) {
69
+ return issues;
70
+ }
71
+
72
+ const hasUseClient = /^'use client';?|^"use client";?/.test(content.trimStart());
73
+
74
+ // Check 1: use client present but no interactivity
75
+ if (hasUseClient) {
76
+ const hasEventHandlers = EVENT_HANDLER_PATTERNS.some(pattern => pattern.test(content));
77
+ const hasClientHooks = CLIENT_HOOKS.some(hook => new RegExp(`\\b${hook}\\b`).test(content));
78
+ if (!hasEventHandlers && !hasClientHooks) {
79
+ issues.push({
80
+ type: 'warning',
81
+ message: "'use client' directive present but no interactivity detected (no hooks, no event handlers)",
82
+ suggestion: "Remove 'use client' to make this a Server Component, or add interactive behavior",
83
+ file: filePath,
84
+ });
85
+ }
86
+ }
87
+
88
+ // Check 2: hooks used without use client
89
+ if (!hasUseClient) {
90
+ const usedHooks = CLIENT_HOOKS.filter(hook => new RegExp(`\\b${hook}\\b`).test(content));
91
+ if (usedHooks.length > 0) {
92
+ issues.push({
93
+ type: 'error',
94
+ message: `React hook(s) used (${usedHooks.join(', ')}) without 'use client' directive`,
95
+ suggestion: "Add 'use client' as the first line of the file",
96
+ file: filePath,
97
+ line: 1,
98
+ });
99
+ }
100
+ }
101
+
102
+ // Check 3: file naming convention (kebab-case)
103
+ const fileName = filePath.split('/').pop() ?? '';
104
+ const isSpecialFile = NEXTJS_SPECIAL_FILES.includes(fileName);
105
+
106
+ if (!isSpecialFile && fileName.endsWith('.tsx')) {
107
+ const baseName = fileName.replace('.tsx', '');
108
+ const hasUpperCase = /[A-Z]/.test(baseName);
109
+ if (hasUpperCase) {
110
+ issues.push({
111
+ type: 'warning',
112
+ message: `Component file '${fileName}' should use kebab-case naming`,
113
+ suggestion: `Rename to '${toKebabCase(baseName)}.tsx'`,
114
+ file: filePath,
115
+ });
116
+ }
117
+ }
118
+
119
+ return issues;
120
+ }
121
+
122
+ // ============================================
123
+ // HELPERS
124
+ // ============================================
125
+
126
+ function toKebabCase(str) {
127
+ return str
128
+ .replace(/([A-Z])/g, '-$1')
129
+ .toLowerCase()
130
+ .replace(/^-/, '');
131
+ }
132
+
133
+ // ============================================
134
+ // PROJECT-LEVEL SCANNER
135
+ // ============================================
136
+
137
+ /**
138
+ * Validates all Next.js component files in a project directory.
139
+ * Wraps validateNextComponent for use in validation-runner.js.
140
+ *
141
+ * @param {string} projectPath - Root path of the project
142
+ * @param {Object} _options - Validator options (unused, for interface compatibility)
143
+ * @returns {Promise<{ errors: number, warnings: number, issues: Array }>}
144
+ */
145
+ export async function validateNextComponentFiles(projectPath, _options = {}) {
146
+ const result = { errors: 0, warnings: 0, issues: [] };
147
+
148
+ // Find all .tsx and .ts files under src/
149
+ const srcDir = join(projectPath, 'src');
150
+ const normalizedSrc = srcDir.replace(/\\/g, '/');
151
+ const pattern = normalizedSrc + '/**/*.{tsx,ts}';
152
+ const files = await glob(pattern, { ignore: ['**/node_modules/**', '**/.next/**'] });
153
+
154
+ for (const filePath of files) {
155
+ let content;
156
+ try {
157
+ content = readFileSync(filePath, 'utf8');
158
+ } catch {
159
+ continue;
160
+ }
161
+
162
+ // Make path relative for cleaner output
163
+ const normFilePath = filePath.replace(/\\/g, '/');
164
+ const relPath = normFilePath.replace(projectPath.replace(/\\/g, '/') + '/', '');
165
+ const fileIssues = validateNextComponent(content, relPath);
166
+
167
+ for (const issue of fileIssues) {
168
+ result.issues.push({
169
+ level: issue.type,
170
+ message: issue.message,
171
+ file: issue.file,
172
+ line: issue.line,
173
+ solution: issue.suggestion,
174
+ });
175
+ if (issue.type === 'error') result.errors++;
176
+ else if (issue.type === 'warning') result.warnings++;
177
+ }
178
+ }
179
+
180
+ return result;
181
+ }