Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
100 lines
3.9 KiB
Rust
100 lines
3.9 KiB
Rust
//! [`ProjectMeta`] — the on-disk shape of `.ideai/project.json` (ARCHITECTURE §9.1).
|
|
//!
|
|
//! This is the *project-local* metadata that travels with the code (it lives
|
|
//! inside the project root, is versionable, and is independent of the machine).
|
|
//! The known-projects **registry** is a separate, machine-local concern owned by
|
|
//! the [`domain::ports::ProjectStore`] adapter (ARCHITECTURE §9.2).
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use domain::{Project, ProjectId, ProjectPath, RemoteRef};
|
|
|
|
use crate::error::AppError;
|
|
|
|
/// The `.ideai/` directory name inside a project root.
|
|
pub(crate) const IDEAI_DIR: &str = ".ideai";
|
|
|
|
/// The project-meta file name inside `.ideai/`.
|
|
pub(crate) const PROJECT_FILE: &str = "project.json";
|
|
|
|
/// The agent manifest file name inside `.ideai/`.
|
|
pub(crate) const AGENTS_FILE: &str = "agents.json";
|
|
|
|
/// Current schema version of `project.json`.
|
|
pub(crate) const PROJECT_META_VERSION: u32 = 1;
|
|
|
|
/// Serialised contents of `.ideai/project.json`.
|
|
///
|
|
/// Carries the project's identity and the metadata needed to reopen it: its
|
|
/// stable id, display name, the default agent profile, the remote reference and
|
|
/// the creation timestamp. The `root` itself is *not* stored here — the file
|
|
/// already lives at `<root>/.ideai/project.json`, so the root is implied by the
|
|
/// file's location (and authoritatively held by the registry).
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ProjectMeta {
|
|
/// Schema version of this file.
|
|
pub version: u32,
|
|
/// Stable project id (matches the registry entry).
|
|
pub id: ProjectId,
|
|
/// Display name.
|
|
pub name: String,
|
|
/// Default agent profile id, if one has been chosen (`null` until first-run
|
|
/// / profile selection in later lots).
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub default_profile_id: Option<String>,
|
|
/// Where the project physically lives.
|
|
pub remote: RemoteRef,
|
|
/// Creation timestamp, epoch milliseconds.
|
|
pub created_at: i64,
|
|
}
|
|
|
|
impl ProjectMeta {
|
|
/// Builds the meta image from a [`Project`].
|
|
#[must_use]
|
|
pub fn from_project(project: &Project, default_profile_id: Option<String>) -> Self {
|
|
Self {
|
|
version: PROJECT_META_VERSION,
|
|
id: project.id,
|
|
name: project.name.clone(),
|
|
default_profile_id,
|
|
remote: project.remote.clone(),
|
|
created_at: project.created_at,
|
|
}
|
|
}
|
|
|
|
/// Reconstructs a validated [`Project`] from this meta and its (registry-known)
|
|
/// root.
|
|
///
|
|
/// # Errors
|
|
/// Returns [`AppError::Invalid`] if the stored fields violate a domain
|
|
/// invariant (e.g. empty name).
|
|
pub fn into_project(self, root: ProjectPath) -> Result<Project, AppError> {
|
|
Project::new(self.id, self.name, root, self.remote, self.created_at)
|
|
.map_err(|e| AppError::Invalid(e.to_string()))
|
|
}
|
|
}
|
|
|
|
/// Serialises a value to pretty JSON bytes, mapping failures to [`AppError`].
|
|
pub(crate) fn to_json_bytes<T: Serialize>(value: &T) -> Result<Vec<u8>, AppError> {
|
|
serde_json::to_vec_pretty(value)
|
|
.map(|mut v| {
|
|
v.push(b'\n');
|
|
v
|
|
})
|
|
.map_err(|e| AppError::Store(format!("serialize failed: {e}")))
|
|
}
|
|
|
|
/// Deserialises JSON bytes, mapping failures to [`AppError`].
|
|
pub(crate) fn from_json_bytes<T: for<'de> Deserialize<'de>>(bytes: &[u8]) -> Result<T, AppError> {
|
|
serde_json::from_slice(bytes).map_err(|e| AppError::Store(format!("deserialize failed: {e}")))
|
|
}
|
|
|
|
/// Joins a project root with a relative path segment using a POSIX-style
|
|
/// separator. Paths inside `.ideai/` are always written with `/`, which is valid
|
|
/// on every platform we target (Windows `tokio::fs` accepts `/`).
|
|
pub(crate) fn join_root(root: &ProjectPath, rel: &str) -> String {
|
|
let base = root.as_str().trim_end_matches(['/', '\\']);
|
|
format!("{base}/{rel}")
|
|
}
|