@@ -519,24 +519,172 @@ describe('results repo write path', () => {
519519 ) ;
520520 } , 20000 ) ;
521521
522- it ( 'blocks dirty non-results changes with git summaries instead of resetting ' , async ( ) => {
522+ it ( 'ignores dirty non-results files when reporting project sync status ' , async ( ) => {
523523 const { remoteDir } = initializeRemoteRepo ( rootDir ) ;
524524 const cloneDir = path . join ( rootDir , 'results-clone' ) ;
525525 const config = createResultsConfig ( remoteDir , cloneDir ) ;
526526
527527 await ensureResultsRepoClone ( config ) ;
528528 writeFileSync ( path . join ( cloneDir , 'NOTES.md' ) , 'do not auto-push me\n' ) ;
529529
530+ await expect ( getResultsRepoSyncStatus ( config ) ) . resolves . toMatchObject ( {
531+ sync_status : 'clean' ,
532+ dirty_paths : [ ] ,
533+ last_error : undefined ,
534+ } ) ;
535+
530536 const status = await syncResultsRepoForProject ( config ) ;
531537
532- expect ( status . sync_status ) . toBe ( 'dirty' ) ;
533- expect ( status . blocked ) . toBe ( true ) ;
534- expect ( status . block_reason ) . toContain ( 'non-results' ) ;
535- expect ( status . dirty_paths ) . toEqual ( [ 'NOTES.md' ] ) ;
538+ expect ( status . sync_status ) . toBe ( 'clean' ) ;
539+ expect ( status . blocked ) . toBe ( false ) ;
540+ expect ( status . dirty_paths ) . toEqual ( [ ] ) ;
536541 expect ( status . git_status ) . toContain ( 'NOTES.md' ) ;
537542 expect ( readFileSync ( path . join ( cloneDir , 'NOTES.md' ) , 'utf8' ) ) . toBe ( 'do not auto-push me\n' ) ;
538543 } , 20000 ) ;
539544
545+ it ( 'commits and pushes dirty result artifacts while leaving unrelated files untracked' , async ( ) => {
546+ const { remoteDir } = initializeRemoteRepo ( rootDir ) ;
547+ const cloneDir = path . join ( rootDir , 'results-clone' ) ;
548+ const config = createResultsConfig ( remoteDir , cloneDir ) ;
549+
550+ await ensureResultsRepoClone ( config ) ;
551+ git ( 'git config user.email "test@example.com"' , cloneDir ) ;
552+ git ( 'git config user.name "Test User"' , cloneDir ) ;
553+ writeFileSync ( path . join ( cloneDir , 'package.json' ) , '{"dependencies":{"agentv":"next"}}\n' ) ;
554+
555+ const runTimestamp = '2026-05-24T11-00-00-000Z' ;
556+ const runDir = path . join ( cloneDir , '.agentv' , 'results' , 'runs' , 'safe-run' , runTimestamp ) ;
557+ writeRunArtifacts ( runDir , 'safe-run' , '2026-05-24T11:00:00.000Z' ) ;
558+
559+ const status = await syncResultsRepoForProject ( config ) ;
560+
561+ expect ( status ) . toMatchObject ( {
562+ sync_status : 'clean' ,
563+ commit_created : true ,
564+ push_performed : true ,
565+ blocked : false ,
566+ } ) ;
567+ expect ( status . dirty_paths ) . toEqual ( [ ] ) ;
568+ expect ( git ( `git --git-dir "${ remoteDir } " ls-tree -r --name-only main` , rootDir ) ) . toContain (
569+ `.agentv/results/runs/safe-run/${ runTimestamp } /benchmark.json` ,
570+ ) ;
571+ expect ( git ( `git --git-dir "${ remoteDir } " ls-tree -r --name-only main` , rootDir ) ) . not . toContain (
572+ 'package.json' ,
573+ ) ;
574+ expect ( readFileSync ( path . join ( cloneDir , 'package.json' ) , 'utf8' ) ) . toBe (
575+ '{"dependencies":{"agentv":"next"}}\n' ,
576+ ) ;
577+ } , 20000 ) ;
578+
579+ it ( 'does not commit unrelated files that were already staged before sync' , async ( ) => {
580+ const { remoteDir } = initializeRemoteRepo ( rootDir ) ;
581+ const cloneDir = path . join ( rootDir , 'results-clone' ) ;
582+ const config = createResultsConfig ( remoteDir , cloneDir ) ;
583+
584+ await ensureResultsRepoClone ( config ) ;
585+ git ( 'git config user.email "test@example.com"' , cloneDir ) ;
586+ git ( 'git config user.name "Test User"' , cloneDir ) ;
587+ writeFileSync ( path . join ( cloneDir , 'package.json' ) , '{"dependencies":{"agentv":"next"}}\n' ) ;
588+ git ( 'git add package.json' , cloneDir ) ;
589+
590+ const runTimestamp = '2026-05-24T11-30-00-000Z' ;
591+ const runDir = path . join (
592+ cloneDir ,
593+ '.agentv' ,
594+ 'results' ,
595+ 'runs' ,
596+ 'staged-unrelated' ,
597+ runTimestamp ,
598+ ) ;
599+ writeRunArtifacts ( runDir , 'staged-unrelated' , '2026-05-24T11:30:00.000Z' ) ;
600+
601+ const status = await syncResultsRepoForProject ( config ) ;
602+
603+ expect ( status ) . toMatchObject ( {
604+ sync_status : 'clean' ,
605+ commit_created : true ,
606+ push_performed : true ,
607+ blocked : false ,
608+ } ) ;
609+ const remoteFiles = git ( `git --git-dir "${ remoteDir } " ls-tree -r --name-only main` , rootDir ) ;
610+ expect ( remoteFiles ) . toContain (
611+ `.agentv/results/runs/staged-unrelated/${ runTimestamp } /benchmark.json` ,
612+ ) ;
613+ expect ( remoteFiles ) . not . toContain ( 'package.json' ) ;
614+ expect ( git ( 'git status --porcelain' , cloneDir ) ) . toContain ( 'A package.json' ) ;
615+ } , 20000 ) ;
616+
617+ it ( 'fast-forwards remote updates even when unrelated local files are dirty' , async ( ) => {
618+ const { remoteDir, seedDir } = initializeRemoteRepo ( rootDir ) ;
619+ const cloneDir = path . join ( rootDir , 'results-clone' ) ;
620+ const config = createResultsConfig ( remoteDir , cloneDir ) ;
621+
622+ await ensureResultsRepoClone ( config ) ;
623+ writeFileSync ( path . join ( cloneDir , 'package.json' ) , '{"dependencies":{"agentv":"next"}}\n' ) ;
624+ writeFileSync ( path . join ( seedDir , 'REMOTE.md' ) , 'remote update\n' ) ;
625+ git ( 'git add REMOTE.md && git commit --quiet -m "remote update"' , seedDir ) ;
626+ git ( 'git push --quiet origin main' , seedDir ) ;
627+
628+ const status = await syncResultsRepoForProject ( config ) ;
629+
630+ expect ( status ) . toMatchObject ( {
631+ sync_status : 'clean' ,
632+ pull_performed : true ,
633+ push_performed : false ,
634+ commit_created : false ,
635+ blocked : false ,
636+ } ) ;
637+ expect ( readFileSync ( path . join ( cloneDir , 'REMOTE.md' ) , 'utf8' ) ) . toBe ( 'remote update\n' ) ;
638+ expect ( readFileSync ( path . join ( cloneDir , 'package.json' ) , 'utf8' ) ) . toBe (
639+ '{"dependencies":{"agentv":"next"}}\n' ,
640+ ) ;
641+ } , 20000 ) ;
642+
643+ it ( 'pulls remote updates before pushing local result artifacts with unrelated dirty files' , async ( ) => {
644+ const { remoteDir, seedDir } = initializeRemoteRepo ( rootDir ) ;
645+ const cloneDir = path . join ( rootDir , 'results-clone' ) ;
646+ const config = createResultsConfig ( remoteDir , cloneDir ) ;
647+
648+ await ensureResultsRepoClone ( config ) ;
649+ git ( 'git config user.email "test@example.com"' , cloneDir ) ;
650+ git ( 'git config user.name "Test User"' , cloneDir ) ;
651+ writeFileSync ( path . join ( cloneDir , 'package.json' ) , '{"dependencies":{"agentv":"next"}}\n' ) ;
652+
653+ writeFileSync ( path . join ( seedDir , 'REMOTE.md' ) , 'remote update\n' ) ;
654+ git ( 'git add REMOTE.md && git commit --quiet -m "remote update"' , seedDir ) ;
655+ git ( 'git push --quiet origin main' , seedDir ) ;
656+
657+ const runTimestamp = '2026-05-24T12-00-00-000Z' ;
658+ const runDir = path . join (
659+ cloneDir ,
660+ '.agentv' ,
661+ 'results' ,
662+ 'runs' ,
663+ 'pulled-then-pushed' ,
664+ runTimestamp ,
665+ ) ;
666+ writeRunArtifacts ( runDir , 'pulled-then-pushed' , '2026-05-24T12:00:00.000Z' ) ;
667+
668+ const status = await syncResultsRepoForProject ( config ) ;
669+
670+ expect ( status ) . toMatchObject ( {
671+ sync_status : 'clean' ,
672+ pull_performed : true ,
673+ push_performed : true ,
674+ commit_created : true ,
675+ blocked : false ,
676+ } ) ;
677+ const remoteFiles = git ( `git --git-dir "${ remoteDir } " ls-tree -r --name-only main` , rootDir ) ;
678+ expect ( remoteFiles ) . toContain ( 'REMOTE.md' ) ;
679+ expect ( remoteFiles ) . toContain (
680+ `.agentv/results/runs/pulled-then-pushed/${ runTimestamp } /benchmark.json` ,
681+ ) ;
682+ expect ( remoteFiles ) . not . toContain ( 'package.json' ) ;
683+ expect ( readFileSync ( path . join ( cloneDir , 'package.json' ) , 'utf8' ) ) . toBe (
684+ '{"dependencies":{"agentv":"next"}}\n' ,
685+ ) ;
686+ } , 20000 ) ;
687+
540688 it ( 'blocks diverged committed histories with diff summary' , async ( ) => {
541689 const { remoteDir, seedDir } = initializeRemoteRepo ( rootDir ) ;
542690 const cloneDir = path . join ( rootDir , 'results-clone' ) ;
0 commit comments