Skip to main content

aida_core/
project.rs

1use anyhow::Result;
2use std::env;
3use std::path::PathBuf;
4
5use crate::registry::{get_registry_path, Registry};
6
7/// Result of checking for migration status
8#[derive(Debug)]
9pub enum MigrationCheck {
10    /// No migration detected, use this path
11    NoMigration(PathBuf),
12    /// YAML was migrated to SQLite, use the SQLite path instead
13    MigratedToSqlite {
14        yaml_path: PathBuf,
15        sqlite_path: PathBuf,
16    },
17    /// SQLite exists alongside YAML but no marker - potential stale data
18    PossibleStaleYaml {
19        yaml_path: PathBuf,
20        sqlite_path: PathBuf,
21    },
22}
23
24/// Check if a YAML file has been migrated to SQLite
25pub fn check_migration_status(yaml_path: &PathBuf) -> MigrationCheck {
26    // Check if corresponding SQLite file exists
27    let sqlite_path = yaml_path.with_extension("db");
28
29    if yaml_path.exists() && sqlite_path.exists() {
30        // Both files exist - check for migration marker at the START of YAML file
31        // The marker should be at the beginning, not embedded in nested content
32        if let Ok(content) = std::fs::read_to_string(yaml_path) {
33            // Check first few lines for the migration marker (comment + field)
34            let first_lines: String = content.lines().take(5).collect::<Vec<_>>().join("\n");
35            if first_lines.contains("migrated_to:") {
36                return MigrationCheck::MigratedToSqlite {
37                    yaml_path: yaml_path.clone(),
38                    sqlite_path,
39                };
40            }
41        }
42        // Both exist but no marker at start - potential stale data situation
43        return MigrationCheck::PossibleStaleYaml {
44            yaml_path: yaml_path.clone(),
45            sqlite_path,
46        };
47    }
48
49    // Only SQLite exists - use it
50    if sqlite_path.exists() && !yaml_path.exists() {
51        return MigrationCheck::NoMigration(sqlite_path);
52    }
53
54    // Default: use the YAML path (or it doesn't exist yet)
55    MigrationCheck::NoMigration(yaml_path.clone())
56}
57
58/// Determines the requirements file path to use based on the available information.
59/// This version does NOT check for migration - use `determine_requirements_path_with_migration_check`
60/// for migration-aware path resolution.
61pub fn determine_requirements_path(project_option: Option<&str>) -> Result<PathBuf> {
62    // Check if requirements file exists in current directory - but only if we're not explicitly
63    // specifying a project via command line option or environment variable
64    let use_local_file = project_option.is_none() && env::var("REQ_DB_NAME").is_err();
65
66    // Check for requirements.db first (SQLite preferred for MCP/concurrent access)
67    let current_dir_db = PathBuf::from("requirements.db");
68    if use_local_file && current_dir_db.exists() {
69        return Ok(current_dir_db);
70    }
71
72    let current_dir_path = PathBuf::from("requirements.yaml");
73    if use_local_file && current_dir_path.exists() {
74        return Ok(current_dir_path);
75    }
76
77    // Get the registry path and ensure it exists
78    let registry_path = get_registry_path()?;
79    if !registry_path.exists() {
80        Registry::create_default(&registry_path)?;
81    }
82
83    // Load the registry
84    let registry = Registry::load(&registry_path)?;
85
86    // Priority 1: Use the command line project option if provided
87    if let Some(project_name) = project_option {
88        if let Some(project) = registry.get_project(project_name) {
89            return Ok(PathBuf::from(&project.path));
90        } else {
91            anyhow::bail!("Project '{}' not found in registry", project_name);
92        }
93    }
94
95    // Priority 2: Use the REQ_DB_NAME environment variable if set
96    if let Ok(env_project) = env::var("REQ_DB_NAME") {
97        if let Some(project) = registry.get_project(&env_project) {
98            return Ok(PathBuf::from(&project.path));
99        } else {
100            anyhow::bail!(
101                "Project '{}' from REQ_DB_NAME not found in registry",
102                env_project
103            );
104        }
105    }
106
107    // Priority 3: Check if there's only one project in the registry
108    if registry.projects.len() == 1 {
109        let (_, project) = registry.projects.iter().next().unwrap();
110        return Ok(PathBuf::from(&project.path));
111    }
112
113    // Priority 4: Use the default project if configured in registry
114    if let Some((_, default_project)) = registry.get_default_project() {
115        return Ok(PathBuf::from(&default_project.path));
116    }
117
118    // If we got here, there's no clear project to use
119    anyhow::bail!(
120        "Could not determine requirements file. Please specify a project with -p, \
121         set REQ_DB_NAME environment variable, or run `aida init` in your project directory"
122    )
123}
124
125/// Lists available projects from the registry
126pub fn list_available_projects() -> Result<Vec<(String, String)>> {
127    let registry_path = get_registry_path()?;
128    if !registry_path.exists() {
129        Registry::create_default(&registry_path)?;
130    }
131
132    let registry = Registry::load(&registry_path)?;
133    let mut projects = Vec::new();
134
135    for (name, project) in &registry.projects {
136        projects.push((name.clone(), project.description.clone()));
137    }
138
139    Ok(projects)
140}