Skip to main content

kiss_coding/
trust.rs

1//! Project trust: gate loading of project-local resources behind a saved
2//! decision in `~/.kiss/agent/trust.json` (parent-folder decisions apply).
3
4use crate::settings::ProjectTrustDefault;
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7use std::path::{Path, PathBuf};
8
9#[derive(Debug, Default, Serialize, Deserialize)]
10struct TrustFile {
11    #[serde(flatten)]
12    decisions: BTreeMap<String, bool>,
13}
14
15pub fn trust_file_path() -> Option<PathBuf> {
16    dirs::home_dir().map(|h| h.join(".kiss/agent/trust.json"))
17}
18
19fn read_trust_file() -> TrustFile {
20    trust_file_path()
21        .and_then(|p| std::fs::read_to_string(p).ok())
22        .and_then(|t| serde_json::from_str(&t).ok())
23        .unwrap_or_default()
24}
25
26/// Does this project directory carry project-local resources that need a
27/// trust decision at all?
28pub fn has_project_resources(cwd: &Path) -> bool {
29    cwd.join(".kiss").is_dir() || cwd.join(".agents/skills").is_dir()
30}
31
32/// Saved decision for `cwd` or any ancestor.
33pub fn saved_decision(cwd: &Path) -> Option<bool> {
34    let file = read_trust_file();
35    for dir in cwd.ancestors() {
36        if let Some(&decision) = file.decisions.get(&dir.display().to_string()) {
37            return Some(decision);
38        }
39    }
40    None
41}
42
43pub fn save_decision(path: &Path, trusted: bool) -> anyhow::Result<()> {
44    let file_path = trust_file_path().ok_or_else(|| anyhow::anyhow!("no home directory"))?;
45    if let Some(parent) = file_path.parent() {
46        std::fs::create_dir_all(parent)?;
47    }
48    let mut file = read_trust_file();
49    file.decisions.insert(path.display().to_string(), trusted);
50    std::fs::write(&file_path, serde_json::to_string_pretty(&file)?)?;
51    Ok(())
52}
53
54/// Resolve trust non-interactively (print/json modes and CLI overrides).
55pub fn resolve_non_interactive(
56    cwd: &Path,
57    cli_override: Option<bool>,
58    default: ProjectTrustDefault,
59) -> bool {
60    if let Some(explicit) = cli_override {
61        return explicit;
62    }
63    if !has_project_resources(cwd) {
64        return true; // nothing project-local to gate
65    }
66    if let Some(saved) = saved_decision(cwd) {
67        return saved;
68    }
69    matches!(default, ProjectTrustDefault::Always)
70}