@@ -99,8 +99,9 @@ pub struct CommentOnWorkItemConfig {
9999 #[ serde( default = "default_max" ) ]
100100 pub max : u32 ,
101101
102- /// Target scope — which work items can be commented on (required)
103- pub target : CommentTarget ,
102+ /// Target scope — which work items can be commented on.
103+ /// `None` means no target was configured; execution must reject this.
104+ pub target : Option < CommentTarget > ,
104105}
105106
106107fn default_max ( ) -> u32 {
@@ -111,7 +112,7 @@ impl Default for CommentOnWorkItemConfig {
111112 fn default ( ) -> Self {
112113 Self {
113114 max : default_max ( ) ,
114- target : CommentTarget :: StringTarget ( "*" . to_string ( ) ) ,
115+ target : None ,
115116 }
116117 }
117118}
@@ -191,10 +192,21 @@ impl Executor for CommentOnWorkItemResult {
191192 let config: CommentOnWorkItemConfig = ctx. get_tool_config ( "comment-on-work-item" ) ;
192193 debug ! ( "Target: {:?}, max: {}" , config. target, config. max) ;
193194
195+ let target = match & config. target {
196+ Some ( t) => t,
197+ None => {
198+ return Ok ( ExecutionResult :: failure (
199+ "comment-on-work-item target is not configured. \
200+ This is required to scope which work items the agent can comment on."
201+ . to_string ( ) ,
202+ ) ) ;
203+ }
204+ } ;
205+
194206 let client = reqwest:: Client :: new ( ) ;
195207
196208 // Validate work item ID against target policy
197- match config . target . allows_id ( self . work_item_id ) {
209+ match target. allows_id ( self . work_item_id ) {
198210 Some ( true ) => {
199211 debug ! (
200212 "Work item #{} allowed by target policy" ,
@@ -209,29 +221,30 @@ impl Executor for CommentOnWorkItemResult {
209221 }
210222 None => {
211223 // Area path validation — need to fetch the work item
212- if let Some ( prefix) = config. target . area_path_prefix ( ) {
213- debug ! (
214- "Validating area path for work item #{} against prefix '{}'" ,
215- self . work_item_id, prefix
216- ) ;
217- match get_work_item_area_path ( & client, org_url, project, token, self . work_item_id )
218- . await
219- {
220- Ok ( area_path) => {
221- if !area_path. starts_with ( prefix) {
222- return Ok ( ExecutionResult :: failure ( format ! (
223- "Work item #{} has area path '{}' which is not under allowed prefix '{}'" ,
224- self . work_item_id, area_path, prefix
225- ) ) ) ;
226- }
227- debug ! ( "Area path '{}' validated against '{}'" , area_path, prefix) ;
228- }
229- Err ( e) => {
224+ let prefix = target. area_path_prefix ( ) . expect (
225+ "allows_id returned None but area_path_prefix is also None; this is a bug" ,
226+ ) ;
227+ debug ! (
228+ "Validating area path for work item #{} against prefix '{}'" ,
229+ self . work_item_id, prefix
230+ ) ;
231+ match get_work_item_area_path ( & client, org_url, project, token, self . work_item_id )
232+ . await
233+ {
234+ Ok ( area_path) => {
235+ if !area_path. starts_with ( prefix) {
230236 return Ok ( ExecutionResult :: failure ( format ! (
231- "Failed to validate area path for work item #{}: {} " ,
232- self . work_item_id, e
237+ "Work item #{} has area path '{}' which is not under allowed prefix '{}' " ,
238+ self . work_item_id, area_path , prefix
233239 ) ) ) ;
234240 }
241+ debug ! ( "Area path '{}' validated against '{}'" , area_path, prefix) ;
242+ }
243+ Err ( e) => {
244+ return Ok ( ExecutionResult :: failure ( format ! (
245+ "Failed to validate area path for work item #{}: {}" ,
246+ self . work_item_id, e
247+ ) ) ) ;
235248 }
236249 }
237250 }
@@ -382,6 +395,7 @@ mod tests {
382395 fn test_config_defaults ( ) {
383396 let config = CommentOnWorkItemConfig :: default ( ) ;
384397 assert_eq ! ( config. max, 1 ) ;
398+ assert ! ( config. target. is_none( ) ) ;
385399 }
386400
387401 #[ test]
@@ -392,6 +406,7 @@ target: "*"
392406"# ;
393407 let config: CommentOnWorkItemConfig = serde_yaml:: from_str ( yaml) . unwrap ( ) ;
394408 assert_eq ! ( config. max, 5 ) ;
409+ assert ! ( config. target. is_some( ) ) ;
395410 }
396411
397412 #[ test]
@@ -401,8 +416,9 @@ max: 1
401416target: 12345
402417"# ;
403418 let config: CommentOnWorkItemConfig = serde_yaml:: from_str ( yaml) . unwrap ( ) ;
404- assert ! ( config. target. allows_id( 12345 ) == Some ( true ) ) ;
405- assert ! ( config. target. allows_id( 99999 ) == Some ( false ) ) ;
419+ let target = config. target . unwrap ( ) ;
420+ assert ! ( target. allows_id( 12345 ) == Some ( true ) ) ;
421+ assert ! ( target. allows_id( 99999 ) == Some ( false ) ) ;
406422 }
407423
408424 #[ test]
@@ -415,9 +431,10 @@ target:
415431 - 300
416432"# ;
417433 let config: CommentOnWorkItemConfig = serde_yaml:: from_str ( yaml) . unwrap ( ) ;
418- assert ! ( config. target. allows_id( 100 ) == Some ( true ) ) ;
419- assert ! ( config. target. allows_id( 200 ) == Some ( true ) ) ;
420- assert ! ( config. target. allows_id( 999 ) == Some ( false ) ) ;
434+ let target = config. target . unwrap ( ) ;
435+ assert ! ( target. allows_id( 100 ) == Some ( true ) ) ;
436+ assert ! ( target. allows_id( 200 ) == Some ( true ) ) ;
437+ assert ! ( target. allows_id( 999 ) == Some ( false ) ) ;
421438 }
422439
423440 #[ test]
@@ -426,8 +443,9 @@ target:
426443target: "*"
427444"# ;
428445 let config: CommentOnWorkItemConfig = serde_yaml:: from_str ( yaml) . unwrap ( ) ;
429- assert ! ( config. target. allows_id( 1 ) == Some ( true ) ) ;
430- assert ! ( config. target. allows_id( 99999 ) == Some ( true ) ) ;
446+ let target = config. target . unwrap ( ) ;
447+ assert ! ( target. allows_id( 1 ) == Some ( true ) ) ;
448+ assert ! ( target. allows_id( 99999 ) == Some ( true ) ) ;
431449 }
432450
433451 #[ test]
@@ -436,8 +454,18 @@ target: "*"
436454target: "4x4\\QED"
437455"# ;
438456 let config: CommentOnWorkItemConfig = serde_yaml:: from_str ( yaml) . unwrap ( ) ;
439- assert ! ( config. target. allows_id( 1 ) . is_none( ) ) ;
440- assert_eq ! ( config. target. area_path_prefix( ) , Some ( "4x4\\ QED" ) ) ;
457+ let target = config. target . unwrap ( ) ;
458+ assert ! ( target. allows_id( 1 ) . is_none( ) ) ;
459+ assert_eq ! ( target. area_path_prefix( ) , Some ( "4x4\\ QED" ) ) ;
460+ }
461+
462+ #[ test]
463+ fn test_config_missing_target_defaults_to_none ( ) {
464+ let yaml = r#"
465+ max: 3
466+ "# ;
467+ let config: CommentOnWorkItemConfig = serde_yaml:: from_str ( yaml) . unwrap ( ) ;
468+ assert ! ( config. target. is_none( ) ) ;
441469 }
442470
443471 #[ test]
0 commit comments