ricecoder_storage/industry/
kiro.rs1use crate::config::{Config, SteeringRule};
7use crate::error::StorageResult;
8use crate::types::DocumentFormat;
9use std::path::Path;
10use tracing::debug;
11
12use super::adapter::IndustryFileAdapter;
13
14pub struct KiroAdapter;
16
17impl KiroAdapter {
18 pub fn new() -> Self {
20 KiroAdapter
21 }
22
23 fn read_kiro_config(&self, project_root: &Path) -> StorageResult<Option<String>> {
25 let kiro_dir = project_root.join(".kiro");
26
27 if !kiro_dir.exists() {
28 debug!("No .kiro directory found at {:?}", kiro_dir);
29 return Ok(None);
30 }
31
32 debug!("Reading .kiro configuration from {:?}", kiro_dir);
33
34 let mut combined_content = String::new();
36
37 let specs_dir = kiro_dir.join("specs");
39 if specs_dir.exists() {
40 if let Ok(entries) = std::fs::read_dir(&specs_dir) {
41 for entry in entries.flatten() {
42 if let Ok(metadata) = entry.metadata() {
43 if metadata.is_file() {
44 if let Ok(content) = std::fs::read_to_string(entry.path()) {
45 combined_content.push_str(&format!(
46 "# Spec: {}\n{}\n\n",
47 entry.path().display(),
48 content
49 ));
50 }
51 }
52 }
53 }
54 }
55 }
56
57 let steering_dir = kiro_dir.join("steering");
59 if steering_dir.exists() {
60 if let Ok(entries) = std::fs::read_dir(&steering_dir) {
61 for entry in entries.flatten() {
62 if let Ok(metadata) = entry.metadata() {
63 if metadata.is_file() {
64 if let Ok(content) = std::fs::read_to_string(entry.path()) {
65 combined_content.push_str(&format!(
66 "# Steering: {}\n{}\n\n",
67 entry.path().display(),
68 content
69 ));
70 }
71 }
72 }
73 }
74 }
75 }
76
77 if combined_content.is_empty() {
78 Ok(None)
79 } else {
80 Ok(Some(combined_content))
81 }
82 }
83}
84
85impl Default for KiroAdapter {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91impl IndustryFileAdapter for KiroAdapter {
92 fn name(&self) -> &'static str {
93 "kiro"
94 }
95
96 fn can_handle(&self, project_root: &Path) -> bool {
97 project_root.join(".kiro").exists()
98 }
99
100 fn read_config(&self, project_root: &Path) -> StorageResult<Config> {
101 let mut config = Config::default();
102
103 if let Ok(Some(kiro_config)) = self.read_kiro_config(project_root) {
104 debug!("Adding Kiro configuration as steering rule");
105 config.steering.push(SteeringRule {
106 name: "kiro-config".to_string(),
107 content: kiro_config,
108 format: DocumentFormat::Markdown,
109 });
110 }
111
112 Ok(config)
113 }
114
115 fn priority(&self) -> u32 {
116 100
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use std::fs;
125 use tempfile::TempDir;
126
127 #[test]
128 fn test_kiro_adapter_detects_directory() {
129 let temp_dir = TempDir::new().unwrap();
130 let kiro_dir = temp_dir.path().join(".kiro");
131 fs::create_dir(&kiro_dir).unwrap();
132
133 let adapter = KiroAdapter::new();
134 assert!(adapter.can_handle(temp_dir.path()));
135 }
136
137 #[test]
138 fn test_kiro_adapter_no_directory() {
139 let temp_dir = TempDir::new().unwrap();
140
141 let adapter = KiroAdapter::new();
142 assert!(!adapter.can_handle(temp_dir.path()));
143 }
144
145 #[test]
146 fn test_kiro_adapter_reads_specs() {
147 let temp_dir = TempDir::new().unwrap();
148 let kiro_dir = temp_dir.path().join(".kiro");
149 fs::create_dir(&kiro_dir).unwrap();
150 let specs_dir = kiro_dir.join("specs");
151 fs::create_dir(&specs_dir).unwrap();
152 fs::write(specs_dir.join("spec1.md"), "# Spec 1").unwrap();
153
154 let adapter = KiroAdapter::new();
155 let config = adapter.read_config(temp_dir.path()).unwrap();
156
157 assert_eq!(config.steering.len(), 1);
158 assert_eq!(config.steering[0].name, "kiro-config");
159 assert!(config.steering[0].content.contains("Spec 1"));
160 }
161
162 #[test]
163 fn test_kiro_adapter_reads_steering() {
164 let temp_dir = TempDir::new().unwrap();
165 let kiro_dir = temp_dir.path().join(".kiro");
166 fs::create_dir(&kiro_dir).unwrap();
167 let steering_dir = kiro_dir.join("steering");
168 fs::create_dir(&steering_dir).unwrap();
169 fs::write(steering_dir.join("rules.md"), "# Rules").unwrap();
170
171 let adapter = KiroAdapter::new();
172 let config = adapter.read_config(temp_dir.path()).unwrap();
173
174 assert_eq!(config.steering.len(), 1);
175 assert_eq!(config.steering[0].name, "kiro-config");
176 assert!(config.steering[0].content.contains("Rules"));
177 }
178
179 #[test]
180 fn test_kiro_adapter_priority() {
181 let adapter = KiroAdapter::new();
182 assert_eq!(adapter.priority(), 100);
183 }
184
185 #[test]
186 fn test_kiro_adapter_name() {
187 let adapter = KiroAdapter::new();
188 assert_eq!(adapter.name(), "kiro");
189 }
190}