intent_engine/
project.rs

1use crate::db::{create_pool, run_migrations};
2use crate::error::{IntentError, Result};
3use sqlx::SqlitePool;
4use std::path::PathBuf;
5
6const INTENT_DIR: &str = ".intent-engine";
7const DB_FILE: &str = "project.db";
8
9#[derive(Debug)]
10pub struct ProjectContext {
11    pub root: PathBuf,
12    pub db_path: PathBuf,
13    pub pool: SqlitePool,
14}
15
16impl ProjectContext {
17    /// Find the project root by searching upwards for .intent-engine directory
18    pub fn find_project_root() -> Option<PathBuf> {
19        let mut current = std::env::current_dir().ok()?;
20
21        loop {
22            let intent_dir = current.join(INTENT_DIR);
23            if intent_dir.exists() && intent_dir.is_dir() {
24                return Some(current);
25            }
26
27            if !current.pop() {
28                break;
29            }
30        }
31
32        None
33    }
34
35    /// Initialize a new Intent-Engine project in the current directory
36    pub async fn initialize_project() -> Result<Self> {
37        let root = std::env::current_dir()?;
38        let intent_dir = root.join(INTENT_DIR);
39        let db_path = intent_dir.join(DB_FILE);
40
41        // Create .intent-engine directory if it doesn't exist
42        if !intent_dir.exists() {
43            std::fs::create_dir_all(&intent_dir)?;
44        }
45
46        // Create database connection
47        let pool = create_pool(&db_path).await?;
48
49        // Run migrations
50        run_migrations(&pool).await?;
51
52        Ok(ProjectContext {
53            root,
54            db_path,
55            pool,
56        })
57    }
58
59    /// Load an existing project context
60    pub async fn load() -> Result<Self> {
61        let root = Self::find_project_root().ok_or(IntentError::NotAProject)?;
62        let db_path = root.join(INTENT_DIR).join(DB_FILE);
63
64        let pool = create_pool(&db_path).await?;
65
66        Ok(ProjectContext {
67            root,
68            db_path,
69            pool,
70        })
71    }
72
73    /// Load project context, initializing if necessary (for write commands)
74    pub async fn load_or_init() -> Result<Self> {
75        match Self::load().await {
76            Ok(ctx) => Ok(ctx),
77            Err(IntentError::NotAProject) => Self::initialize_project().await,
78            Err(e) => Err(e),
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    // Note: Tests that modify the current directory are intentionally limited
88    // because they can interfere with other tests running in parallel.
89    // These functionalities are thoroughly tested by integration tests.
90
91    #[test]
92    fn test_constants() {
93        assert_eq!(INTENT_DIR, ".intent-engine");
94        assert_eq!(DB_FILE, "project.db");
95    }
96
97    #[test]
98    fn test_project_context_debug() {
99        // Just verify that ProjectContext implements Debug
100        // We can't easily create one without side effects in a unit test
101        let _type_check = |ctx: ProjectContext| {
102            let _ = format!("{:?}", ctx);
103        };
104    }
105}