Skip to content

Commit eb2eba6

Browse files
author
wangnov
committed
feat(init): codex提示词加入Read/apply_patch规则
1 parent 76f7603 commit eb2eba6

File tree

1 file changed

+52
-26
lines changed

1 file changed

+52
-26
lines changed

src/init.rs

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use crate::cli::{InitTarget, Scope};
99
use crate::config::home_dir;
1010
use crate::i18n::I18n;
1111

12-
/// Embedded shnote rules content
13-
const SHNOTE_RULES: &str = r#"# shnote - Shell Command Wrapper
12+
/// Embedded shnote rules content (shared by all targets)
13+
const SHNOTE_RULES_BASE: &str = r#"# shnote - Shell Command Wrapper
1414
1515
## 概述
1616
@@ -338,10 +338,25 @@ file2.txt
338338
这使得 AI Agent 可以轻松追踪每个命令的意图和执行结果。
339339
"#;
340340

341+
/// Codex-specific additions to the shnote rules
342+
const SHNOTE_RULES_CODEX_EXTRA: &str = r#"## Codex 额外规则
343+
344+
除非用户明确要求,否则默认使用 **Read** 工具和 **apply_patch** 工具来完成阅读与修改文件的操作,不要用 shnote 来做读取/编辑。
345+
"#;
346+
341347
/// Marker to identify shnote rules section in append mode
342348
const SHNOTE_MARKER_START: &str = "\n<!-- shnote rules start -->\n";
343349
const SHNOTE_MARKER_END: &str = "\n<!-- shnote rules end -->\n";
344350

351+
fn rules_for_target(target: InitTarget) -> String {
352+
let mut rules = SHNOTE_RULES_BASE.to_string();
353+
if matches!(target, InitTarget::Codex) {
354+
rules.push_str("\n\n");
355+
rules.push_str(SHNOTE_RULES_CODEX_EXTRA);
356+
}
357+
rules
358+
}
359+
345360
pub fn run_init(i18n: &I18n, target: InitTarget, scope: Scope) -> Result<()> {
346361
match target {
347362
InitTarget::Claude => init_claude(i18n, scope),
@@ -361,6 +376,7 @@ fn get_base_dir(i18n: &I18n, scope: Scope) -> Result<PathBuf> {
361376
fn init_claude(i18n: &I18n, scope: Scope) -> Result<()> {
362377
let probe = probe_cli_tool(i18n, "claude");
363378
let base = get_base_dir(i18n, scope)?;
379+
let rules = rules_for_target(InitTarget::Claude);
364380

365381
// Claude Code >= 2.0.64 supports ~/.claude/rules/*.md.
366382
// For older versions (or when version cannot be determined), append rules to ~/.claude/CLAUDE.md.
@@ -387,7 +403,7 @@ fn init_claude(i18n: &I18n, scope: Scope) -> Result<()> {
387403

388404
if !migrated {
389405
// No migration needed, just write the rules file
390-
fs::write(&target_file, SHNOTE_RULES)
406+
fs::write(&target_file, &rules)
391407
.context(i18n.err_write_file(&target_file.display().to_string()))?;
392408
}
393409

@@ -410,7 +426,7 @@ fn init_claude(i18n: &I18n, scope: Scope) -> Result<()> {
410426
fs::create_dir_all(&claude_dir)
411427
.context(i18n.err_create_dir(&claude_dir.display().to_string()))?;
412428
let target_file = claude_dir.join("CLAUDE.md");
413-
append_rules(i18n, &target_file)?;
429+
append_rules(i18n, &target_file, &rules)?;
414430
println!(
415431
"{}",
416432
i18n.init_claude_success(&target_file.display().to_string())
@@ -442,7 +458,7 @@ fn migrate_shnote_rules(i18n: &I18n, old_file: &Path, new_file: &Path) -> Result
442458

443459
// Write extracted rules to new file (use latest rules, not old content)
444460
// This ensures we always have the latest version
445-
fs::write(new_file, SHNOTE_RULES)
461+
fs::write(new_file, SHNOTE_RULES_BASE)
446462
.context(i18n.err_write_file(&new_file.display().to_string()))?;
447463

448464
// Remove shnote rules from old file
@@ -475,14 +491,15 @@ fn migrate_shnote_rules(i18n: &I18n, old_file: &Path, new_file: &Path) -> Result
475491
fn init_codex(i18n: &I18n, scope: Scope) -> Result<()> {
476492
let _ = probe_cli_tool(i18n, "codex");
477493
let base = get_base_dir(i18n, scope)?;
494+
let rules = rules_for_target(InitTarget::Codex);
478495
let codex_dir = base.join(".codex");
479496
let target_file = codex_dir.join("AGENTS.md");
480497

481498
// Create directory if needed
482499
fs::create_dir_all(&codex_dir)
483500
.context(i18n.err_create_dir(&codex_dir.display().to_string()))?;
484501

485-
append_rules(i18n, &target_file)?;
502+
append_rules(i18n, &target_file, &rules)?;
486503

487504
println!(
488505
"{}",
@@ -494,14 +511,15 @@ fn init_codex(i18n: &I18n, scope: Scope) -> Result<()> {
494511
fn init_gemini(i18n: &I18n, scope: Scope) -> Result<()> {
495512
let _ = probe_cli_tool(i18n, "gemini");
496513
let base = get_base_dir(i18n, scope)?;
514+
let rules = rules_for_target(InitTarget::Gemini);
497515
let gemini_dir = base.join(".gemini");
498516
let target_file = gemini_dir.join("GEMINI.md");
499517

500518
// Create directory if needed
501519
fs::create_dir_all(&gemini_dir)
502520
.context(i18n.err_create_dir(&gemini_dir.display().to_string()))?;
503521

504-
append_rules(i18n, &target_file)?;
522+
append_rules(i18n, &target_file, &rules)?;
505523

506524
println!(
507525
"{}",
@@ -510,7 +528,7 @@ fn init_gemini(i18n: &I18n, scope: Scope) -> Result<()> {
510528
Ok(())
511529
}
512530

513-
fn append_rules(i18n: &I18n, target_file: &PathBuf) -> Result<()> {
531+
fn append_rules(i18n: &I18n, target_file: &PathBuf, rules: &str) -> Result<()> {
514532
let mut content = if target_file.exists() {
515533
fs::read_to_string(target_file)
516534
.context(i18n.err_read_file(&target_file.display().to_string()))?
@@ -530,7 +548,7 @@ fn append_rules(i18n: &I18n, target_file: &PathBuf) -> Result<()> {
530548
let mut new_content = String::new();
531549
new_content.push_str(&content[..start_idx]);
532550
new_content.push_str(SHNOTE_MARKER_START);
533-
new_content.push_str(SHNOTE_RULES);
551+
new_content.push_str(rules);
534552
new_content.push_str(SHNOTE_MARKER_END);
535553
new_content.push_str(&content[end_idx..]);
536554

@@ -541,7 +559,7 @@ fn append_rules(i18n: &I18n, target_file: &PathBuf) -> Result<()> {
541559
} else {
542560
// Append new rules (rewrite the file to keep behavior deterministic and testable)
543561
content.push_str(SHNOTE_MARKER_START);
544-
content.push_str(SHNOTE_RULES);
562+
content.push_str(rules);
545563
content.push_str(SHNOTE_MARKER_END);
546564

547565
fs::write(target_file, content)
@@ -667,10 +685,17 @@ mod tests {
667685
#[test]
668686
fn shnote_rules_has_content() {
669687
// Verify rules contain expected content
670-
assert!(SHNOTE_RULES.contains("shnote"));
671-
assert!(SHNOTE_RULES.contains("--what"));
672-
assert!(SHNOTE_RULES.contains("--why"));
673-
assert!(SHNOTE_RULES.len() > 1000); // Rules should be substantial
688+
assert!(SHNOTE_RULES_BASE.contains("shnote"));
689+
assert!(SHNOTE_RULES_BASE.contains("--what"));
690+
assert!(SHNOTE_RULES_BASE.contains("--why"));
691+
assert!(SHNOTE_RULES_BASE.len() > 1000); // Rules should be substantial
692+
}
693+
694+
#[test]
695+
fn codex_rules_include_extra_instruction() {
696+
let rules = rules_for_target(InitTarget::Codex);
697+
assert!(rules.contains("Read"));
698+
assert!(rules.contains("apply_patch"));
674699
}
675700

676701
#[test]
@@ -734,7 +759,7 @@ mod tests {
734759
let temp_dir = TempDir::new().unwrap();
735760
let target_file = temp_dir.path().join("test.md");
736761

737-
append_rules(&i18n, &target_file).unwrap();
762+
append_rules(&i18n, &target_file, SHNOTE_RULES_BASE).unwrap();
738763

739764
assert!(target_file.exists());
740765
let content = fs::read_to_string(&target_file).unwrap();
@@ -759,13 +784,13 @@ mod tests {
759784
)
760785
.unwrap();
761786

762-
append_rules(&i18n, &target_file).unwrap();
787+
append_rules(&i18n, &target_file, SHNOTE_RULES_BASE).unwrap();
763788

764789
let content = fs::read_to_string(&target_file).unwrap();
765790
assert!(content.contains("Some content"));
766791
assert!(content.contains("More content"));
767792
assert!(!content.contains("OLD RULES"));
768-
assert!(content.contains(SHNOTE_RULES));
793+
assert!(content.contains(SHNOTE_RULES_BASE));
769794
}
770795

771796
#[cfg(unix)]
@@ -785,7 +810,7 @@ mod tests {
785810
let rules_file = temp_dir.path().join(".claude/rules/shnote.md");
786811
assert!(rules_file.exists());
787812
let content = fs::read_to_string(rules_file).unwrap();
788-
assert_eq!(content, SHNOTE_RULES);
813+
assert_eq!(content, rules_for_target(InitTarget::Claude));
789814
}
790815

791816
#[test]
@@ -943,7 +968,7 @@ mod tests {
943968
let rules_file = temp_dir.path().join(".claude/rules/shnote.md");
944969
assert!(rules_file.exists());
945970
let content = fs::read_to_string(&rules_file).unwrap();
946-
assert_eq!(content, SHNOTE_RULES);
971+
assert_eq!(content, rules_for_target(InitTarget::Claude));
947972

948973
// Check old CLAUDE.md no longer has shnote rules
949974
let old_content = fs::read_to_string(&old_claude_md).unwrap();
@@ -1017,7 +1042,7 @@ mod tests {
10171042
let rules_file = temp_dir.path().join(".claude/rules/shnote.md");
10181043
assert!(rules_file.exists());
10191044
let content = fs::read_to_string(&rules_file).unwrap();
1020-
assert_eq!(content, SHNOTE_RULES);
1045+
assert_eq!(content, rules_for_target(InitTarget::Claude));
10211046

10221047
// Check old CLAUDE.md is unchanged
10231048
let old_content = fs::read_to_string(&old_claude_md).unwrap();
@@ -1191,11 +1216,11 @@ mod tests {
11911216
)
11921217
.unwrap();
11931218

1194-
append_rules(&i18n, &target_file).unwrap();
1219+
append_rules(&i18n, &target_file, SHNOTE_RULES_BASE).unwrap();
11951220

11961221
let content = fs::read_to_string(&target_file).unwrap();
11971222
assert!(content.contains("before"));
1198-
assert!(content.contains(SHNOTE_RULES));
1223+
assert!(content.contains(SHNOTE_RULES_BASE));
11991224
assert!(!content.contains("OLD RULES WITHOUT END"));
12001225
assert!(!content.contains("after"));
12011226
}
@@ -1207,7 +1232,7 @@ mod tests {
12071232
let target_file = temp_dir.path().join("dir-as-file");
12081233
fs::create_dir_all(&target_file).unwrap();
12091234

1210-
let err = append_rules(&i18n, &target_file).unwrap_err();
1235+
let err = append_rules(&i18n, &target_file, SHNOTE_RULES_BASE).unwrap_err();
12111236
// Check error chain contains the file path (use Debug format to see full chain)
12121237
let err_debug = format!("{:?}", err);
12131238
assert!(err_debug.contains("dir-as-file"));
@@ -1232,7 +1257,7 @@ mod tests {
12321257
.unwrap();
12331258
fs::set_permissions(&target_file, fs::Permissions::from_mode(0o444)).unwrap();
12341259

1235-
let err = append_rules(&i18n, &target_file).unwrap_err();
1260+
let err = append_rules(&i18n, &target_file, SHNOTE_RULES_BASE).unwrap_err();
12361261
assert!(err
12371262
.to_string()
12381263
.contains(&i18n.err_write_file(&target_file.display().to_string())));
@@ -1250,7 +1275,7 @@ mod tests {
12501275
fs::write(&target_file, "existing content\n").unwrap();
12511276
fs::set_permissions(&target_file, fs::Permissions::from_mode(0o444)).unwrap();
12521277

1253-
let err = append_rules(&i18n, &target_file).unwrap_err();
1278+
let err = append_rules(&i18n, &target_file, SHNOTE_RULES_BASE).unwrap_err();
12541279
assert!(err
12551280
.to_string()
12561281
.contains(&i18n.err_write_file(&target_file.display().to_string())));
@@ -1301,7 +1326,7 @@ mod tests {
13011326
let rules_file = temp_dir.path().join(".claude/rules/shnote.md");
13021327
assert!(rules_file.exists());
13031328
let content = fs::read_to_string(rules_file).unwrap();
1304-
assert_eq!(content, SHNOTE_RULES);
1329+
assert_eq!(content, rules_for_target(InitTarget::Claude));
13051330

13061331
// CLAUDE.md should not exist
13071332
let claude_md = temp_dir.path().join(".claude/CLAUDE.md");
@@ -1327,6 +1352,7 @@ mod tests {
13271352
let content = fs::read_to_string(target_file).unwrap();
13281353
assert!(content.contains(SHNOTE_MARKER_START));
13291354
assert!(content.contains("shnote"));
1355+
assert!(content.contains("apply_patch"));
13301356
}
13311357

13321358
#[test]

0 commit comments

Comments
 (0)