use anyhow::Result;
use std::fs;
use std::path::PathBuf;
use crate::paths;
pub const BUILTIN_RULES: &str = r#"# devist core (always applied)
## Output discipline
- Output STRICT JSON matching the requested schema. No commentary outside the JSON.
- "facts" are DURABLE truths (architectural choices, conventions, preferences) — skip transient changes.
- "advice" is HIGH SIGNAL — missing tests, security issues, dependency mismatches, conflicting patterns. Skip nitpicks.
- Empty arrays are fine. Prefer fewer, higher-quality items.
## Safety
- Never suggest committing or hardcoding secrets, API keys, tokens, passwords, or credentials.
- Never suggest disabling type checks, tests, or lints to "make it pass".
- Never suggest force-pushing or rewriting shared git history without an explicit reason.
## Scope
- Don't suggest framework / language version migrations unless the user explicitly asks.
- Don't suggest adding tools/configs (linters, CI, test frameworks) the user hasn't expressed interest in.
- Don't comment on auto-generated files (lock files, build artifacts, .tsbuildinfo, node_modules, target/).
## Tone
- Be concise. State the issue, then the fix.
- Don't justify or hedge — explain decisions, don't apologize for them.
"#;
#[derive(Debug, Default, Clone)]
pub struct Rules {
pub user: Option<String>,
}
impl Rules {
pub fn load_global() -> Self {
let user = paths::worker_rules_file()
.ok()
.and_then(|p| read_nonempty(&p));
Self { user }
}
pub fn render(&self) -> String {
let mut out = String::with_capacity(BUILTIN_RULES.len() + 256);
out.push_str(BUILTIN_RULES.trim_end());
out.push_str("\n\n");
if let Some(u) = &self.user {
out.push_str("# User rules\n\n");
out.push_str(u.trim());
out.push('\n');
}
out
}
}
pub fn ensure_global_template() -> Result<PathBuf> {
let path = paths::worker_rules_file()?;
if path.exists() {
return Ok(path);
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&path, DEFAULT_USER_TEMPLATE)?;
Ok(path)
}
fn read_nonempty(path: &std::path::Path) -> Option<String> {
let s = fs::read_to_string(path).ok()?;
if s.trim().is_empty() {
None
} else {
Some(s)
}
}
const DEFAULT_USER_TEMPLATE: &str = "# User rules
Add anything devist core doesn't already cover.
## Tone
- Respond in Korean.
## Focus
- (Add areas you want extra attention on.)
## Skip
- (Add patterns you want explicitly ignored.)
";