@@ -281,7 +281,7 @@ describe('HypothesisExecutor', () => {
281281
282282 expect ( result . rounds ) . toBe ( 1 ) ;
283283 expect ( result . findings ) . toHaveLength ( 1 ) ;
284- expect ( result . stopReason ) . toBeNull ( ) ;
284+ expect ( result . stopReason ) . toBe ( 'Strategy concluded' ) ;
285285 expect ( services . circuitBreaker . canExecute ) . toHaveBeenCalled ( ) ;
286286 expect ( services . circuitBreaker . recordIteration ) . toHaveBeenCalledWith ( 'hypothesis_loop' ) ;
287287 expect ( services . messageBus . dispatchTasksParallel ) . toHaveBeenCalled ( ) ;
@@ -457,6 +457,47 @@ describe('HypothesisExecutor', () => {
457457 expect ( services . messageBus . updateHypothesis ) . toHaveBeenCalled ( ) ;
458458 } ) ;
459459
460+ it ( 'records a stop reason when the strategy planner concludes' , async ( ) => {
461+ const ctx = createMockExecutionContext ( ) ;
462+ strategyPlanner . planNextIteration . mockResolvedValue ( {
463+ strategy : 'conclude' ,
464+ confidence : 0.8 ,
465+ reasoning : 'Sufficient findings collected' ,
466+ } ) ;
467+
468+ const result = await executor . execute ( ctx , emitter ) ;
469+
470+ expect ( result . stopReason ) . toBe ( 'Strategy concluded' ) ;
471+ expect ( emittedUpdates ) . toEqual ( expect . arrayContaining ( [
472+ expect . objectContaining ( {
473+ type : 'progress' ,
474+ content : expect . objectContaining ( {
475+ phase : 'early_stop' ,
476+ reason : 'Strategy concluded' ,
477+ } ) ,
478+ } ) ,
479+ ] ) ) ;
480+ } ) ;
481+
482+ it ( 'does not replace an existing focused time range with the global trace range during deep_dive' , async ( ) => {
483+ const ctx = createMockExecutionContext ( {
484+ options : {
485+ traceProcessorService : { } ,
486+ packageName : 'com.example.app' ,
487+ timeRange : { start : '0' , end : '9999' } ,
488+ } ,
489+ } ) ;
490+ ctx . sharedContext . focusedTimeRange = { start : '1000' , end : '2000' } ;
491+
492+ strategyPlanner . planNextIteration
493+ . mockResolvedValueOnce ( { strategy : 'deep_dive' , confidence : 0.6 , reasoning : 'Need deeper' , focusArea : 'cpu' } )
494+ . mockResolvedValueOnce ( { strategy : 'conclude' , confidence : 0.8 , reasoning : 'Done' } ) ;
495+
496+ await executor . execute ( ctx , emitter ) ;
497+
498+ expect ( ctx . sharedContext . focusedTimeRange ) . toEqual ( { start : '1000' , end : '2000' } ) ;
499+ } ) ;
500+
460501 it ( 'handles pivot strategy' , async ( ) => {
461502 const ctx = createMockExecutionContext ( ) ;
462503
@@ -599,8 +640,8 @@ describe('HypothesisExecutor', () => {
599640
600641 const result = await executor . execute ( ctx , emitter ) ;
601642
602- // Should complete without early stop due to noProgress (we reset the counter)
603- expect ( result . stopReason ) . toBeNull ( ) ;
643+ // Should complete by strategy conclusion, not no-progress early stop.
644+ expect ( result . stopReason ) . toBe ( 'Strategy concluded' ) ;
604645 expect ( result . rounds ) . toBe ( 4 ) ;
605646 // Verify we accumulated findings from both rounds where we had findings
606647 expect ( result . findings . length ) . toBeGreaterThanOrEqual ( 2 ) ;
@@ -626,7 +667,7 @@ describe('HypothesisExecutor', () => {
626667
627668 const result = await executor . execute ( ctx , emitter ) ;
628669
629- expect ( result . stopReason ) . toBeNull ( ) ;
670+ expect ( result . stopReason ) . toBe ( 'Strategy concluded' ) ;
630671 expect ( result . rounds ) . toBe ( 1 ) ;
631672 expect ( emittedUpdates . some (
632673 update => update . type === 'progress' && String ( update . content . phase || '' ) . includes ( 'intervention' )
0 commit comments