11import { describe , it , expect , beforeEach , vi } from 'vitest' ;
22import { KeywordService } from './keyword.service' ;
33import type { KeywordModesConfig } from './keyword.types' ;
4+ import type { PrimaryAgentResolver } from './primary-agent-resolver' ;
45
56/**
67 * NOTE: Korean/Japanese/Chinese/Spanish test inputs (e.g., '계획 인증 기능 설계')
@@ -852,7 +853,7 @@ describe('KeywordService', () => {
852853 mockLoadConfig ,
853854 mockLoadRule ,
854855 mockLoadAgentInfo ,
855- mockResolver as any ,
856+ mockResolver as unknown as PrimaryAgentResolver ,
856857 ) ;
857858
858859 const result = await serviceWithResolver . parseMode ( 'PLAN design API' ) ;
@@ -878,7 +879,7 @@ describe('KeywordService', () => {
878879 mockLoadConfig ,
879880 mockLoadRule ,
880881 mockLoadAgentInfo ,
881- mockResolver as any ,
882+ mockResolver as unknown as PrimaryAgentResolver ,
882883 ) ;
883884
884885 const result = await serviceWithResolver . parseMode ( 'PLAN build UI' ) ;
@@ -903,7 +904,7 @@ describe('KeywordService', () => {
903904 mockLoadConfig ,
904905 mockLoadRule ,
905906 mockLoadAgentInfo ,
906- mockResolver as any ,
907+ mockResolver as unknown as PrimaryAgentResolver ,
907908 ) ;
908909
909910 const result = await serviceWithResolver . parseMode ( 'ACT implement' ) ;
@@ -925,7 +926,7 @@ describe('KeywordService', () => {
925926 mockLoadConfig ,
926927 mockLoadRule ,
927928 mockLoadAgentInfo ,
928- mockResolver as any ,
929+ mockResolver as unknown as PrimaryAgentResolver ,
929930 ) ;
930931
931932 const result = await serviceWithResolver . parseMode ( 'EVAL review' ) ;
@@ -952,7 +953,7 @@ describe('KeywordService', () => {
952953 mockLoadConfig ,
953954 mockLoadRule ,
954955 mockLoadAgentInfo ,
955- mockResolver as any ,
956+ mockResolver as unknown as PrimaryAgentResolver ,
956957 ) ;
957958
958959 const result = await serviceWithResolver . parseMode ( 'PLAN design' ) ;
@@ -1031,4 +1032,177 @@ describe('KeywordService', () => {
10311032 expect ( result . activation_message ?. activations [ 0 ] . tier ) . toBe ( 'primary' ) ;
10321033 } ) ;
10331034 } ) ;
1035+
1036+ describe ( 'recommendedActAgent parameter (ACT mode agent override)' , ( ) => {
1037+ it ( 'uses recommendedActAgent when provided in ACT mode' , async ( ) => {
1038+ const mockResolver = {
1039+ resolve : vi . fn ( ) . mockResolvedValue ( {
1040+ agentName : 'backend-developer' ,
1041+ source : 'config' ,
1042+ confidence : 1.0 ,
1043+ reason : 'Using recommended agent from PLAN mode: backend-developer' ,
1044+ } ) ,
1045+ } ;
1046+ const serviceWithResolver = new KeywordService (
1047+ mockLoadConfig ,
1048+ mockLoadRule ,
1049+ mockLoadAgentInfo ,
1050+ mockResolver as unknown as PrimaryAgentResolver ,
1051+ ) ;
1052+
1053+ const result = await serviceWithResolver . parseMode (
1054+ 'ACT implement login API' ,
1055+ { recommendedActAgent : 'backend-developer' } ,
1056+ ) ;
1057+
1058+ expect ( result . mode ) . toBe ( 'ACT' ) ;
1059+ expect ( result . delegates_to ) . toBe ( 'backend-developer' ) ;
1060+ expect ( mockResolver . resolve ) . toHaveBeenCalledWith (
1061+ 'ACT' ,
1062+ 'implement login API' ,
1063+ undefined ,
1064+ 'backend-developer' ,
1065+ ) ;
1066+ } ) ;
1067+
1068+ it ( 'ignores recommendedActAgent in PLAN mode' , async ( ) => {
1069+ const mockResolver = {
1070+ resolve : vi . fn ( ) . mockResolvedValue ( {
1071+ agentName : 'solution-architect' ,
1072+ source : 'default' ,
1073+ confidence : 1.0 ,
1074+ reason : 'PLAN mode default' ,
1075+ } ) ,
1076+ } ;
1077+ const serviceWithResolver = new KeywordService (
1078+ mockLoadConfig ,
1079+ mockLoadRule ,
1080+ mockLoadAgentInfo ,
1081+ mockResolver as unknown as PrimaryAgentResolver ,
1082+ ) ;
1083+
1084+ const result = await serviceWithResolver . parseMode ( 'PLAN design API' , {
1085+ recommendedActAgent : 'backend-developer' ,
1086+ } ) ;
1087+
1088+ expect ( result . mode ) . toBe ( 'PLAN' ) ;
1089+ expect ( result . delegates_to ) . toBe ( 'solution-architect' ) ;
1090+ // Should not pass recommendedActAgent for PLAN mode
1091+ expect ( mockResolver . resolve ) . toHaveBeenCalledWith (
1092+ 'PLAN' ,
1093+ 'design API' ,
1094+ undefined ,
1095+ undefined ,
1096+ ) ;
1097+ } ) ;
1098+
1099+ it ( 'ignores recommendedActAgent in EVAL mode' , async ( ) => {
1100+ const mockResolver = {
1101+ resolve : vi . fn ( ) . mockResolvedValue ( {
1102+ agentName : 'code-reviewer' ,
1103+ source : 'default' ,
1104+ confidence : 1.0 ,
1105+ reason : 'EVAL always uses code-reviewer' ,
1106+ } ) ,
1107+ } ;
1108+ const serviceWithResolver = new KeywordService (
1109+ mockLoadConfig ,
1110+ mockLoadRule ,
1111+ mockLoadAgentInfo ,
1112+ mockResolver as unknown as PrimaryAgentResolver ,
1113+ ) ;
1114+
1115+ const result = await serviceWithResolver . parseMode ( 'EVAL review code' , {
1116+ recommendedActAgent : 'backend-developer' ,
1117+ } ) ;
1118+
1119+ expect ( result . mode ) . toBe ( 'EVAL' ) ;
1120+ expect ( result . delegates_to ) . toBe ( 'code-reviewer' ) ;
1121+ } ) ;
1122+
1123+ it ( 'falls back to default resolution if no recommendedActAgent provided' , async ( ) => {
1124+ const mockResolver = {
1125+ resolve : vi . fn ( ) . mockResolvedValue ( {
1126+ agentName : 'frontend-developer' ,
1127+ source : 'default' ,
1128+ confidence : 1.0 ,
1129+ reason : 'ACT mode default: frontend-developer' ,
1130+ } ) ,
1131+ } ;
1132+ const serviceWithResolver = new KeywordService (
1133+ mockLoadConfig ,
1134+ mockLoadRule ,
1135+ mockLoadAgentInfo ,
1136+ mockResolver as unknown as PrimaryAgentResolver ,
1137+ ) ;
1138+
1139+ const result = await serviceWithResolver . parseMode (
1140+ 'ACT implement feature' ,
1141+ ) ;
1142+
1143+ expect ( result . delegates_to ) . toBe ( 'frontend-developer' ) ;
1144+ expect ( mockResolver . resolve ) . toHaveBeenCalledWith (
1145+ 'ACT' ,
1146+ 'implement feature' ,
1147+ undefined ,
1148+ undefined ,
1149+ ) ;
1150+ } ) ;
1151+
1152+ it ( 'treats empty string recommendedActAgent as undefined' , async ( ) => {
1153+ const mockResolver = {
1154+ resolve : vi . fn ( ) . mockResolvedValue ( {
1155+ agentName : 'frontend-developer' ,
1156+ source : 'default' ,
1157+ confidence : 1.0 ,
1158+ reason : 'ACT mode default: frontend-developer' ,
1159+ } ) ,
1160+ } ;
1161+ const serviceWithResolver = new KeywordService (
1162+ mockLoadConfig ,
1163+ mockLoadRule ,
1164+ mockLoadAgentInfo ,
1165+ mockResolver as unknown as PrimaryAgentResolver ,
1166+ ) ;
1167+
1168+ // Empty string should be treated as undefined
1169+ const result = await serviceWithResolver . parseMode ( 'ACT implement' , {
1170+ recommendedActAgent : '' ,
1171+ } ) ;
1172+
1173+ expect ( result . delegates_to ) . toBe ( 'frontend-developer' ) ;
1174+ // Should be called with undefined, not empty string
1175+ expect ( mockResolver . resolve ) . toHaveBeenCalledWith (
1176+ 'ACT' ,
1177+ 'implement' ,
1178+ undefined ,
1179+ '' , // Empty string is passed through (MCP layer handles trim)
1180+ ) ;
1181+ } ) ;
1182+
1183+ it ( 'treats whitespace-only recommendedActAgent as undefined' , async ( ) => {
1184+ const mockResolver = {
1185+ resolve : vi . fn ( ) . mockResolvedValue ( {
1186+ agentName : 'frontend-developer' ,
1187+ source : 'default' ,
1188+ confidence : 1.0 ,
1189+ reason : 'ACT mode default: frontend-developer' ,
1190+ } ) ,
1191+ } ;
1192+ const serviceWithResolver = new KeywordService (
1193+ mockLoadConfig ,
1194+ mockLoadRule ,
1195+ mockLoadAgentInfo ,
1196+ mockResolver as unknown as PrimaryAgentResolver ,
1197+ ) ;
1198+
1199+ // Whitespace should be treated as undefined at MCP layer
1200+ // KeywordService passes through, validation is in MCP handler
1201+ const result = await serviceWithResolver . parseMode ( 'ACT implement' , {
1202+ recommendedActAgent : ' ' ,
1203+ } ) ;
1204+
1205+ expect ( result . delegates_to ) . toBe ( 'frontend-developer' ) ;
1206+ } ) ;
1207+ } ) ;
10341208} ) ;
0 commit comments