Ваш шаблон не делает то, что вы хотите.Давайте разберем его на части:
^(\w+([\s-]\w+)?)+$
Соответствует строкам, которые состоят только из одной или нескольких последовательностей шаблона:
\w+([\s-]\w+)?
..., который представляет собой последовательность символов слова с последующимпо выбору - еще одна последовательность символов слова, разделенных одним пробелом или тире.
Другими словами, ваш шаблон ищет строки вроде:
xxx-xxxyyy-yyyzzz zzz
... но вы намереваетесьнапишите шаблон, который найдет:
xxx-xxxxxx-xxxxxx yyy
В ваших примерах это соответствует:
Counter-terrorism forum-category-a
... но это интерпретируется как следующая последовательность:
(Counter(-terroris)) (m( foru)) (m(-categor) (y(-a))
Как вы можете видеть, шаблон действительно не нашел слова, которые вы ищете.
Этот пример не соответствует:
forum-category-a Preventing Violent
...поскольку шаблон не может образовывать группы из «символов слова, пробела или тире, символов слова», когда он встречает один символ слова, за которым следует пробел или тире:
(forum(-categor)) (y(-a)) <Mismatch: Found " " but expected "\w">
Если вы добавите еще один символ в"форум-категория-а", скажем "форум-категория-топор", это будет соответствоватьснова, так как он может разделиться на «топор»:
(forum(-categor)) (y(-a)) (x( Preventin)) (g( Violent))
Что вас действительно интересует, так это шаблон типа
^(\w+(-\w+)*)(\s\w+(-\w+)*)*$
... который найдетпоследовательность слов, которые могут содержать тире, разделенные пробелами:
(forum(-category)(-a)) ( Preventing) ( Violent)
Кстати, я проверил это с помощью скрипта Python и пытаясь сопоставить ваш шаблон с примером строки "International«Форум исследований-публикаций-форум-категория-b форум-категория-а», механизм регулярных выражений, похоже, запутался в бесконечном цикле ...
import re
expr = re.compile(r'^(\w+([\s-]\w+)?)+$')
expr.match('International-Research-and-Publications forum-category-b forum-category-a')