Skip to content

Commit 925100c

Browse files
committed
fix codex global RTK reference path
1 parent 7571c8e commit 925100c

3 files changed

Lines changed: 165 additions & 37 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ Creates `~/.gemini/hooks/rtk-hook-gemini.sh` + patches `~/.gemini/settings.json`
368368
rtk init -g --codex
369369
```
370370

371-
Creates `~/.codex/RTK.md` + `~/.codex/AGENTS.md` with `@RTK.md` reference. Codex reads these as global instructions.
371+
Creates `~/.codex/RTK.md` + `~/.codex/AGENTS.md`. Global Codex installs write the absolute `~/.codex/RTK.md` path into `AGENTS.md` so the instructions still resolve when Codex is opened from another project.
372372

373373
### Windsurf
374374

hooks/codex/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
## Specifics
66

77
- Prompt-level guidance via awareness document -- no programmatic hook
8-
- `rtk-awareness.md` is injected into `AGENTS.md` with an `@RTK.md` reference
8+
- Global installs inject the absolute `~/.codex/RTK.md` path into `AGENTS.md`; local installs keep the project-local `@RTK.md` reference
99
- Installed to `~/.codex/` by `rtk init --codex`

src/hooks/init.rs

Lines changed: 163 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,65 @@ const AGENTS_MD: &str = "AGENTS.md";
6262
const RTK_MD_REF: &str = "@RTK.md";
6363
const 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)]
67126
pub 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!("\nRTK 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 -->\nold\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

Comments
 (0)