#![allow(dead_code)]
use anyhow::{Context, Result};
use std::path::PathBuf;
use crate::db::intent::{Intent, Strength};
use crate::db::pattern::Pattern;
pub mod claude_code;
pub mod limits;
#[derive(Debug, Default)]
pub struct CompiledArtifacts {
pub text_blocks: Vec<TextBlock>,
pub gate_rules: Vec<GateRule>,
pub files: Vec<GeneratedFile>,
}
#[derive(Debug, Clone)]
pub struct TextBlock {
pub id: String,
pub target: BlockTarget,
pub body: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BlockTarget {
GlobalClaudeMd,
ProjectClaudeMd(PathBuf),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct GateRule {
pub intent_id: String,
pub tool: Option<String>,
pub contains: Option<String>,
pub decision: String,
pub reason: String,
}
#[derive(Debug, Clone)]
pub struct GeneratedFile {
pub path: PathBuf,
pub body: String,
}
pub trait RuntimeAdapter {
fn compile(&self, intents: &[Intent], patterns: &[Pattern]) -> Result<CompiledArtifacts>;
fn apply(&self, artifacts: &CompiledArtifacts) -> Result<ApplyReport>;
fn name(&self) -> &'static str;
}
#[derive(Debug, Default)]
pub struct ApplyReport {
pub files_written: Vec<PathBuf>,
pub blocks_updated: Vec<String>,
pub gate_rules_count: usize,
}
pub fn block_begin_marker(id: &str) -> String {
format!("<!-- BEGIN ASURADA:{} -->", id)
}
pub fn block_end_marker(id: &str) -> String {
format!("<!-- END ASURADA:{} -->", id)
}
pub fn upsert_managed_block(file_path: &std::path::Path, block: &TextBlock) -> Result<bool> {
let begin = block_begin_marker(&block.id);
let end = block_end_marker(&block.id);
let existing = if file_path.exists() {
std::fs::read_to_string(file_path)
.with_context(|| format!("read {}", file_path.display()))?
} else {
String::new()
};
let new_block = format!("{}\n{}\n{}", begin, block.body.trim_end(), end);
let updated = if let (Some(b), Some(e)) = (existing.find(&begin), existing.find(&end)) {
let e_end = e + end.len();
let mut out = String::with_capacity(existing.len() + new_block.len());
out.push_str(&existing[..b]);
out.push_str(&new_block);
out.push_str(&existing[e_end..]);
out
} else if existing.trim().is_empty() {
new_block
} else {
format!("{}\n\n{}\n", existing.trim_end(), new_block)
};
if updated == existing {
return Ok(false);
}
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent)?;
}
let tmp = file_path.with_extension(format!(
"{}.asurada.tmp",
file_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("md")
));
std::fs::write(&tmp, updated)?;
std::fs::rename(&tmp, file_path)?;
Ok(true)
}
pub fn remove_managed_block(file_path: &std::path::Path, block_id: &str) -> Result<bool> {
if !file_path.exists() {
return Ok(false);
}
let begin = block_begin_marker(block_id);
let end = block_end_marker(block_id);
let existing = std::fs::read_to_string(file_path)?;
let (Some(b), Some(e)) = (existing.find(&begin), existing.find(&end)) else {
return Ok(false);
};
let e_end = e + end.len();
let mut out = String::with_capacity(existing.len());
out.push_str(existing[..b].trim_end_matches('\n'));
out.push_str(existing[e_end..].trim_start_matches('\n'));
std::fs::write(file_path, out)?;
Ok(true)
}
pub fn strength_label(s: Strength) -> &'static str {
match s {
Strength::Preference => "선호",
Strength::Principle => "원칙",
Strength::Context => "맥락",
}
}