tsift-resolution 0.1.65

Multi-strategy reference resolution, scoring heuristics, and path blocklist for tsift
Documentation
use std::path::Path;

pub fn relative_path_is_generated_artifact(relative: &str) -> bool {
    let relative = relative.trim_start_matches("./").replace('\\', "/");
    relative == ".tsift"
        || relative.starts_with(".tsift/")
        || relative.ends_with("/.tsift")
        || relative.contains("/.tsift/")
        || relative == ".agent-doc"
        || relative.starts_with(".agent-doc/")
        || relative.ends_with("/.agent-doc")
        || relative.contains("/.agent-doc/")
        || relative == "target"
        || relative.starts_with("target/")
        || relative.contains("/target/")
}

pub fn path_is_generated_artifact(root: &Path, source_root: &Path, path: &Path) -> bool {
    let mut relatives = Vec::new();
    if let Ok(relative) = path.strip_prefix(source_root) {
        relatives.push(relative.to_string_lossy().replace('\\', "/"));
    }
    if let Ok(relative) = path.strip_prefix(root) {
        relatives.push(relative.to_string_lossy().replace('\\', "/"));
    }
    if !path.is_absolute() {
        relatives.push(path.to_string_lossy().replace('\\', "/"));
    }
    relatives
        .iter()
        .any(|relative| relative_path_is_generated_artifact(relative))
}

pub fn index_snapshot_part_is_generated(root: &Path, source_root: &Path, part: &str) -> bool {
    let Some(rest) = part.strip_prefix("file:") else {
        return false;
    };
    let path = rest.split(':').next().unwrap_or(rest);
    path_is_generated_artifact(root, source_root, Path::new(path))
}

pub fn is_planner_config_path(path: &str) -> bool {
    let normalized = path.replace('\\', "/").to_ascii_lowercase();
    let file_name = normalized.rsplit('/').next().unwrap_or(normalized.as_str());
    normalized.starts_with(".github/")
        || normalized.starts_with(".codex/")
        || normalized.starts_with(".agent-doc/")
        || normalized.contains("/.github/")
        || matches!(
            file_name,
            "agents.md"
                | "claude.md"
                | "cargo.toml"
                | "cargo.lock"
                | "package.json"
                | "package-lock.json"
                | "pnpm-lock.yaml"
                | "yarn.lock"
                | "makefile"
                | "justfile"
                | "dockerfile"
                | "docker-compose.yml"
                | "docker-compose.yaml"
                | "config.toml"
                | "tsconfig.json"
        )
        || file_name.ends_with(".config.js")
        || file_name.ends_with(".config.ts")
        || file_name.ends_with(".yml")
        || file_name.ends_with(".yaml")
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    #[test]
    fn relative_path_is_tsift_dir() {
        assert!(relative_path_is_generated_artifact(".tsift"));
        assert!(relative_path_is_generated_artifact(".tsift/graph.db"));
        assert!(relative_path_is_generated_artifact("foo/.tsift"));
        assert!(relative_path_is_generated_artifact("foo/.tsift/bar"));
    }

    #[test]
    fn relative_path_is_agent_doc_dir() {
        assert!(relative_path_is_generated_artifact(".agent-doc"));
        assert!(relative_path_is_generated_artifact(".agent-doc/snapshots"));
    }

    #[test]
    fn relative_path_is_target_dir() {
        assert!(relative_path_is_generated_artifact("target"));
        assert!(relative_path_is_generated_artifact("target/debug"));
        assert!(relative_path_is_generated_artifact("foo/target/bar"));
    }

    #[test]
    fn relative_path_not_generated() {
        assert!(!relative_path_is_generated_artifact("src/main.rs"));
        assert!(!relative_path_is_generated_artifact("lib/index.ts"));
    }

    #[test]
    fn relative_path_normalizes_backslashes() {
        assert!(relative_path_is_generated_artifact("target\\debug"));
    }

    #[test]
    fn relative_path_strips_dot_slash() {
        assert!(relative_path_is_generated_artifact("./.tsift"));
    }

    #[test]
    fn path_is_generated_artifact_with_source_root() {
        let root = PathBuf::from("/project");
        let source_root = PathBuf::from("/project/src");
        let path = PathBuf::from("/project/src/.tsift/config");
        assert!(path_is_generated_artifact(&root, &source_root, &path));
    }

    #[test]
    fn path_is_generated_artifact_not_generated() {
        let root = PathBuf::from("/project");
        let source_root = PathBuf::from("/project/src");
        let path = PathBuf::from("/project/src/main.rs");
        assert!(!path_is_generated_artifact(&root, &source_root, &path));
    }

    #[test]
    fn index_snapshot_part_generated() {
        let root = PathBuf::from("/project");
        let source_root = PathBuf::from("/project");
        assert!(index_snapshot_part_is_generated(
            &root,
            &source_root,
            "file:target/debug/out.rs:line"
        ));
    }

    #[test]
    fn index_snapshot_part_not_generated() {
        let root = PathBuf::from("/project");
        let source_root = PathBuf::from("/project");
        assert!(!index_snapshot_part_is_generated(
            &root,
            &source_root,
            "file:src/main.rs:line"
        ));
    }

    #[test]
    fn index_snapshot_part_no_file_prefix() {
        let root = PathBuf::from("/project");
        let source_root = PathBuf::from("/project");
        assert!(!index_snapshot_part_is_generated(
            &root,
            &source_root,
            "scope:main"
        ));
    }

    #[test]
    fn is_planner_config_github() {
        assert!(is_planner_config_path(".github/workflows/ci.yml"));
    }

    #[test]
    fn is_planner_config_agents_md() {
        assert!(is_planner_config_path("AGENTS.md"));
    }

    #[test]
    fn is_planner_config_cargo() {
        assert!(is_planner_config_path("Cargo.toml"));
    }

    #[test]
    fn is_planner_config_yml_extension() {
        assert!(is_planner_config_path("config/production.yml"));
    }

    #[test]
    fn is_planner_config_config_ts() {
        assert!(is_planner_config_path("vite.config.ts"));
    }

    #[test]
    fn is_planner_config_not_config() {
        assert!(!is_planner_config_path("src/main.rs"));
        assert!(!is_planner_config_path("lib/index.ts"));
    }

    #[test]
    fn is_planner_config_case_insensitive() {
        assert!(is_planner_config_path(".GitHub/test.yml"));
    }

    #[test]
    fn is_planner_config_codex() {
        assert!(is_planner_config_path(".codex/hooks.json"));
    }
}