use std::borrow::Cow;
use std::path::{Path, PathBuf};
use thiserror::Error;
pub const MARKER_KEY: &str = "_sentinel";
pub const STATE_DIR: &str = ".sentinel";
pub const WAIVER_DIR: &str = "sentinel";
pub const WAIVERS_FILE: &str = "sentinel/waivers.toml";
pub const RULE_PREFIX: &str = "sentinel.";
const MODERN_RULE_PREFIX: &str = "pushkin.";
pub const CODEX_RULES: &str = ".codex/rules/sentinel.rules";
pub const AUGGIE_SCRIPT: &str = ".augment/hooks/sentinel.sh";
pub const OPENCODE_PLUGIN: &str = ".opencode/plugin/sentinel.ts";
pub const HERMES_HOOK: &str = ".hermes/hooks/sentinel.json";
pub const HERMES_PLUGIN_DIR: &str = "sentinel-gate";
pub const HERMES_DRAFT_DIR: &str = "sentinel_gate";
#[must_use]
pub fn modern_rule_id(rule: &str) -> Cow<'_, str> {
match rule.strip_prefix(RULE_PREFIX) {
Some(tail) => Cow::Owned(format!("{MODERN_RULE_PREFIX}{tail}")),
None => Cow::Borrowed(rule),
}
}
#[must_use]
pub fn legacy_rule_id(rule: &str) -> Cow<'_, str> {
match rule.strip_prefix(MODERN_RULE_PREFIX) {
Some(tail) => Cow::Owned(format!("{RULE_PREFIX}{tail}")),
None => Cow::Borrowed(rule),
}
}
#[derive(Debug, Error)]
pub enum MigrationError {
#[error(
"both {old} and {new} exist — refusing to guess which is current. \
A human must reconcile them (keep one, remove or merge the other), \
then re-run."
)]
BothExist { old: String, new: String },
#[error("cannot rename {old} to {new}: {source}")]
Rename {
old: String,
new: String,
source: std::io::Error,
},
}
#[derive(Debug)]
pub struct MigratedDir {
pub old: PathBuf,
pub new: PathBuf,
}
pub fn migrate_dirs(root: &Path) -> Result<Vec<MigratedDir>, MigrationError> {
let mut renamed = Vec::new();
let state_pair = (root.join(STATE_DIR), root.join(".pushkin"));
if state_pair.0.is_dir() {
rename_pair(&state_pair.0, &state_pair.1, &mut renamed)?;
}
let waiver_pair = (root.join(WAIVER_DIR), root.join("pushkin"));
if root.join(WAIVERS_FILE).is_file() {
rename_pair(&waiver_pair.0, &waiver_pair.1, &mut renamed)?;
}
Ok(renamed)
}
fn rename_pair(
old: &Path,
new: &Path,
renamed: &mut Vec<MigratedDir>,
) -> Result<(), MigrationError> {
if new.exists() {
return Err(MigrationError::BothExist {
old: format!("{}/", old.display()),
new: format!("{}/", new.display()),
});
}
std::fs::rename(old, new).map_err(|source| MigrationError::Rename {
old: old.display().to_string(),
new: new.display().to_string(),
source,
})?;
renamed.push(MigratedDir {
old: old.to_path_buf(),
new: new.to_path_buf(),
});
Ok(())
}