feat: consolidate AI rules into unified AGENTS.md (#501)

- Merge all AI rules from .rules.md, .cursorrules, and CLAUDE.md into AGENTS.md
- Add competitor keyword prohibition rules (minio, ceph, swift, etc.)
- Simplify rules by removing overly detailed code examples
- Integrate new development principles as highest priority
- Remove old tool-specific rule files
- Fix clippy warnings for format string improvements
This commit is contained in:
安正超
2025-09-09 21:36:34 +08:00
committed by GitHub
parent 14a8802ce7
commit 9c97524c3b
13 changed files with 277 additions and 1415 deletions
+17 -17
View File
@@ -91,9 +91,9 @@ impl CheckpointManager {
}
}
let checkpoint_file = data_dir.join(format!("scanner_checkpoint_{}.json", node_id));
let backup_file = data_dir.join(format!("scanner_checkpoint_{}.backup", node_id));
let temp_file = data_dir.join(format!("scanner_checkpoint_{}.tmp", node_id));
let checkpoint_file = data_dir.join(format!("scanner_checkpoint_{node_id}.json"));
let backup_file = data_dir.join(format!("scanner_checkpoint_{node_id}.backup"));
let temp_file = data_dir.join(format!("scanner_checkpoint_{node_id}.tmp"));
Self {
checkpoint_file,
@@ -116,21 +116,21 @@ impl CheckpointManager {
let checkpoint_data = CheckpointData::new(progress.clone(), self.node_id.clone());
let json_data = serde_json::to_string_pretty(&checkpoint_data)
.map_err(|e| Error::Serialization(format!("serialize checkpoint failed: {}", e)))?;
.map_err(|e| Error::Serialization(format!("serialize checkpoint failed: {e}")))?;
tokio::fs::write(&self.temp_file, json_data)
.await
.map_err(|e| Error::IO(format!("write temp checkpoint file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("write temp checkpoint file failed: {e}")))?;
if self.checkpoint_file.exists() {
tokio::fs::copy(&self.checkpoint_file, &self.backup_file)
.await
.map_err(|e| Error::IO(format!("backup checkpoint file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("backup checkpoint file failed: {e}")))?;
}
tokio::fs::rename(&self.temp_file, &self.checkpoint_file)
.await
.map_err(|e| Error::IO(format!("replace checkpoint file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("replace checkpoint file failed: {e}")))?;
*self.last_save.write().await = now;
@@ -183,17 +183,17 @@ impl CheckpointManager {
/// load checkpoint from file
async fn load_checkpoint_from_file(&self, file_path: &Path) -> Result<ScanProgress> {
if !file_path.exists() {
return Err(Error::NotFound(format!("checkpoint file not exists: {:?}", file_path)));
return Err(Error::NotFound(format!("checkpoint file not exists: {file_path:?}")));
}
// read file content
let content = tokio::fs::read_to_string(file_path)
.await
.map_err(|e| Error::IO(format!("read checkpoint file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("read checkpoint file failed: {e}")))?;
// deserialize
let checkpoint_data: CheckpointData =
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize checkpoint failed: {}", e)))?;
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize checkpoint failed: {e}")))?;
// validate checkpoint data
self.validate_checkpoint(&checkpoint_data)?;
@@ -223,7 +223,7 @@ impl CheckpointManager {
// checkpoint is too old (more than 24 hours), may be data expired
if checkpoint_age > Duration::from_secs(24 * 3600) {
return Err(Error::InvalidCheckpoint(format!("checkpoint data is too old: {:?}", checkpoint_age)));
return Err(Error::InvalidCheckpoint(format!("checkpoint data is too old: {checkpoint_age:?}")));
}
// validate version compatibility
@@ -245,21 +245,21 @@ impl CheckpointManager {
if self.checkpoint_file.exists() {
tokio::fs::remove_file(&self.checkpoint_file)
.await
.map_err(|e| Error::IO(format!("delete main checkpoint file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("delete main checkpoint file failed: {e}")))?;
}
// delete backup file
if self.backup_file.exists() {
tokio::fs::remove_file(&self.backup_file)
.await
.map_err(|e| Error::IO(format!("delete backup checkpoint file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("delete backup checkpoint file failed: {e}")))?;
}
// delete temp file
if self.temp_file.exists() {
tokio::fs::remove_file(&self.temp_file)
.await
.map_err(|e| Error::IO(format!("delete temp checkpoint file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("delete temp checkpoint file failed: {e}")))?;
}
info!("cleaned up all checkpoint files");
@@ -274,14 +274,14 @@ impl CheckpointManager {
let metadata = tokio::fs::metadata(&self.checkpoint_file)
.await
.map_err(|e| Error::IO(format!("get checkpoint file metadata failed: {}", e)))?;
.map_err(|e| Error::IO(format!("get checkpoint file metadata failed: {e}")))?;
let content = tokio::fs::read_to_string(&self.checkpoint_file)
.await
.map_err(|e| Error::IO(format!("read checkpoint file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("read checkpoint file failed: {e}")))?;
let checkpoint_data: CheckpointData =
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize checkpoint failed: {}", e)))?;
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize checkpoint failed: {e}")))?;
Ok(Some(CheckpointInfo {
file_size: metadata.len(),
+8 -9
View File
@@ -2580,11 +2580,11 @@ mod tests {
let temp_dir = std::path::PathBuf::from(test_base_dir);
if temp_dir.exists() {
if let Err(e) = fs::remove_dir_all(&temp_dir) {
panic!("Failed to remove test directory: {}", e);
panic!("Failed to remove test directory: {e}");
}
}
if let Err(e) = fs::create_dir_all(&temp_dir) {
panic!("Failed to create test directory: {}", e);
panic!("Failed to create test directory: {e}");
}
// create 4 disk dirs
@@ -2597,7 +2597,7 @@ mod tests {
for disk_path in &disk_paths {
if let Err(e) = fs::create_dir_all(disk_path) {
panic!("Failed to create disk directory {:?}: {}", disk_path, e);
panic!("Failed to create disk directory {disk_path:?}: {e}");
}
}
@@ -2768,13 +2768,13 @@ mod tests {
// Try to create bucket, handle case where it might already exist
match ecstore.make_bucket(bucket, &Default::default()).await {
Ok(_) => {
println!("Successfully created bucket: {}", bucket);
println!("Successfully created bucket: {bucket}");
}
Err(rustfs_ecstore::error::StorageError::BucketExists(_)) => {
println!("Bucket {} already exists, continuing with test", bucket);
println!("Bucket {bucket} already exists, continuing with test");
}
Err(e) => {
panic!("Failed to create bucket {}: {}", bucket, e);
panic!("Failed to create bucket {bucket}: {e}");
}
}
@@ -2833,14 +2833,13 @@ mod tests {
if retry_count >= 3 {
// If we still can't load after 3 retries, log and skip this verification
println!(
"Warning: Could not load persisted data after {} retries: {}. Skipping persistence verification.",
retry_count, e
"Warning: Could not load persisted data after {retry_count} retries: {e}. Skipping persistence verification."
);
println!("This is likely due to concurrent test execution and doesn't indicate a functional issue.");
// Just continue with the rest of the test
break DataUsageInfo::new(); // Use empty data to skip assertions
}
println!("Retry {} loading persisted data after error: {}", retry_count, e);
println!("Retry {retry_count} loading persisted data after error: {e}");
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
}
}
+12 -12
View File
@@ -121,9 +121,9 @@ impl LocalStatsManager {
}
}
let stats_file = data_dir.join(format!("scanner_stats_{}.json", node_id));
let backup_file = data_dir.join(format!("scanner_stats_{}.backup", node_id));
let temp_file = data_dir.join(format!("scanner_stats_{}.tmp", node_id));
let stats_file = data_dir.join(format!("scanner_stats_{node_id}.json"));
let backup_file = data_dir.join(format!("scanner_stats_{node_id}.backup"));
let temp_file = data_dir.join(format!("scanner_stats_{node_id}.tmp"));
Self {
node_id: node_id.to_string(),
@@ -172,10 +172,10 @@ impl LocalStatsManager {
async fn load_stats_from_file(&self, file_path: &Path) -> Result<LocalScanStats> {
let content = tokio::fs::read_to_string(file_path)
.await
.map_err(|e| Error::IO(format!("read stats file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("read stats file failed: {e}")))?;
let stats: LocalScanStats =
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize stats data failed: {}", e)))?;
serde_json::from_str(&content).map_err(|e| Error::Serialization(format!("deserialize stats data failed: {e}")))?;
Ok(stats)
}
@@ -194,24 +194,24 @@ impl LocalStatsManager {
// serialize
let json_data = serde_json::to_string_pretty(&stats)
.map_err(|e| Error::Serialization(format!("serialize stats data failed: {}", e)))?;
.map_err(|e| Error::Serialization(format!("serialize stats data failed: {e}")))?;
// atomic write
tokio::fs::write(&self.temp_file, json_data)
.await
.map_err(|e| Error::IO(format!("write temp stats file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("write temp stats file failed: {e}")))?;
// backup existing file
if self.stats_file.exists() {
tokio::fs::copy(&self.stats_file, &self.backup_file)
.await
.map_err(|e| Error::IO(format!("backup stats file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("backup stats file failed: {e}")))?;
}
// atomic replace
tokio::fs::rename(&self.temp_file, &self.stats_file)
.await
.map_err(|e| Error::IO(format!("replace stats file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("replace stats file failed: {e}")))?;
*self.last_save.write().await = now;
@@ -374,21 +374,21 @@ impl LocalStatsManager {
if self.stats_file.exists() {
tokio::fs::remove_file(&self.stats_file)
.await
.map_err(|e| Error::IO(format!("delete stats file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("delete stats file failed: {e}")))?;
}
// delete backup file
if self.backup_file.exists() {
tokio::fs::remove_file(&self.backup_file)
.await
.map_err(|e| Error::IO(format!("delete backup stats file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("delete backup stats file failed: {e}")))?;
}
// delete temp file
if self.temp_file.exists() {
tokio::fs::remove_file(&self.temp_file)
.await
.map_err(|e| Error::IO(format!("delete temp stats file failed: {}", e)))?;
.map_err(|e| Error::IO(format!("delete temp stats file failed: {e}")))?;
}
info!("cleanup all stats files");
+2 -2
View File
@@ -1007,7 +1007,7 @@ impl NodeScanner {
let object_size = self.estimate_object_size(&entry_path).await;
let entry = ScanResultEntry {
object_path: format!("{}/{}", bucket_name, object_name),
object_path: format!("{bucket_name}/{object_name}"),
bucket_name: bucket_name.to_string(),
object_size,
is_healthy: true, // assume most objects are healthy
@@ -1047,7 +1047,7 @@ impl NodeScanner {
// simulate some scan results
for i in 0..5 {
let entry = ScanResultEntry {
object_path: format!("/fallback-bucket/object_{}", i),
object_path: format!("/fallback-bucket/object_{i}"),
bucket_name: "fallback-bucket".to_string(),
object_size: 1024 * (i + 1),
is_healthy: true,
+2 -2
View File
@@ -203,7 +203,7 @@ impl NodeClient {
.get(url)
.send()
.await
.map_err(|e| Error::Other(format!("HTTP request failed: {}", e)))?;
.map_err(|e| Error::Other(format!("HTTP request failed: {e}")))?;
if !response.status().is_success() {
return Err(Error::Other(format!("HTTP status error: {}", response.status())));
@@ -212,7 +212,7 @@ impl NodeClient {
let summary = response
.json::<StatsSummary>()
.await
.map_err(|e| Error::Serialization(format!("deserialize stats data failed: {}", e)))?;
.map_err(|e| Error::Serialization(format!("deserialize stats data failed: {e}")))?;
Ok(summary)
}
+4 -4
View File
@@ -24,7 +24,7 @@ async fn test_endpoint_index_settings() -> anyhow::Result<()> {
let temp_dir = TempDir::new()?;
// create test disk paths
let disk_paths: Vec<_> = (0..4).map(|i| temp_dir.path().join(format!("disk{}", i))).collect();
let disk_paths: Vec<_> = (0..4).map(|i| temp_dir.path().join(format!("disk{i}"))).collect();
for path in &disk_paths {
tokio::fs::create_dir_all(path).await?;
@@ -60,9 +60,9 @@ async fn test_endpoint_index_settings() -> anyhow::Result<()> {
// validate all endpoint indexes are in valid range
for (i, ep) in endpoints.iter().enumerate() {
assert_eq!(ep.pool_idx, 0, "Endpoint {} pool_idx should be 0", i);
assert_eq!(ep.set_idx, 0, "Endpoint {} set_idx should be 0", i);
assert_eq!(ep.disk_idx, i as i32, "Endpoint {} disk_idx should be {}", i, i);
assert_eq!(ep.pool_idx, 0, "Endpoint {i} pool_idx should be 0");
assert_eq!(ep.set_idx, 0, "Endpoint {i} set_idx should be 0");
assert_eq!(ep.disk_idx, i as i32, "Endpoint {i} disk_idx should be {i}");
println!(
"Endpoint {} indices are valid: pool={}, set={}, disk={}",
i, ep.pool_idx, ep.set_idx, ep.disk_idx
+6 -6
View File
@@ -270,15 +270,15 @@ async fn test_performance_impact_measurement() {
};
println!("Performance impact measurement:");
println!(" Baseline duration: {:?}", baseline_duration);
println!(" With scanner duration: {:?}", with_scanner_duration);
println!(" Overhead: {} ms", overhead_ms);
println!(" Impact percentage: {:.2}%", impact_percentage);
println!(" Baseline duration: {baseline_duration:?}");
println!(" With scanner duration: {with_scanner_duration:?}");
println!(" Overhead: {overhead_ms} ms");
println!(" Impact percentage: {impact_percentage:.2}%");
println!(" Meets optimization goals: {}", benchmark.meets_optimization_goals());
// Verify optimization target (business impact < 10%)
// Note: In real environment this test may need longer time and real load
assert!(impact_percentage < 50.0, "Performance impact too high: {:.2}%", impact_percentage);
assert!(impact_percentage < 50.0, "Performance impact too high: {impact_percentage:.2}%");
io_monitor.stop().await;
}
@@ -308,7 +308,7 @@ async fn test_concurrent_scanner_operations() {
tokio::spawn(async move {
for _i in 0..5 {
if let Err(e) = scanner.force_save_checkpoint().await {
eprintln!("Checkpoint save failed: {}", e);
eprintln!("Checkpoint save failed: {e}");
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
+7 -7
View File
@@ -319,14 +319,14 @@ async fn test_optimized_performance_characteristics() {
// Create several test objects
for i in 0..10 {
let object_name = format!("perf-object-{}", i);
let object_name = format!("perf-object-{i}");
let test_data = vec![b'A' + (i % 26) as u8; 1024 * (i + 1)]; // Variable size objects
let mut put_reader = PutObjReader::from_vec(test_data);
let object_opts = rustfs_ecstore::store_api::ObjectOptions::default();
ecstore
.put_object(bucket_name, &object_name, &mut put_reader, &object_opts)
.await
.unwrap_or_else(|_| panic!("Failed to create object {}", object_name));
.unwrap_or_else(|_| panic!("Failed to create object {object_name}"));
}
// Create optimized scanner
@@ -340,7 +340,7 @@ async fn test_optimized_performance_characteristics() {
let scan_result = scanner.scan_cycle().await;
let scan_duration = start_time.elapsed();
println!("Optimized scan completed in: {:?}", scan_duration);
println!("Optimized scan completed in: {scan_duration:?}");
assert!(scan_result.is_ok(), "Performance scan should succeed");
// Verify the scan was reasonably fast (should be faster than old concurrent scanner)
@@ -359,11 +359,11 @@ async fn test_optimized_performance_characteristics() {
let _scan_result2 = scanner.scan_cycle().await;
let scan_duration2 = start_time2.elapsed();
println!("Second optimized scan completed in: {:?}", scan_duration2);
println!("Second optimized scan completed in: {scan_duration2:?}");
// Second scan should be similar or faster due to caching
let performance_ratio = scan_duration2.as_millis() as f64 / scan_duration.as_millis() as f64;
println!("Performance ratio (second/first): {:.2}", performance_ratio);
println!("Performance ratio (second/first): {performance_ratio:.2}");
// Clean up
let _ = std::fs::remove_dir_all(std::path::Path::new(TEST_DIR_PERF));
@@ -404,7 +404,7 @@ async fn test_optimized_load_balancing_and_throttling() {
];
for (expected_level, latency, qps, error_rate, connections) in load_scenarios {
println!("Testing load scenario: {:?}", expected_level);
println!("Testing load scenario: {expected_level:?}");
// Update business metrics to simulate load
node_scanner
@@ -416,7 +416,7 @@ async fn test_optimized_load_balancing_and_throttling() {
// Get current load level
let current_level = io_monitor.get_business_load_level().await;
println!("Detected load level: {:?}", current_level);
println!("Detected load level: {current_level:?}");
// Get throttling decision
let _current_metrics = io_monitor.get_current_metrics().await;
@@ -298,7 +298,7 @@ async fn test_scanner_performance_impact() {
let throttle_stats = throttler.get_throttle_stats().await;
println!("Performance test results:");
println!(" Load level: {:?}", load_level);
println!(" Load level: {load_level:?}");
println!(" Throttle decisions: {}", throttle_stats.total_decisions);
println!(" Average delay: {:?}", throttle_stats.average_delay);