pub mod publish;
pub mod record;
pub mod relevance;
pub mod status;
pub mod validate;
use std::path::Path;
pub use record::{DigestTable, Record, RecordState};
pub use status::StatusError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FreshnessState {
Fresh,
Stale {
indexed_head: String,
current_head: String,
changed_files: usize,
},
Unknown {
reason: UnknownReason,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnknownReason {
NoIndexState,
GitUnavailable,
OtherWorktree,
ValidationBudget,
IndexChanged,
}
impl UnknownReason {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
UnknownReason::NoIndexState => "no-index-state",
UnknownReason::GitUnavailable => "git-unavailable",
UnknownReason::OtherWorktree => "other-worktree",
UnknownReason::ValidationBudget => "validation-budget",
UnknownReason::IndexChanged => "index-changed",
}
}
}
#[must_use]
pub fn repo_id(root: &Path) -> String {
root.file_name()
.and_then(|n| n.to_str())
.unwrap_or("repo")
.to_string()
}
fn short(head: &str) -> &str {
&head[..head.len().min(7)]
}
#[must_use]
pub fn stale_line(repo: &str, indexed_head: &str, current_head: &str, changed: usize) -> String {
format!(
"devscout: index for {repo} is stale (indexed at {}, HEAD {}; {changed} changed files) — rebuild with devscout map",
short(indexed_head),
short(current_head),
)
}
#[must_use]
pub fn other_worktree_line(repo: &str) -> String {
format!(
"devscout: index for {repo} was built in another worktree — run devscout map here to use it"
)
}
#[must_use]
pub fn index_changed_line(repo: &str) -> String {
format!(
"devscout: index for {repo} changed while this query ran, or a map is running or was interrupted — re-run the query, or rebuild with devscout map"
)
}
#[must_use]
pub fn validation_budget_line(repo: &str) -> String {
format!(
"devscout: freshness of the index for {repo} could not be verified (more changes than the check reads) — rebuild with devscout map"
)
}
#[must_use]
pub fn uncertifiable_line(repo: &str) -> String {
format!(
"devscout: index for {repo} has no freshness record this version can verify — rebuild with devscout map"
)
}
#[must_use]
pub fn index_freshness_state(root: &Path) -> FreshnessState {
validate::begin(root, root).finish().state
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn no_index_state_sidecar_is_unknown_with_that_reason() {
let dir = std::env::temp_dir().join(format!(
"devscout-freshness-unit-{}-{:?}",
std::process::id(),
std::time::SystemTime::now()
));
fs::create_dir_all(&dir).unwrap();
assert_eq!(
index_freshness_state(&dir),
FreshnessState::Unknown {
reason: UnknownReason::NoIndexState
}
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn unknown_reason_words_are_stable() {
assert_eq!(UnknownReason::NoIndexState.as_str(), "no-index-state");
assert_eq!(UnknownReason::GitUnavailable.as_str(), "git-unavailable");
assert_eq!(UnknownReason::OtherWorktree.as_str(), "other-worktree");
assert_eq!(
UnknownReason::ValidationBudget.as_str(),
"validation-budget"
);
assert_eq!(UnknownReason::IndexChanged.as_str(), "index-changed");
}
#[test]
fn each_reason_has_its_own_line_with_a_stable_key_phrase() {
assert!(other_worktree_line("repo").contains("another worktree"));
assert!(index_changed_line("repo").contains("changed while"));
assert!(validation_budget_line("repo").contains("could not be verified"));
assert!(uncertifiable_line("repo").contains("rebuild with devscout map"));
}
#[test]
fn the_stale_line_keeps_the_legacy_shape() {
assert_eq!(
stale_line("repo", "abcdef0123", "1234567890", 2),
"devscout: index for repo is stale (indexed at abcdef0, HEAD 1234567; 2 changed files) — rebuild with devscout map"
);
}
}