@@ -2411,40 +2411,26 @@ class MCPToolServer {
24112411 }
24122412 }
24132413
2414- // Param inference: fix "." or "" filePath when user message references a real file
2414+ // Param inference: fix "." or "" filePath for file operations when user message references a real file
2415+ // NOTE: list_directory is excluded — ".", "./", and "" all resolve correctly to the project root
2416+ // in _listDirectory via path.join(projectPath, "."). Keyword extraction from user messages
2417+ // was removed because it's a classifier pattern that mis-triggers on common words like "build".
24152418 if ( options . userMessage ) {
24162419 for ( const call of toolCalls ) {
24172420 if ( ! call || typeof call . tool !== 'string' ) continue ;
24182421 const fp = call . params ?. filePath ?? call . params ?. path ?? call . params ?. file_path ?? '' ;
24192422 const isFileOp = [ 'read_file' , 'write_file' , 'edit_file' ] . includes ( call . tool ) ;
2420- const isListOp = call . tool === 'list_directory' ;
24212423 const pathIsBad = fp === '.' || fp === './' || fp === '' || fp === '..' ;
24222424
2423- if ( pathIsBad && ( isFileOp || isListOp ) ) {
2425+ if ( pathIsBad && isFileOp ) {
24242426 const msg = options . userMessage ;
2425- if ( isFileOp ) {
2426- const fileMatch = msg . match ( / \b ( [ \w . - ] + \. (?: j s o n | j s | t s | t s x | j s x | m d | h t m l | c s s | y m l | y a m l | t o m l | p y | s h | b a t | t x t | x m l | e n v | c f g | c o n f | i n i | l o g | c s v ) ) \b / i) ;
2427- if ( fileMatch ) {
2428- const inferred = fileMatch [ 1 ] ;
2429- console . log ( `[MCP] Param inference: "${ call . tool } " filePath "${ fp } " → "${ inferred } " (from user message)` ) ;
2430- call . params = { ...call . params , filePath : inferred } ;
2431- if ( call . params . path ) delete call . params . path ;
2432- if ( call . params . file_path ) delete call . params . file_path ;
2433- }
2434- } else if ( isListOp ) {
2435- const dirMatch = msg . match ( / \b ( s r c | m a i n | s c r i p t s | t e s t s | c o m p o n e n t s | s e r v i c e s | u t i l s | c o n f i g | p u b l i c | b u i l d | d i s t | o u t p u t | l i b | a s s e t s ) \b / i) ;
2436- if ( dirMatch ) {
2437- const inferred = dirMatch [ 1 ] . toLowerCase ( ) ;
2438- const dp = call . params ?. dirPath ?? call . params ?. path ?? call . params ?. directory ?? '.' ;
2439- if ( dp === '.' || dp === '' || dp === './' ) {
2440- console . log ( `[MCP] Param inference: "list_directory" path "${ dp } " → "${ inferred } " (from user message)` ) ;
2441- call . params = { ...call . params } ;
2442- if ( call . params . dirPath != null ) call . params . dirPath = inferred ;
2443- else if ( call . params . directory != null ) call . params . directory = inferred ;
2444- else if ( call . params . path != null ) call . params . path = inferred ;
2445- else call . params . dirPath = inferred ;
2446- }
2447- }
2427+ const fileMatch = msg . match ( / \b ( [ \w . - ] + \. (?: j s o n | j s | t s | t s x | j s x | m d | h t m l | c s s | y m l | y a m l | t o m l | p y | s h | b a t | t x t | x m l | e n v | c f g | c o n f | i n i | l o g | c s v ) ) \b / i) ;
2428+ if ( fileMatch ) {
2429+ const inferred = fileMatch [ 1 ] ;
2430+ console . log ( `[MCP] Param inference: "${ call . tool } " filePath "${ fp } " → "${ inferred } " (from user message)` ) ;
2431+ call . params = { ...call . params , filePath : inferred } ;
2432+ if ( call . params . path ) delete call . params . path ;
2433+ if ( call . params . file_path ) delete call . params . file_path ;
24482434 }
24492435 }
24502436 }
@@ -2532,7 +2518,37 @@ class MCPToolServer {
25322518 const wfLimit = ( options . continuationCount || 0 ) > 0 ? 1 : 2 ;
25332519 if ( wfPath && options . writeFileHistory [ wfPath ] && options . writeFileHistory [ wfPath ] . count >= wfLimit ) {
25342520 console . log ( `[MCP] Write dedup: blocking ${ call . tool } to "${ wfPath } " (already written ${ options . writeFileHistory [ wfPath ] . count } x)` ) ;
2535- results . push ( { tool : call . tool , params : call . params , result : { success : false , error : `BLOCKED: "${ wfPath } " has already been written ${ options . writeFileHistory [ wfPath ] . count } times this conversation. Use append_to_file or edit_file instead.` } } ) ;
2521+ // Auto-convert: extract new content and append instead of blocking outright
2522+ let autoConverted = false ;
2523+ const newContent = call . params ?. content || '' ;
2524+ if ( newContent . length > 50 ) {
2525+ try {
2526+ const _fs = require ( 'fs' ) , _path = require ( 'path' ) ;
2527+ const fullPath = _path . resolve ( this . projectPath || '.' , wfPath ) ;
2528+ const existing = _fs . existsSync ( fullPath ) ? _fs . readFileSync ( fullPath , 'utf-8' ) : '' ;
2529+ if ( existing . length > 0 ) {
2530+ const el = existing . split ( '\n' ) , nl = newContent . split ( '\n' ) ;
2531+ let m = 0 ;
2532+ for ( let i = 0 ; i < Math . min ( el . length , nl . length ) ; i ++ ) { if ( el [ i ] . trimEnd ( ) === nl [ i ] . trimEnd ( ) ) m ++ ; else break ; }
2533+ if ( m > 5 && nl . length > el . length ) {
2534+ const suffix = nl . slice ( m ) . join ( '\n' ) ;
2535+ if ( suffix . trim ( ) . length > 20 ) {
2536+ console . log ( `[MCP] Write dedup auto-convert: "${ wfPath } " (${ m } /${ el . length } overlap, appending ${ nl . length - m } new lines)` ) ;
2537+ const ar = await this . executeTool ( 'append_to_file' , { filePath : wfPath , content : suffix } ) ;
2538+ results . push ( { tool : 'append_to_file' , params : { filePath : wfPath , content : '...(auto-converted)' } , result : ar } ) ;
2539+ autoConverted = true ;
2540+ }
2541+ }
2542+ if ( ! autoConverted && m > el . length * 0.5 ) {
2543+ results . push ( { tool : call . tool , params : call . params , result : { success : true , message : `File "${ wfPath } " already written (${ el . length } lines). Move on to next task.` } } ) ;
2544+ autoConverted = true ;
2545+ }
2546+ }
2547+ } catch ( e ) { console . warn ( `[MCP] Write dedup auto-convert failed: ${ e . message } ` ) ; }
2548+ }
2549+ if ( ! autoConverted ) {
2550+ results . push ( { tool : call . tool , params : call . params , result : { success : false , error : `BLOCKED: "${ wfPath } " already written ${ options . writeFileHistory [ wfPath ] . count } times. Use append_to_file or edit_file instead.` } } ) ;
2551+ }
25362552 continue ;
25372553 }
25382554 }
@@ -2605,7 +2621,37 @@ class MCPToolServer {
26052621 const wfLimit = ( options . continuationCount || 0 ) > 0 ? 1 : 2 ;
26062622 if ( wfPath && options . writeFileHistory [ wfPath ] && options . writeFileHistory [ wfPath ] . count >= wfLimit ) {
26072623 console . log ( `[MCP] Write dedup: blocking ${ call . tool } to "${ wfPath } " (already written ${ options . writeFileHistory [ wfPath ] . count } x)` ) ;
2608- results . push ( { tool : call . tool , params : call . params , result : { success : false , error : `BLOCKED: "${ wfPath } " has already been written ${ options . writeFileHistory [ wfPath ] . count } times this conversation. Use append_to_file or edit_file instead.` } } ) ;
2624+ // Auto-convert: extract new content and append instead of blocking outright
2625+ let autoConverted = false ;
2626+ const newContent = call . params ?. content || '' ;
2627+ if ( newContent . length > 50 ) {
2628+ try {
2629+ const _fs = require ( 'fs' ) , _path = require ( 'path' ) ;
2630+ const fullPath = _path . resolve ( this . projectPath || '.' , wfPath ) ;
2631+ const existing = _fs . existsSync ( fullPath ) ? _fs . readFileSync ( fullPath , 'utf-8' ) : '' ;
2632+ if ( existing . length > 0 ) {
2633+ const el = existing . split ( '\n' ) , nl = newContent . split ( '\n' ) ;
2634+ let m = 0 ;
2635+ for ( let i = 0 ; i < Math . min ( el . length , nl . length ) ; i ++ ) { if ( el [ i ] . trimEnd ( ) === nl [ i ] . trimEnd ( ) ) m ++ ; else break ; }
2636+ if ( m > 5 && nl . length > el . length ) {
2637+ const suffix = nl . slice ( m ) . join ( '\n' ) ;
2638+ if ( suffix . trim ( ) . length > 20 ) {
2639+ console . log ( `[MCP] Write dedup auto-convert: "${ wfPath } " (${ m } /${ el . length } overlap, appending ${ nl . length - m } new lines)` ) ;
2640+ const ar = await this . executeTool ( 'append_to_file' , { filePath : wfPath , content : suffix } ) ;
2641+ results . push ( { tool : 'append_to_file' , params : { filePath : wfPath , content : '...(auto-converted)' } , result : ar } ) ;
2642+ autoConverted = true ;
2643+ }
2644+ }
2645+ if ( ! autoConverted && m > el . length * 0.5 ) {
2646+ results . push ( { tool : call . tool , params : call . params , result : { success : true , message : `File "${ wfPath } " already written (${ el . length } lines). Move on to next task.` } } ) ;
2647+ autoConverted = true ;
2648+ }
2649+ }
2650+ } catch ( e ) { console . warn ( `[MCP] Write dedup auto-convert failed: ${ e . message } ` ) ; }
2651+ }
2652+ if ( ! autoConverted ) {
2653+ results . push ( { tool : call . tool , params : call . params , result : { success : false , error : `BLOCKED: "${ wfPath } " already written ${ options . writeFileHistory [ wfPath ] . count } times. Use append_to_file or edit_file instead.` } } ) ;
2654+ }
26092655 continue ;
26102656 }
26112657 }
0 commit comments