@@ -519,24 +519,134 @@ 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 ( 'fast-forwards remote updates even when unrelated local files are dirty' , async ( ) => {
580+ const { remoteDir, seedDir } = initializeRemoteRepo ( rootDir ) ;
581+ const cloneDir = path . join ( rootDir , 'results-clone' ) ;
582+ const config = createResultsConfig ( remoteDir , cloneDir ) ;
583+
584+ await ensureResultsRepoClone ( config ) ;
585+ writeFileSync ( path . join ( cloneDir , 'package.json' ) , '{"dependencies":{"agentv":"next"}}\n' ) ;
586+ writeFileSync ( path . join ( seedDir , 'REMOTE.md' ) , 'remote update\n' ) ;
587+ git ( 'git add REMOTE.md && git commit --quiet -m "remote update"' , seedDir ) ;
588+ git ( 'git push --quiet origin main' , seedDir ) ;
589+
590+ const status = await syncResultsRepoForProject ( config ) ;
591+
592+ expect ( status ) . toMatchObject ( {
593+ sync_status : 'clean' ,
594+ pull_performed : true ,
595+ push_performed : false ,
596+ commit_created : false ,
597+ blocked : false ,
598+ } ) ;
599+ expect ( readFileSync ( path . join ( cloneDir , 'REMOTE.md' ) , 'utf8' ) ) . toBe ( 'remote update\n' ) ;
600+ expect ( readFileSync ( path . join ( cloneDir , 'package.json' ) , 'utf8' ) ) . toBe (
601+ '{"dependencies":{"agentv":"next"}}\n' ,
602+ ) ;
603+ } , 20000 ) ;
604+
605+ it ( 'pulls remote updates before pushing local result artifacts with unrelated dirty files' , async ( ) => {
606+ const { remoteDir, seedDir } = initializeRemoteRepo ( rootDir ) ;
607+ const cloneDir = path . join ( rootDir , 'results-clone' ) ;
608+ const config = createResultsConfig ( remoteDir , cloneDir ) ;
609+
610+ await ensureResultsRepoClone ( config ) ;
611+ git ( 'git config user.email "test@example.com"' , cloneDir ) ;
612+ git ( 'git config user.name "Test User"' , cloneDir ) ;
613+ writeFileSync ( path . join ( cloneDir , 'package.json' ) , '{"dependencies":{"agentv":"next"}}\n' ) ;
614+
615+ writeFileSync ( path . join ( seedDir , 'REMOTE.md' ) , 'remote update\n' ) ;
616+ git ( 'git add REMOTE.md && git commit --quiet -m "remote update"' , seedDir ) ;
617+ git ( 'git push --quiet origin main' , seedDir ) ;
618+
619+ const runTimestamp = '2026-05-24T12-00-00-000Z' ;
620+ const runDir = path . join (
621+ cloneDir ,
622+ '.agentv' ,
623+ 'results' ,
624+ 'runs' ,
625+ 'pulled-then-pushed' ,
626+ runTimestamp ,
627+ ) ;
628+ writeRunArtifacts ( runDir , 'pulled-then-pushed' , '2026-05-24T12:00:00.000Z' ) ;
629+
630+ const status = await syncResultsRepoForProject ( config ) ;
631+
632+ expect ( status ) . toMatchObject ( {
633+ sync_status : 'clean' ,
634+ pull_performed : true ,
635+ push_performed : true ,
636+ commit_created : true ,
637+ blocked : false ,
638+ } ) ;
639+ const remoteFiles = git ( `git --git-dir "${ remoteDir } " ls-tree -r --name-only main` , rootDir ) ;
640+ expect ( remoteFiles ) . toContain ( 'REMOTE.md' ) ;
641+ expect ( remoteFiles ) . toContain (
642+ `.agentv/results/runs/pulled-then-pushed/${ runTimestamp } /benchmark.json` ,
643+ ) ;
644+ expect ( remoteFiles ) . not . toContain ( 'package.json' ) ;
645+ expect ( readFileSync ( path . join ( cloneDir , 'package.json' ) , 'utf8' ) ) . toBe (
646+ '{"dependencies":{"agentv":"next"}}\n' ,
647+ ) ;
648+ } , 20000 ) ;
649+
540650 it ( 'blocks diverged committed histories with diff summary' , async ( ) => {
541651 const { remoteDir, seedDir } = initializeRemoteRepo ( rootDir ) ;
542652 const cloneDir = path . join ( rootDir , 'results-clone' ) ;
0 commit comments