@@ -8,6 +8,36 @@ import { cleanupBranches, getRepoRoot, removeWorktree } from "../utils/git.js";
88
99const exec = promisify ( execFile ) ;
1010
11+ export interface ConflictInfo {
12+ appliedFiles : string [ ] ;
13+ conflictedFiles : string [ ] ;
14+ }
15+
16+ /** Parse git apply --3way stderr to extract applied/conflicted file lists. */
17+ export function parseApplyConflicts ( stderr : string ) : ConflictInfo {
18+ const conflictedFiles : string [ ] = [ ] ;
19+ const appliedFiles : string [ ] = [ ] ;
20+ for ( const line of stderr . split ( "\n" ) ) {
21+ const patchMatch = line . match ( / A p p l i e d p a t c h t o ' ( [ ^ ' ] + ) ' / ) ;
22+ if ( patchMatch ) {
23+ const file = patchMatch [ 1 ] ;
24+ if ( line . includes ( "with conflicts" ) ) {
25+ if ( ! conflictedFiles . includes ( file ) ) {
26+ conflictedFiles . push ( file ) ;
27+ }
28+ } else if ( ! appliedFiles . includes ( file ) ) {
29+ appliedFiles . push ( file ) ;
30+ }
31+ continue ;
32+ }
33+ const failMatch = line . match ( / ^ e r r o r : p a t c h f a i l e d : ( [ ^ : ] + ) / ) ;
34+ if ( failMatch && ! conflictedFiles . includes ( failMatch [ 1 ] ) ) {
35+ conflictedFiles . push ( failMatch [ 1 ] ) ;
36+ }
37+ }
38+ return { appliedFiles, conflictedFiles } ;
39+ }
40+
1141export interface ApplyOptions {
1242 agent ?: number ;
1343 preview ?: boolean ;
@@ -112,10 +142,56 @@ export async function apply(opts: ApplyOptions): Promise<void> {
112142
113143 console . log ( " Changes applied successfully." ) ;
114144 } catch ( err : unknown ) {
115- const e = err as { stderr ?: string } ;
116- console . error ( " Failed to apply diff. There may be conflicts." ) ;
117- if ( e . stderr ) console . error ( ` ${ e . stderr } ` ) ;
118- console . error ( ` You can manually inspect the diff at: ${ agent . worktree } ` ) ;
145+ const e = err as { stderr ?: string ; stdout ?: string } ;
146+ const stderr = e . stderr ?? "" ;
147+ const { appliedFiles, conflictedFiles } = parseApplyConflicts ( stderr ) ;
148+
149+ console . error ( ) ;
150+ console . error ( pc . bold ( pc . red ( " Apply failed — conflicts detected" ) ) ) ;
151+ console . error ( pc . dim ( " " + "─" . repeat ( 58 ) ) ) ;
152+ console . error ( ) ;
153+
154+ if ( appliedFiles . length > 0 ) {
155+ console . error ( pc . green ( " Applied cleanly:" ) ) ;
156+ for ( const f of appliedFiles ) {
157+ console . error ( pc . green ( ` ✓ ${ f } ` ) ) ;
158+ }
159+ console . error ( ) ;
160+ }
161+
162+ if ( conflictedFiles . length > 0 ) {
163+ console . error ( pc . red ( " Conflicted:" ) ) ;
164+ for ( const f of conflictedFiles ) {
165+ console . error ( pc . red ( ` ✗ ${ f } ` ) ) ;
166+ }
167+ console . error ( ) ;
168+ }
169+
170+ // If we couldn't parse any files, show the raw stderr
171+ if ( conflictedFiles . length === 0 && appliedFiles . length === 0 && stderr . trim ( ) ) {
172+ console . error ( pc . dim ( ` ${ stderr . trim ( ) } ` ) ) ;
173+ console . error ( ) ;
174+ }
175+
176+ const otherAgents = result . agents
177+ . filter ( ( a ) => a . id !== agentId && a . status === "success" && a . diff )
178+ . map ( ( a ) => `#${ a . id } ` ) ;
179+
180+ console . error ( " Next steps:" ) ;
181+ if ( otherAgents . length > 0 ) {
182+ console . error (
183+ ` • Try a different agent: thinktank apply --agent ${ otherAgents [ 0 ] . slice ( 1 ) } ` ,
184+ ) ;
185+ }
186+ console . error ( ` • Inspect the diff first: thinktank apply --preview --agent ${ agentId } ` ) ;
187+ console . error ( " • Manually merge from the worktree:" ) ;
188+ console . error ( pc . dim ( ` ${ agent . worktree } ` ) ) ;
189+ if ( conflictedFiles . length > 0 && appliedFiles . length > 0 ) {
190+ console . error ( " • Resolve conflict markers in your working tree:" ) ;
191+ console . error ( pc . dim ( " git diff # review conflict markers" ) ) ;
192+ console . error ( pc . dim ( " git checkout --conflict=merge <file> # re-create markers" ) ) ;
193+ }
194+ console . error ( ) ;
119195 process . exit ( 1 ) ;
120196 }
121197
0 commit comments