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 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 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 if !intent_dir.exists() {
43 std::fs::create_dir_all(&intent_dir)?;
44 }
45
46 let pool = create_pool(&db_path).await?;
48
49 run_migrations(&pool).await?;
51
52 Ok(ProjectContext {
53 root,
54 db_path,
55 pool,
56 })
57 }
58
59 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 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 #[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 let _type_check = |ctx: ProjectContext| {
102 let _ = format!("{:?}", ctx);
103 };
104 }
105}