- LOT C2 (§14.5.3) : use cases de configuration des embedders déclaratifs (List/Save/Delete + DescribeEmbedderEngines : modèles ONNX recommandés, environnement local détecté, stratégies compilées). UI EmbedderSettings. - LOT C3 (§14.5.5) : suggestion contextuelle best-effort à l'activation quand la mémoire dépasse le budget de recall sans embedder configuré (event EmbedderSuggested, anti-spam 1×/session, « ne plus demander »). - Contexte projet partagé .ideai/CONTEXT.md (model-agnostic) injecté à tous les agents/profils au lancement, avant la persona. UI ProjectContextPanel. Tests : backend workspace vert (0 échec) ; frontend 306/306. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
3.6 KiB
Rust
114 lines
3.6 KiB
Rust
//! [`OpenProject`] and [`ListProjects`] (ARCHITECTURE §6).
|
|
|
|
use std::sync::Arc;
|
|
|
|
use domain::ports::{FileSystem, ProjectStore, RemotePath};
|
|
use domain::{AgentManifest, Project, ProjectId};
|
|
|
|
use crate::error::AppError;
|
|
|
|
use super::meta::{from_json_bytes, join_root, ProjectMeta, AGENTS_FILE, IDEAI_DIR, PROJECT_FILE};
|
|
|
|
/// Input for [`OpenProject::execute`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct OpenProjectInput {
|
|
/// Id of the project to open (as known by the registry).
|
|
pub project_id: ProjectId,
|
|
}
|
|
|
|
/// Output of [`OpenProject::execute`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct OpenProjectOutput {
|
|
/// The opened project (registry source of truth for id/name/root/remote).
|
|
pub project: Project,
|
|
/// The project-local meta read from `.ideai/project.json`, if present and
|
|
/// readable. Read tolerantly: a missing/corrupt file does not fail the open.
|
|
pub meta: Option<ProjectMeta>,
|
|
/// The agent manifest read from `.ideai/agents.json`, if present. Tolerant:
|
|
/// absent in a brand-new project.
|
|
pub manifest: Option<AgentManifest>,
|
|
}
|
|
|
|
/// Loads a project, its `.ideai/project.json` meta and (tolerantly) its manifest.
|
|
pub struct OpenProject {
|
|
store: Arc<dyn ProjectStore>,
|
|
fs: Arc<dyn FileSystem>,
|
|
}
|
|
|
|
impl OpenProject {
|
|
/// Builds the use case from its injected ports.
|
|
#[must_use]
|
|
pub fn new(store: Arc<dyn ProjectStore>, fs: Arc<dyn FileSystem>) -> Self {
|
|
Self { store, fs }
|
|
}
|
|
|
|
/// Executes the open.
|
|
///
|
|
/// # Errors
|
|
/// - [`AppError::NotFound`] if the registry has no such project,
|
|
/// - [`AppError::Store`] on registry I/O failure.
|
|
///
|
|
/// Reading the `.ideai/` files is **tolerant**: their absence or a parse
|
|
/// failure yields `None` rather than an error, so a project whose `.ideai/`
|
|
/// was deleted can still be opened.
|
|
pub async fn execute(&self, input: OpenProjectInput) -> Result<OpenProjectOutput, AppError> {
|
|
let project = self.store.load_project(input.project_id).await?;
|
|
|
|
let meta = self
|
|
.read_optional_json::<ProjectMeta>(&project, PROJECT_FILE)
|
|
.await;
|
|
let manifest = self
|
|
.read_optional_json::<AgentManifest>(&project, AGENTS_FILE)
|
|
.await;
|
|
|
|
Ok(OpenProjectOutput {
|
|
project,
|
|
meta,
|
|
manifest,
|
|
})
|
|
}
|
|
|
|
/// Reads and parses a JSON file inside the project's `.ideai/`, tolerating
|
|
/// any failure (missing file, I/O error, parse error) by returning `None`.
|
|
async fn read_optional_json<T: for<'de> serde::Deserialize<'de>>(
|
|
&self,
|
|
project: &Project,
|
|
file: &str,
|
|
) -> Option<T> {
|
|
let path = RemotePath::new(join_root(&project.root, &format!("{IDEAI_DIR}/{file}")));
|
|
match self.fs.read(&path).await {
|
|
Ok(bytes) => from_json_bytes::<T>(&bytes).ok(),
|
|
Err(_) => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Output of [`ListProjects::execute`].
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ListProjectsOutput {
|
|
/// All projects known to the registry.
|
|
pub projects: Vec<Project>,
|
|
}
|
|
|
|
/// Lists the projects known to the registry.
|
|
pub struct ListProjects {
|
|
store: Arc<dyn ProjectStore>,
|
|
}
|
|
|
|
impl ListProjects {
|
|
/// Builds the use case from its injected port.
|
|
#[must_use]
|
|
pub fn new(store: Arc<dyn ProjectStore>) -> Self {
|
|
Self { store }
|
|
}
|
|
|
|
/// Executes the listing.
|
|
///
|
|
/// # Errors
|
|
/// [`AppError::Store`] on registry I/O failure.
|
|
pub async fn execute(&self) -> Result<ListProjectsOutput, AppError> {
|
|
let projects = self.store.list_projects().await?;
|
|
Ok(ListProjectsOutput { projects })
|
|
}
|
|
}
|