@@ -62,6 +62,65 @@ const AGENTS_MD: &str = "AGENTS.md";
6262const RTK_MD_REF : & str = "@RTK.md" ;
6363const GEMINI_MD : & str = "GEMINI.md" ;
6464
65+ fn codex_agents_reference ( rtk_md_path : & Path , global : bool ) -> String {
66+ if global {
67+ rtk_md_path. display ( ) . to_string ( )
68+ } else {
69+ RTK_MD_REF . to_string ( )
70+ }
71+ }
72+
73+ fn codex_agents_reference_aliases ( rtk_md_path : & Path , global : bool ) -> Vec < String > {
74+ let absolute_ref = rtk_md_path. display ( ) . to_string ( ) ;
75+
76+ if global {
77+ vec ! [
78+ absolute_ref. clone( ) ,
79+ format!( "@{absolute_ref}" ) ,
80+ RTK_MD_REF . to_string( ) ,
81+ ]
82+ } else {
83+ vec ! [
84+ RTK_MD_REF . to_string( ) ,
85+ RTK_MD . to_string( ) ,
86+ absolute_ref. clone( ) ,
87+ format!( "@{absolute_ref}" ) ,
88+ ]
89+ }
90+ }
91+
92+ fn content_has_reference ( content : & str , reference : & str ) -> bool {
93+ content. lines ( ) . any ( |line| line. trim ( ) == reference)
94+ }
95+
96+ fn content_has_any_reference ( content : & str , references : & [ String ] ) -> bool {
97+ references
98+ . iter ( )
99+ . any ( |reference| content_has_reference ( content, reference) )
100+ }
101+
102+ fn normalize_agents_references (
103+ content : & str ,
104+ references : & [ String ] ,
105+ desired_reference : & str ,
106+ ) -> String {
107+ let filtered = content
108+ . lines ( )
109+ . filter ( |line| {
110+ let trimmed = line. trim ( ) ;
111+ !references. iter ( ) . any ( |reference| trimmed == reference)
112+ } )
113+ . collect :: < Vec < _ > > ( )
114+ . join ( "\n " ) ;
115+
116+ let trimmed = filtered. trim ( ) ;
117+ if trimmed. is_empty ( ) {
118+ format ! ( "{desired_reference}\n " )
119+ } else {
120+ format ! ( "{trimmed}\n \n {desired_reference}\n " )
121+ }
122+ }
123+
65124/// Control flow for settings.json patching
66125#[ derive( Debug , Clone , Copy , PartialEq ) ]
67126pub enum PatchMode {
@@ -685,8 +744,9 @@ fn uninstall_codex_at(codex_dir: &Path, verbose: u8) -> Result<Vec<String>> {
685744 }
686745
687746 let agents_md_path = codex_dir. join ( AGENTS_MD ) ;
688- if remove_rtk_reference_from_agents ( & agents_md_path, verbose) ? {
689- removed. push ( "AGENTS.md: removed @RTK.md reference" . to_string ( ) ) ;
747+ let references = codex_agents_reference_aliases ( & rtk_md_path, true ) ;
748+ if remove_rtk_reference_from_agents ( & agents_md_path, & references, verbose) ? {
749+ removed. push ( "AGENTS.md: removed RTK reference" . to_string ( ) ) ;
690750 }
691751
692752 Ok ( removed)
@@ -1265,14 +1325,22 @@ fn run_codex_mode(global: bool, verbose: u8) -> Result<()> {
12651325 }
12661326
12671327 write_if_changed ( & rtk_md_path, RTK_SLIM_CODEX , RTK_MD , verbose) ?;
1268- let added_ref = patch_agents_md ( & agents_md_path, verbose) ?;
1328+ let agents_reference = codex_agents_reference ( & rtk_md_path, global) ;
1329+ let changed_ref = patch_agents_md (
1330+ & agents_md_path,
1331+ & agents_reference,
1332+ & rtk_md_path,
1333+ global,
1334+ verbose,
1335+ ) ?;
12691336
12701337 println ! ( "\n RTK configured for Codex CLI.\n " ) ;
12711338 println ! ( " RTK.md: {}" , rtk_md_path. display( ) ) ;
1272- if added_ref {
1273- println ! ( " AGENTS.md: @RTK.md reference added" ) ;
1339+ println ! ( " AGENTS.md: {}" , agents_reference) ;
1340+ if changed_ref {
1341+ println ! ( " reference installed or updated" ) ;
12741342 } else {
1275- println ! ( " AGENTS.md: @RTK.md reference already present" ) ;
1343+ println ! ( " reference already present" ) ;
12761344 }
12771345 if global {
12781346 println ! (
@@ -1401,8 +1469,14 @@ fn patch_claude_md(path: &Path, verbose: u8) -> Result<bool> {
14011469 Ok ( migrated)
14021470}
14031471
1404- /// Patch AGENTS.md: add @RTK.md, migrate old inline block if present
1405- fn patch_agents_md ( path : & Path , verbose : u8 ) -> Result < bool > {
1472+ /// Patch AGENTS.md: add the desired RTK reference, migrate old inline block if present
1473+ fn patch_agents_md (
1474+ path : & Path ,
1475+ desired_reference : & str ,
1476+ rtk_md_path : & Path ,
1477+ global : bool ,
1478+ verbose : u8 ,
1479+ ) -> Result < bool > {
14061480 let mut content = if path. exists ( ) {
14071481 fs:: read_to_string ( path)
14081482 . with_context ( || format ! ( "Failed to read AGENTS.md: {}" , path. display( ) ) ) ?
@@ -1422,57 +1496,59 @@ fn patch_agents_md(path: &Path, verbose: u8) -> Result<bool> {
14221496 }
14231497 }
14241498
1425- if content. contains ( RTK_MD_REF ) {
1499+ let references = codex_agents_reference_aliases ( rtk_md_path, global) ;
1500+ let has_desired_reference = content_has_reference ( & content, desired_reference) ;
1501+ let has_legacy_reference = references. iter ( ) . any ( |reference| {
1502+ reference != desired_reference && content_has_reference ( & content, reference)
1503+ } ) ;
1504+
1505+ if has_desired_reference && !has_legacy_reference && !migrated {
14261506 if verbose > 0 {
1427- eprintln ! ( "@RTK.md reference already present in AGENTS.md" ) ;
1428- }
1429- if migrated {
1430- atomic_write ( path, & content)
1431- . with_context ( || format ! ( "Failed to write AGENTS.md: {}" , path. display( ) ) ) ?;
1507+ eprintln ! ( "RTK reference already present in AGENTS.md" ) ;
14321508 }
14331509 return Ok ( false ) ;
14341510 }
14351511
1436- let new_content = if content. is_empty ( ) {
1437- "@RTK.md\n " . to_string ( )
1438- } else {
1439- format ! ( "{}\n \n @RTK.md\n " , content. trim( ) )
1440- } ;
1512+ let new_content = normalize_agents_references ( & content, & references, desired_reference) ;
14411513
14421514 atomic_write ( path, & new_content)
14431515 . with_context ( || format ! ( "Failed to write AGENTS.md: {}" , path. display( ) ) ) ?;
14441516 if verbose > 0 {
1445- eprintln ! ( "Added @ RTK.md reference to AGENTS.md" ) ;
1517+ eprintln ! ( "Installed RTK reference in AGENTS.md" ) ;
14461518 }
14471519
14481520 Ok ( true )
14491521}
14501522
1451- fn remove_rtk_reference_from_agents ( path : & Path , verbose : u8 ) -> Result < bool > {
1523+ fn remove_rtk_reference_from_agents (
1524+ path : & Path ,
1525+ references : & [ String ] ,
1526+ verbose : u8 ,
1527+ ) -> Result < bool > {
14521528 if !path. exists ( ) {
14531529 return Ok ( false ) ;
14541530 }
14551531
14561532 let content = fs:: read_to_string ( path)
14571533 . with_context ( || format ! ( "Failed to read AGENTS.md: {}" , path. display( ) ) ) ?;
1458- if !content . contains ( RTK_MD_REF ) {
1534+ if !content_has_any_reference ( & content , references ) {
14591535 return Ok ( false ) ;
14601536 }
14611537
14621538 let new_content = content
14631539 . lines ( )
1464- . filter ( |line| !line. trim ( ) . starts_with ( RTK_MD_REF ) )
1540+ . filter ( |line| {
1541+ let trimmed = line. trim ( ) ;
1542+ !references. iter ( ) . any ( |reference| trimmed == reference)
1543+ } )
14651544 . collect :: < Vec < _ > > ( )
14661545 . join ( "\n " ) ;
14671546 let cleaned = clean_double_blanks ( & new_content) ;
14681547 atomic_write ( path, & cleaned)
14691548 . with_context ( || format ! ( "Failed to write AGENTS.md: {}" , path. display( ) ) ) ?;
14701549
14711550 if verbose > 0 {
1472- eprintln ! (
1473- "Removed @RTK.md reference from AGENTS.md: {}" ,
1474- path. display( )
1475- ) ;
1551+ eprintln ! ( "Removed RTK reference from AGENTS.md: {}" , path. display( ) ) ;
14761552 }
14771553
14781554 Ok ( true )
@@ -2049,8 +2125,12 @@ fn show_codex_config() -> Result<()> {
20492125
20502126 if global_agents_md. exists ( ) {
20512127 let content = fs:: read_to_string ( & global_agents_md) ?;
2052- if content. contains ( RTK_MD_REF ) {
2053- println ! ( "[ok] Global AGENTS.md: @RTK.md reference" ) ;
2128+ let global_references = codex_agents_reference_aliases ( & global_rtk_md, true ) ;
2129+ if content_has_any_reference ( & content, & global_references) {
2130+ println ! (
2131+ "[ok] Global AGENTS.md: {}" ,
2132+ codex_agents_reference( & global_rtk_md, true )
2133+ ) ;
20542134 } else if content. contains ( "<!-- rtk-instructions" ) {
20552135 println ! ( "[!!] Global AGENTS.md: old inline RTK block" ) ;
20562136 } else {
@@ -2068,8 +2148,12 @@ fn show_codex_config() -> Result<()> {
20682148
20692149 if local_agents_md. exists ( ) {
20702150 let content = fs:: read_to_string ( & local_agents_md) ?;
2071- if content. contains ( RTK_MD_REF ) {
2072- println ! ( "[ok] Local AGENTS.md: @RTK.md reference" ) ;
2151+ let local_references = codex_agents_reference_aliases ( & local_rtk_md, false ) ;
2152+ if content_has_any_reference ( & content, & local_references) {
2153+ println ! (
2154+ "[ok] Local AGENTS.md: {}" ,
2155+ codex_agents_reference( & local_rtk_md, false )
2156+ ) ;
20732157 } else if content. contains ( "<!-- rtk-instructions" ) {
20742158 println ! ( "[!!] Local AGENTS.md: old inline RTK block" ) ;
20752159 } else {
@@ -2592,10 +2676,11 @@ More notes
25922676 fn test_patch_agents_md_adds_reference_once ( ) {
25932677 let temp = TempDir :: new ( ) . unwrap ( ) ;
25942678 let agents_md = temp. path ( ) . join ( "AGENTS.md" ) ;
2679+ let rtk_md = temp. path ( ) . join ( "RTK.md" ) ;
25952680
25962681 fs:: write ( & agents_md, "# Team rules\n " ) . unwrap ( ) ;
2597- let first_added = patch_agents_md ( & agents_md, 0 ) . unwrap ( ) ;
2598- let second_added = patch_agents_md ( & agents_md, 0 ) . unwrap ( ) ;
2682+ let first_added = patch_agents_md ( & agents_md, RTK_MD_REF , & rtk_md , false , 0 ) . unwrap ( ) ;
2683+ let second_added = patch_agents_md ( & agents_md, RTK_MD_REF , & rtk_md , false , 0 ) . unwrap ( ) ;
25992684
26002685 assert ! ( first_added) ;
26012686 assert ! ( !second_added) ;
@@ -2652,8 +2737,9 @@ More notes
26522737 fn test_patch_agents_md_creates_missing_file ( ) {
26532738 let temp = TempDir :: new ( ) . unwrap ( ) ;
26542739 let agents_md = temp. path ( ) . join ( "AGENTS.md" ) ;
2740+ let rtk_md = temp. path ( ) . join ( "RTK.md" ) ;
26552741
2656- let added = patch_agents_md ( & agents_md, 0 ) . unwrap ( ) ;
2742+ let added = patch_agents_md ( & agents_md, RTK_MD_REF , & rtk_md , false , 0 ) . unwrap ( ) ;
26572743
26582744 assert ! ( added) ;
26592745 let content = fs:: read_to_string ( & agents_md) . unwrap ( ) ;
@@ -2664,20 +2750,39 @@ More notes
26642750 fn test_patch_agents_md_migrates_inline_block ( ) {
26652751 let temp = TempDir :: new ( ) . unwrap ( ) ;
26662752 let agents_md = temp. path ( ) . join ( "AGENTS.md" ) ;
2753+ let rtk_md = temp. path ( ) . join ( "RTK.md" ) ;
26672754 fs:: write (
26682755 & agents_md,
26692756 "# Team rules\n \n <!-- rtk-instructions v2 -->\n old\n <!-- /rtk-instructions -->\n " ,
26702757 )
26712758 . unwrap ( ) ;
26722759
2673- let added = patch_agents_md ( & agents_md, 0 ) . unwrap ( ) ;
2760+ let added = patch_agents_md ( & agents_md, RTK_MD_REF , & rtk_md , false , 0 ) . unwrap ( ) ;
26742761
26752762 assert ! ( added) ;
26762763 let content = fs:: read_to_string ( & agents_md) . unwrap ( ) ;
26772764 assert ! ( !content. contains( "old" ) ) ;
26782765 assert_eq ! ( content. matches( "@RTK.md" ) . count( ) , 1 ) ;
26792766 }
26802767
2768+ #[ test]
2769+ fn test_patch_agents_md_migrates_global_reference_to_absolute_path ( ) {
2770+ let temp = TempDir :: new ( ) . unwrap ( ) ;
2771+ let agents_md = temp. path ( ) . join ( "AGENTS.md" ) ;
2772+ let rtk_md = temp. path ( ) . join ( "RTK.md" ) ;
2773+ let expected_reference = rtk_md. display ( ) . to_string ( ) ;
2774+
2775+ fs:: write ( & agents_md, "# Team rules\n \n @RTK.md\n " ) . unwrap ( ) ;
2776+
2777+ let changed = patch_agents_md ( & agents_md, & expected_reference, & rtk_md, true , 0 ) . unwrap ( ) ;
2778+
2779+ assert ! ( changed) ;
2780+ let content = fs:: read_to_string ( & agents_md) . unwrap ( ) ;
2781+ assert ! ( content. contains( "# Team rules" ) ) ;
2782+ assert ! ( content. contains( & expected_reference) ) ;
2783+ assert ! ( !content. contains( "@RTK.md" ) ) ;
2784+ }
2785+
26812786 #[ test]
26822787 fn test_uninstall_codex_at_is_idempotent ( ) {
26832788 let temp = TempDir :: new ( ) . unwrap ( ) ;
@@ -2700,6 +2805,29 @@ More notes
27002805 assert ! ( content. contains( "# Team rules" ) ) ;
27012806 }
27022807
2808+ #[ test]
2809+ fn test_uninstall_codex_at_removes_absolute_reference ( ) {
2810+ let temp = TempDir :: new ( ) . unwrap ( ) ;
2811+ let codex_dir = temp. path ( ) ;
2812+ let agents_md = codex_dir. join ( "AGENTS.md" ) ;
2813+ let rtk_md = codex_dir. join ( "RTK.md" ) ;
2814+ let absolute_reference = rtk_md. display ( ) . to_string ( ) ;
2815+
2816+ fs:: write (
2817+ & agents_md,
2818+ format ! ( "# Team rules\n \n {absolute_reference}\n " ) ,
2819+ )
2820+ . unwrap ( ) ;
2821+ fs:: write ( & rtk_md, "codex config" ) . unwrap ( ) ;
2822+
2823+ let removed = uninstall_codex_at ( codex_dir, 0 ) . unwrap ( ) ;
2824+
2825+ assert_eq ! ( removed. len( ) , 2 ) ;
2826+ let content = fs:: read_to_string ( & agents_md) . unwrap ( ) ;
2827+ assert ! ( !content. contains( & absolute_reference) ) ;
2828+ assert ! ( content. contains( "# Team rules" ) ) ;
2829+ }
2830+
27032831 #[ test]
27042832 fn test_local_init_unchanged ( ) {
27052833 // Local init should use claude-md mode
0 commit comments