pub(crate) mod plan;
pub(crate) mod schema;
pub(in crate::install) mod writer;
#[cfg(test)]
#[path = "tests.rs"]
mod tests;
use sha2::{Digest, Sha256};
use std::path::PathBuf;
pub(crate) const CURSOR_HOOK_FAILURE_POLICY_LINE: &str = "hook_failure_policy: host_continues";
pub(crate) const CURSOR_SESSION_INIT_LINE: &str = "session-init: not supported on cursor";
pub(crate) fn cursor_hooks_path() -> PathBuf {
crate::install::paths::cursor_hooks_path()
}
pub(crate) fn cursor_mcp_path() -> PathBuf {
crate::install::paths::cursor_mcp_path()
}
pub(crate) fn cursor_detected() -> bool {
crate::install::paths::cursor_dir().exists()
|| cursor_hooks_path().exists()
|| cursor_mcp_path().exists()
}
pub(crate) fn cursor_renderer_supported() -> bool {
cfg!(unix)
}
pub(crate) const CURSOR_INSTALL_CONTRACT_VERSION: i64 = 1;
pub(crate) const CURSOR_RECEIPT_SCHEMA_VERSION: i64 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CursorCapabilityGates {
pub observe_total_failure_policy: bool,
pub mcp_specific_per_call_id: bool,
pub summarize_reader_proven: bool,
}
pub(crate) const CURRENT_CAPABILITY_GATES: CursorCapabilityGates = CursorCapabilityGates {
observe_total_failure_policy: false,
mcp_specific_per_call_id: false,
summarize_reader_proven: false,
};
pub(crate) fn canonical_json_digest(value: &serde_json::Value) -> String {
let canonical = canonicalize(value);
let serialized = serde_json::to_string(&canonical).unwrap_or_default();
let mut hasher = Sha256::new();
hasher.update(serialized.as_bytes());
format!("{:x}", hasher.finalize())
}
fn canonicalize(value: &serde_json::Value) -> serde_json::Value {
match value {
serde_json::Value::Object(map) => {
let mut sorted = std::collections::BTreeMap::new();
for (key, entry) in map {
sorted.insert(key.clone(), canonicalize(entry));
}
serde_json::Value::Object(sorted.into_iter().collect())
}
serde_json::Value::Array(items) => {
serde_json::Value::Array(items.iter().map(canonicalize).collect())
}
other => other.clone(),
}
}