char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
c5dd844
main
1import { diffArrays } from "diff" ; 2 3type Hunk = { start :number ; end :number ; lines :string []; mine :boolean }; 4 5/** Base lines `start` to `end` replaced by `lines`, for each run where `next` differs from `base`. */ 6function hunks ( base :string [], next :string [], mine :boolean ) :Hunk [] { 7const found :Hunk [] = []; 8let at = 0 ; 9let current :Hunk | null = null ; 10for ( const part of diffArrays ( base , next )) { 11if ( ! part . added && ! part . removed ) { 12if ( current ) found . push ( current ); 13current = null ; 14at += part . count ! ; 15continue ; 16} 17current ??= { start :at , end :at , lines :[], mine}; 18if ( part . removed ) current . end = at += part . count ! ; 19else current . lines . push ( ...part . value ); 20} 21if ( current ) found . push ( current ); 22return found ; 23} 24 25/** 26* Both sides' changes to `base` as git merges them: by lines, refusing (with `false`) where they touch or overlap 27* unless they make the same change. 28*/ 29export function merge3 ( base :string , mine :string , theirs :string ) :string | false { 30const lines = ( text :string ) => text . split ( / (?<=\n) / ). filter (( line ) => line !== "" ); 31const original = lines ( base ); 32const all = [ 33 ...hunks ( original , lines ( mine ), true ), 34 ...hunks ( original , lines ( theirs ), false ), 35]. sort (( a , b ) => a . start - b . start || a . end - b . end ); 36const out :string [] = []; 37let at = 0 ; 38for ( let i = 0 ; i < all . length ;) { 39const group = [ all [ i ++ ]]; 40let end = group [ 0 ]. end ; 41while ( i < all . length && all [ i ]. start <= end ) { 42end = Math . max ( end , all [ i ]. end ); 43group . push ( all [ i ++ ]); 44} 45const start = group [ 0 ]. start ; 46const side = ( mine :boolean ) => { 47const result :string [] = []; 48let from = start ; 49for ( const hunk of group . filter (( hunk ) => hunk . mine === mine )) { 50result . push ( ...original . slice ( from , hunk . start ), ...hunk . lines ); 51from = hunk . end ; 52} 53return [ ...result , ...original . slice ( from , end )]; 54}; 55const ours = side ( true ); 56if ( group . some (( hunk ) => ! hunk . mine ) && group . some (( hunk ) => hunk . mine )) { 57if ( ours . join ( "" ) !== side ( false ). join ( "" )) return false ; 58} 59out . push ( ...original . slice ( at , start ), ...( group . some (( h ) => h . mine ) ?ours :side ( false ))); 60at = end ; 61} 62return [ ...out , ...original . slice ( at )]. join ( "" ); 63}