devist 0.15.0

Project bootstrap CLI for AI-assisted development. Spin up new projects from templates, manage backends, and keep your codebase comprehensible.
// Two layers:
//   1. BUILTIN  — hardcoded devist core. Always injected. Not editable
//                 by users (it ships with the binary).
//   2. USER     — single global file at ~/.devist/worker/rules.md.
//                 Edited via the dashboard or by hand.
//
// Per-project rules were removed in v0.12 — too much overhead for too
// little benefit. Express project-specific intent in the global rules
// or in the project's own CLAUDE.md instead.

use anyhow::Result;
use std::fs;
use std::path::PathBuf;

use crate::paths;

/// Devist core rules. Compiled into the binary; users cannot edit.
/// Kept in a single source-controlled string so the dashboard can
/// display the same content (currently mirrored in the dashboard's
/// `BUILTIN_RULES` constant).
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 {
    /// User's global rules at ~/.devist/worker/rules.md (None if file
    /// is missing or empty).
    pub user: Option<String>,
}

impl Rules {
    /// Load the user's global rules file (if present).
    pub fn load_global() -> Self {
        let user = paths::worker_rules_file()
            .ok()
            .and_then(|p| read_nonempty(&p));
        Self { user }
    }

    /// Render = BUILTIN + (optional) user section. BUILTIN always present.
    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.)
";