kiss_coding/
context_files.rs1use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone, PartialEq)]
8pub struct ContextFile {
9 pub path: PathBuf,
10 pub content: String,
11}
12
13fn pick_context_file(dir: &Path) -> Option<PathBuf> {
14 for name in ["AGENTS.override.md", "AGENTS.md", "CLAUDE.md"] {
15 let candidate = dir.join(name);
16 if candidate.is_file() {
17 return Some(candidate);
18 }
19 }
20 None
21}
22
23pub fn discover(cwd: &Path) -> Vec<ContextFile> {
24 let mut paths: Vec<PathBuf> = Vec::new();
25
26 if let Some(home) = dirs::home_dir() {
27 let global = home.join(".kiss/agent/AGENTS.md");
28 if global.is_file() {
29 paths.push(global);
30 }
31 }
32
33 let mut chain: Vec<&Path> = cwd.ancestors().collect();
35 chain.reverse();
36 for dir in chain {
37 if let Some(file) = pick_context_file(dir) {
38 paths.push(file);
39 }
40 }
41
42 paths.dedup();
43 paths
44 .into_iter()
45 .filter_map(|path| {
46 let content = std::fs::read_to_string(&path).ok()?;
47 Some(ContextFile { path, content })
48 })
49 .collect()
50}
51
52pub struct SystemPromptFiles {
54 pub replace: Option<String>,
55 pub append: Option<String>,
56}
57
58pub fn system_prompt_files(cwd: &Path) -> SystemPromptFiles {
59 let read = |p: PathBuf| std::fs::read_to_string(p).ok();
60 let global = dirs::home_dir().map(|h| h.join(".kiss/agent"));
61 let project = cwd.join(".kiss");
62
63 let replace = read(project.join("SYSTEM.md"))
64 .or_else(|| global.as_ref().and_then(|g| read(g.join("SYSTEM.md"))));
65 let append = read(project.join("APPEND_SYSTEM.md")).or_else(|| {
66 global
67 .as_ref()
68 .and_then(|g| read(g.join("APPEND_SYSTEM.md")))
69 });
70 SystemPromptFiles { replace, append }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn override_beats_agents_beats_claude() {
79 let dir = tempfile::tempdir().unwrap();
80 std::fs::write(dir.path().join("CLAUDE.md"), "claude").unwrap();
81 assert!(
82 pick_context_file(dir.path())
83 .unwrap()
84 .ends_with("CLAUDE.md")
85 );
86 std::fs::write(dir.path().join("AGENTS.md"), "agents").unwrap();
87 assert!(
88 pick_context_file(dir.path())
89 .unwrap()
90 .ends_with("AGENTS.md")
91 );
92 std::fs::write(dir.path().join("AGENTS.override.md"), "override").unwrap();
93 assert!(
94 pick_context_file(dir.path())
95 .unwrap()
96 .ends_with("AGENTS.override.md")
97 );
98 }
99
100 #[test]
101 fn ancestors_ordered_root_to_cwd() {
102 let dir = tempfile::tempdir().unwrap();
103 let nested = dir.path().join("a/b");
104 std::fs::create_dir_all(&nested).unwrap();
105 std::fs::write(dir.path().join("AGENTS.md"), "root rules").unwrap();
106 std::fs::write(nested.join("AGENTS.md"), "leaf rules").unwrap();
107 let files = discover(&nested);
108 let contents: Vec<&str> = files.iter().map(|f| f.content.as_str()).collect();
109 let root_pos = contents.iter().position(|c| *c == "root rules").unwrap();
110 let leaf_pos = contents.iter().position(|c| *c == "leaf rules").unwrap();
111 assert!(root_pos < leaf_pos);
112 }
113}